tethering: DAD Proxy Daemon
DAD proxy daemon responsible for forwarding NS/NA between tethered iface and upstream iface. Change-Id: I2e58e10e7fa7dba6a6f63ad03b000549f3afc37e
This commit is contained in:
54
Tethering/src/android/net/ip/DadProxy.java
Normal file
54
Tethering/src/android/net/ip/DadProxy.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.ip;
|
||||
|
||||
import android.net.util.InterfaceParams;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Basic Duplicate address detection proxy.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class DadProxy {
|
||||
private static final String TAG = DadProxy.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
public static NeighborPacketForwarder naForwarder;
|
||||
public static NeighborPacketForwarder nsForwarder;
|
||||
|
||||
public DadProxy(Handler h, InterfaceParams tetheredIface) {
|
||||
naForwarder = new NeighborPacketForwarder(h, tetheredIface,
|
||||
NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
|
||||
nsForwarder = new NeighborPacketForwarder(h, tetheredIface,
|
||||
NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
|
||||
}
|
||||
|
||||
/** Stop NS/NA Forwarders. */
|
||||
public void stop() {
|
||||
naForwarder.stop();
|
||||
nsForwarder.stop();
|
||||
}
|
||||
|
||||
/** Set upstream iface on both forwarders. */
|
||||
public void setUpstreamIface(InterfaceParams upstreamIface) {
|
||||
naForwarder.setUpstreamIface(upstreamIface);
|
||||
nsForwarder.setUpstreamIface(upstreamIface);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import android.net.util.InterfaceParams;
|
||||
import android.net.util.InterfaceSet;
|
||||
import android.net.util.PrefixUtils;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
@@ -160,6 +161,15 @@ public class IpServer extends StateMachine {
|
||||
|
||||
/** Capture IpServer dependencies, for injection. */
|
||||
public abstract static class Dependencies {
|
||||
/**
|
||||
* Create a DadProxy instance to be used by IpServer.
|
||||
* To support multiple tethered interfaces concurrently DAD Proxy
|
||||
* needs to be supported per IpServer instead of per upstream.
|
||||
*/
|
||||
public DadProxy getDadProxy(Handler handler, InterfaceParams ifParams) {
|
||||
return new DadProxy(handler, ifParams);
|
||||
}
|
||||
|
||||
/** Create an IpNeighborMonitor to be used by this IpServer */
|
||||
public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
|
||||
IpNeighborMonitor.NeighborEventConsumer consumer) {
|
||||
@@ -256,6 +266,7 @@ public class IpServer extends StateMachine {
|
||||
// Advertisements (otherwise, we do not add them to mLinkProperties at all).
|
||||
private LinkProperties mLastIPv6LinkProperties;
|
||||
private RouterAdvertisementDaemon mRaDaemon;
|
||||
private DadProxy mDadProxy;
|
||||
|
||||
// To be accessed only on the handler thread
|
||||
private int mDhcpServerStartIndex = 0;
|
||||
@@ -674,6 +685,13 @@ public class IpServer extends StateMachine {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: use ShimUtils instead of explicitly checking the version here.
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R || "S".equals(Build.VERSION.CODENAME)
|
||||
|| "T".equals(Build.VERSION.CODENAME)) {
|
||||
// DAD Proxy starts forwarding packets after IPv6 upstream is present.
|
||||
mDadProxy = mDeps.getDadProxy(getHandler(), mInterfaceParams);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -685,6 +703,11 @@ public class IpServer extends StateMachine {
|
||||
mRaDaemon.stop();
|
||||
mRaDaemon = null;
|
||||
}
|
||||
|
||||
if (mDadProxy != null) {
|
||||
mDadProxy.stop();
|
||||
mDadProxy = null;
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
|
||||
@@ -702,11 +725,16 @@ public class IpServer extends StateMachine {
|
||||
}
|
||||
|
||||
RaParams params = null;
|
||||
int upstreamIfindex = 0;
|
||||
String upstreamIface = null;
|
||||
InterfaceParams upstreamIfaceParams = null;
|
||||
int upstreamIfIndex = 0;
|
||||
|
||||
if (v6only != null) {
|
||||
final String upstreamIface = v6only.getInterfaceName();
|
||||
|
||||
upstreamIface = v6only.getInterfaceName();
|
||||
upstreamIfaceParams = mDeps.getInterfaceParams(upstreamIface);
|
||||
if (upstreamIfaceParams != null) {
|
||||
upstreamIfIndex = upstreamIfaceParams.index;
|
||||
}
|
||||
params = new RaParams();
|
||||
params.mtu = v6only.getMtu();
|
||||
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
|
||||
@@ -726,15 +754,13 @@ public class IpServer extends StateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
upstreamIfindex = mDeps.getIfindex(upstreamIface);
|
||||
|
||||
// Add upstream index to name mapping for the tether stats usage in the coordinator.
|
||||
// Although this mapping could be added by both class Tethering and IpServer, adding
|
||||
// mapping from IpServer guarantees that the mapping is added before the adding
|
||||
// forwarding rules. That is because there are different state machines in both
|
||||
// classes. It is hard to guarantee the link property update order between multiple
|
||||
// state machines.
|
||||
mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface);
|
||||
mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface);
|
||||
}
|
||||
|
||||
// If v6only is null, we pass in null to setRaParams(), which handles
|
||||
@@ -743,8 +769,11 @@ public class IpServer extends StateMachine {
|
||||
setRaParams(params);
|
||||
mLastIPv6LinkProperties = v6only;
|
||||
|
||||
updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null);
|
||||
mLastIPv6UpstreamIfindex = upstreamIfindex;
|
||||
updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null);
|
||||
mLastIPv6UpstreamIfindex = upstreamIfIndex;
|
||||
if (mDadProxy != null) {
|
||||
mDadProxy.setUpstreamIface(upstreamIfaceParams);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) {
|
||||
|
||||
180
Tethering/src/android/net/ip/NeighborPacketForwarder.java
Normal file
180
Tethering/src/android/net/ip/NeighborPacketForwarder.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.ip;
|
||||
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
import static android.system.OsConstants.AF_PACKET;
|
||||
import static android.system.OsConstants.ETH_P_IPV6;
|
||||
import static android.system.OsConstants.IPPROTO_RAW;
|
||||
import static android.system.OsConstants.SOCK_DGRAM;
|
||||
import static android.system.OsConstants.SOCK_NONBLOCK;
|
||||
import static android.system.OsConstants.SOCK_RAW;
|
||||
|
||||
import android.net.util.InterfaceParams;
|
||||
import android.net.util.PacketReader;
|
||||
import android.net.util.SocketUtils;
|
||||
import android.net.util.TetheringUtils;
|
||||
import android.os.Handler;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Basic IPv6 Neighbor Advertisement Forwarder.
|
||||
*
|
||||
* Forward NA packets from upstream iface to tethered iface
|
||||
* and NS packets from tethered iface to upstream iface.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NeighborPacketForwarder extends PacketReader {
|
||||
private final String mTag;
|
||||
|
||||
private FileDescriptor mFd;
|
||||
|
||||
// TODO: get these from NetworkStackConstants.
|
||||
private static final int IPV6_ADDR_LEN = 16;
|
||||
private static final int IPV6_DST_ADDR_OFFSET = 24;
|
||||
private static final int IPV6_HEADER_LEN = 40;
|
||||
private static final int ETH_HEADER_LEN = 14;
|
||||
|
||||
private InterfaceParams mListenIfaceParams, mSendIfaceParams;
|
||||
|
||||
private final int mType;
|
||||
public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
|
||||
public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
|
||||
|
||||
public NeighborPacketForwarder(Handler h, InterfaceParams tetheredInterface, int type) {
|
||||
super(h);
|
||||
mTag = NeighborPacketForwarder.class.getSimpleName() + "-"
|
||||
+ tetheredInterface.name + "-" + type;
|
||||
mType = type;
|
||||
|
||||
if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
|
||||
mSendIfaceParams = tetheredInterface;
|
||||
} else {
|
||||
mListenIfaceParams = tetheredInterface;
|
||||
}
|
||||
}
|
||||
|
||||
/** Set new upstream iface and start/stop based on new params. */
|
||||
public void setUpstreamIface(InterfaceParams upstreamParams) {
|
||||
final InterfaceParams oldUpstreamParams;
|
||||
|
||||
if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
|
||||
oldUpstreamParams = mListenIfaceParams;
|
||||
mListenIfaceParams = upstreamParams;
|
||||
} else {
|
||||
oldUpstreamParams = mSendIfaceParams;
|
||||
mSendIfaceParams = upstreamParams;
|
||||
}
|
||||
|
||||
if (oldUpstreamParams == null && upstreamParams != null) {
|
||||
start();
|
||||
} else if (oldUpstreamParams != null && upstreamParams == null) {
|
||||
stop();
|
||||
} else if (oldUpstreamParams != null && upstreamParams != null
|
||||
&& oldUpstreamParams.index != upstreamParams.index) {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move NetworkStackUtils.closeSocketQuietly to
|
||||
// frameworks/libs/net/common/device/com/android/net/module/util/[someclass].
|
||||
private void closeSocketQuietly(FileDescriptor fd) {
|
||||
try {
|
||||
SocketUtils.closeSocket(fd);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileDescriptor createFd() {
|
||||
try {
|
||||
// ICMPv6 packets from modem do not have eth header, so RAW socket cannot be used.
|
||||
// To keep uniformity in both directions PACKET socket can be used.
|
||||
mFd = Os.socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
|
||||
|
||||
// TODO: convert setup*Socket to setupICMPv6BpfFilter with filter type?
|
||||
if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
|
||||
TetheringUtils.setupNaSocket(mFd);
|
||||
} else if (mType == ICMPV6_NEIGHBOR_SOLICITATION) {
|
||||
TetheringUtils.setupNsSocket(mFd);
|
||||
}
|
||||
|
||||
SocketAddress bindAddress = SocketUtils.makePacketSocketAddress(
|
||||
ETH_P_IPV6, mListenIfaceParams.index);
|
||||
Os.bind(mFd, bindAddress);
|
||||
} catch (ErrnoException | SocketException e) {
|
||||
Log.wtf(mTag, "Failed to create socket", e);
|
||||
closeSocketQuietly(mFd);
|
||||
return null;
|
||||
}
|
||||
|
||||
return mFd;
|
||||
}
|
||||
|
||||
private Inet6Address getIpv6DestinationAddress(byte[] recvbuf) {
|
||||
Inet6Address dstAddr;
|
||||
try {
|
||||
dstAddr = (Inet6Address) Inet6Address.getByAddress(Arrays.copyOfRange(recvbuf,
|
||||
IPV6_DST_ADDR_OFFSET, IPV6_DST_ADDR_OFFSET + IPV6_ADDR_LEN));
|
||||
} catch (UnknownHostException | ClassCastException impossible) {
|
||||
throw new AssertionError("16-byte array not valid IPv6 address?");
|
||||
}
|
||||
return dstAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlePacket(byte[] recvbuf, int length) {
|
||||
if (mSendIfaceParams == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The BPF filter should already have checked the length of the packet, but...
|
||||
if (length < IPV6_HEADER_LEN) {
|
||||
return;
|
||||
}
|
||||
Inet6Address destv6 = getIpv6DestinationAddress(recvbuf);
|
||||
if (!destv6.isMulticastAddress()) {
|
||||
return;
|
||||
}
|
||||
InetSocketAddress dest = new InetSocketAddress(destv6, 0);
|
||||
|
||||
FileDescriptor fd = null;
|
||||
try {
|
||||
fd = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW);
|
||||
SocketUtils.bindSocketToInterface(fd, mSendIfaceParams.name);
|
||||
|
||||
int ret = Os.sendto(fd, recvbuf, 0, length, 0, dest);
|
||||
} catch (ErrnoException | SocketException e) {
|
||||
Log.e(mTag, "handlePacket error: " + e);
|
||||
} finally {
|
||||
closeSocketQuietly(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package android.net.ip;
|
||||
|
||||
import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
|
||||
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
|
||||
import static android.net.util.TetheringUtils.getAllNodesForScopeId;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
import static android.system.OsConstants.IPPROTO_ICMPV6;
|
||||
import static android.system.OsConstants.SOCK_RAW;
|
||||
@@ -44,7 +45,6 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
@@ -92,10 +92,6 @@ public class RouterAdvertisementDaemon {
|
||||
|
||||
private static final int DAY_IN_SECONDS = 86_400;
|
||||
|
||||
private static final byte[] ALL_NODES = new byte[] {
|
||||
(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||
};
|
||||
|
||||
private final InterfaceParams mInterface;
|
||||
private final InetSocketAddress mAllNodes;
|
||||
|
||||
@@ -240,7 +236,6 @@ public class RouterAdvertisementDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public RouterAdvertisementDaemon(InterfaceParams ifParams) {
|
||||
mInterface = ifParams;
|
||||
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
|
||||
@@ -363,15 +358,6 @@ public class RouterAdvertisementDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
private static Inet6Address getAllNodesForScopeId(int scopeId) {
|
||||
try {
|
||||
return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
|
||||
} catch (UnknownHostException uhe) {
|
||||
Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte asByte(int value) {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,15 @@ package android.net.util;
|
||||
|
||||
import android.net.TetherStatsParcel;
|
||||
import android.net.TetheringRequestParcel;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -30,6 +34,24 @@ import java.util.Objects;
|
||||
* {@hide}
|
||||
*/
|
||||
public class TetheringUtils {
|
||||
public static final byte[] ALL_NODES = new byte[] {
|
||||
(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures a socket for receiving and sending ICMPv6 neighbor advertisments.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
*/
|
||||
public static native void setupNaSocket(FileDescriptor fd)
|
||||
throws SocketException;
|
||||
|
||||
/**
|
||||
* Configures a socket for receiving and sending ICMPv6 neighbor solicitations.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
*/
|
||||
public static native void setupNsSocket(FileDescriptor fd)
|
||||
throws SocketException;
|
||||
|
||||
/**
|
||||
* The object which records offload Tx/Rx forwarded bytes/packets.
|
||||
* TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with
|
||||
@@ -129,4 +151,15 @@ public class TetheringUtils {
|
||||
&& request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
|
||||
&& request.showProvisioningUi == otherRequest.showProvisioningUi;
|
||||
}
|
||||
|
||||
/** Get inet6 address for all nodes given scope ID. */
|
||||
public static Inet6Address getAllNodesForScopeId(int scopeId) {
|
||||
try {
|
||||
return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
|
||||
} catch (UnknownHostException uhe) {
|
||||
Log.wtf("TetheringUtils", "Failed to construct Inet6Address from "
|
||||
+ Arrays.toString(ALL_NODES) + " and scopedId " + scopeId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user