Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 692b986c authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Automerger Merge Worker
Browse files

Merge "tethering: DAD Proxy Daemon" am: 199613f2

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1258645

Change-Id: Ib3bbe465b698feace1a3ec1f6588cfa7bff0c9fd
parents 3d0d61de 199613f2
Loading
Loading
Loading
Loading
+51 −1
Original line number Diff line number Diff line
@@ -17,18 +17,63 @@
#include <errno.h>
#include <error.h>
#include <jni.h>
#include <linux/filter.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/JNIHelpCompat.h>
#include <nativehelper/ScopedUtfChars.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <sys/socket.h>
#include <stdio.h>

#define LOG_TAG "TetheringUtils"
#include <android/log.h>

namespace android {

static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt);
static const uint32_t kIPv6PayloadStart = sizeof(ip6_hdr);
static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);

static void android_net_util_setupIcmpFilter(JNIEnv *env, jobject javaFd, uint32_t type) {
    sock_filter filter_code[] = {
        // Check header is ICMPv6.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeaderOffset),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),

        // Check ICMPv6 type.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    type, 0, 1),

        // Accept or reject.
        BPF_STMT(BPF_RET | BPF_K,              0xffff),
        BPF_STMT(BPF_RET | BPF_K,              0)
    };

    const sock_fprog filter = {
        sizeof(filter_code) / sizeof(filter_code[0]),
        filter_code,
    };

    int fd = jniGetFDFromFileDescriptor(env, javaFd);
    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
        jniThrowExceptionFmt(env, "java/net/SocketException",
                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
    }
}

static void android_net_util_setupNaSocket(JNIEnv *env, jobject clazz, jobject javaFd)
{
    android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT);
}

static void android_net_util_setupNsSocket(JNIEnv *env, jobject clazz, jobject javaFd)
{
    android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT);
}

static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
        jint ifIndex)
{
@@ -125,7 +170,12 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket },
    { "setupNaSocket", "(Ljava/io/FileDescriptor;)V",
        (void*) android_net_util_setupNaSocket },
    { "setupNsSocket", "(Ljava/io/FileDescriptor;)V",
        (void*) android_net_util_setupNsSocket },
    { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V",
        (void*) android_net_util_setupRaSocket },
};

int register_android_net_util_TetheringUtils(JNIEnv* env) {
+54 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+37 −8
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+1 −15
Original line number Diff line number Diff line
@@ -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;
    }
Loading