Tethering offload: add/remove IPv6 forwarding rules on ND events.
Use IpNeighborMonitor to listen for ND cache events on the
downstream interface, and push downstream IPv6 fowarding rules
to netd.
Rules are pushed when:
- IPv6 neighbours appear/disappear on the downstream interface.
- The upstream changes.
Test: new unit test
Change-Id: I7b01ba179a4d6bb248fd6c4994e48800613a4efa
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b4d49c0..0d369dc 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -40,12 +40,14 @@
import android.net.dhcp.DhcpServingParamsParcelExt;
import android.net.dhcp.IDhcpLeaseCallbacks;
import android.net.dhcp.IDhcpServer;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.shared.NetdUtils;
import android.net.shared.RouteUtils;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.SharedLog;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -59,14 +61,17 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Random;
@@ -149,6 +154,12 @@
/** Capture IpServer dependencies, for injection. */
public abstract static class Dependencies {
+ /** Create an IpNeighborMonitor to be used by this IpServer */
+ public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
+ IpNeighborMonitor.NeighborEventConsumer consumer) {
+ return new IpNeighborMonitor(handler, log, consumer);
+ }
+
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -159,6 +170,15 @@
return InterfaceParams.getByName(ifName);
}
+ /** Get |ifName|'s interface index. */
+ public int getIfindex(String ifName) {
+ try {
+ return NetworkInterface.getByName(ifName).getIndex();
+ } catch (IOException | NullPointerException e) {
+ Log.e(TAG, "Can't determine interface index for interface " + ifName);
+ return 0;
+ }
+ }
/** Create a DhcpServer instance to be used by IpServer. */
public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb);
@@ -184,6 +204,8 @@
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
+ // new neighbor cache entry on our interface
+ public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -223,6 +245,40 @@
@NonNull
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
+ private int mLastIPv6UpstreamIfindex = 0;
+
+ private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
+ public void accept(NeighborEvent e) {
+ sendMessage(CMD_NEIGHBOR_EVENT, e);
+ }
+ }
+
+ static class Ipv6ForwardingRule {
+ public final int upstreamIfindex;
+ public final int downstreamIfindex;
+ public final Inet6Address address;
+ public final MacAddress srcMac;
+ public final MacAddress dstMac;
+
+ Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
+ MacAddress srcMac, MacAddress dstMac) {
+ this.upstreamIfindex = upstreamIfindex;
+ this.downstreamIfindex = downstreamIfIndex;
+ this.address = address;
+ this.srcMac = srcMac;
+ this.dstMac = dstMac;
+ }
+
+ public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+ return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+ dstMac);
+ }
+ }
+ private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
+ new LinkedHashMap<>();
+
+ private final IpNeighborMonitor mIpNeighborMonitor;
+
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
@@ -240,6 +296,12 @@
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
+ mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
+ new MyNeighborEventConsumer());
+ if (!mIpNeighborMonitor.start()) {
+ mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
+ }
+
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -607,13 +669,16 @@
}
RaParams params = null;
+ int upstreamIfindex = 0;
if (v6only != null) {
+ final String upstreamIface = v6only.getInterfaceName();
+
params = new RaParams();
params.mtu = v6only.getMtu();
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
- if (params.hasDefaultRoute) params.hopLimit = getHopLimit(v6only.getInterfaceName());
+ if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
@@ -627,12 +692,18 @@
params.dnses.add(dnsServer);
}
}
+
+ upstreamIfindex = mDeps.getIfindex(upstreamIface);
}
+
// If v6only is null, we pass in null to setRaParams(), which handles
// deprecation of any existing RA data.
setRaParams(params);
mLastIPv6LinkProperties = v6only;
+
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null);
+ mLastIPv6UpstreamIfindex = upstreamIfindex;
}
private void configureLocalIPv6Routes(
@@ -727,6 +798,73 @@
}
}
+ private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ try {
+ mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex,
+ rule.address.getAddress(), mInterfaceParams.macAddr.toByteArray(),
+ rule.dstMac.toByteArray());
+ mIpv6ForwardingRules.put(rule.address, rule);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Could not add IPv6 downstream rule: " + e);
+ }
+ }
+
+ private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+ try {
+ mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress());
+ if (removeFromMap) {
+ mIpv6ForwardingRules.remove(rule.address);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Could not remove IPv6 downstream rule: " + e);
+ }
+ }
+
+ // Convenience method to replace a rule with the same rule on a new upstream interface.
+ // Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions.
+ // Relies on the fact that rules are in a map indexed by IP address.
+ private void updateIpv6ForwardingRule(Ipv6ForwardingRule rule, int newIfindex) {
+ addIpv6ForwardingRule(rule.onNewUpstream(newIfindex));
+ removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+ }
+
+ // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
+ // changes or if a neighbor event is received.
+ private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
+ NeighborEvent e) {
+ // If the upstream interface has changed, remove all rules and re-add them with the new
+ // upstream interface.
+ if (prevUpstreamIfindex != upstreamIfindex) {
+ for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+ updateIpv6ForwardingRule(rule, upstreamIfindex);
+ }
+ }
+
+ // If we're here to process a NeighborEvent, do so now.
+ if (e == null) return;
+ if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ Ipv6ForwardingRule rule = new Ipv6ForwardingRule(mLastIPv6UpstreamIfindex,
+ mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr,
+ e.macAddr);
+ if (e.isValid()) {
+ addIpv6ForwardingRule(rule);
+ } else {
+ removeIpv6ForwardingRule(rule, true /*removeFromMap*/);
+ }
+ }
+
+ private void handleNeighborEvent(NeighborEvent e) {
+ if (mInterfaceParams != null
+ && mInterfaceParams.index == e.ifindex
+ && mInterfaceParams.hasMacAddress) {
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+ }
+ }
+
private byte getHopLimit(String upstreamIface) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1014,6 +1152,9 @@
}
}
break;
+ case CMD_NEIGHBOR_EVENT:
+ handleNeighborEvent((NeighborEvent) message.obj);
+ break;
default:
return false;
}