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
This commit is contained in:
@@ -40,12 +40,14 @@ import android.net.dhcp.DhcpServingParamsParcel;
|
||||
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.MessageUtils;
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
|
||||
/** 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 @@ public class IpServer extends StateMachine {
|
||||
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 class IpServer extends StateMachine {
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
@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 @@ public class IpServer extends StateMachine {
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
}
|
||||
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class IpServer extends StateMachine {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CMD_NEIGHBOR_EVENT:
|
||||
handleNeighborEvent((NeighborEvent) message.obj);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user