diff options
Diffstat (limited to 'Tethering/src/android/net/ip/NeighborPacketForwarder.java')
| -rw-r--r-- | Tethering/src/android/net/ip/NeighborPacketForwarder.java | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/Tethering/src/android/net/ip/NeighborPacketForwarder.java new file mode 100644 index 0000000000..73fc833fab --- /dev/null +++ b/Tethering/src/android/net/ip/NeighborPacketForwarder.java @@ -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); + } + } +} |
