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

Commit 62c1d307 authored by Erik Kline's avatar Erik Kline Committed by android-build-merger
Browse files

Merge "Have IpManager track L2-L4 signaling traffic required for IP connectivity." am: 80ff5ecd

am: cdcc931d

Change-Id: Ib46bdab897cef221f4c22326e562130822d42298
parents c066d73c cdcc931d
Loading
Loading
Loading
Loading
+11 −0
Original line number Original line Diff line number Diff line
@@ -51,6 +51,17 @@ public class NetworkUtils {
     */
     */
    public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
    public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;


    /**
     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
     *
     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
     *
     * @param fd the socket's {@link FileDescriptor}.
     * @param packetType the hardware address type, one of ARPHRD_*.
     */
    public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
            throws SocketException;

    /**
    /**
     * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
     * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
     * @param fd the socket's {@link FileDescriptor}.
     * @param fd the socket's {@link FileDescriptor}.
+92 −15
Original line number Original line Diff line number Diff line
@@ -44,28 +44,33 @@ int ifc_disable(const char *ifname);


namespace android {
namespace android {


static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
static const uint32_t kEtherHeaderLen = sizeof(ether_header);
static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
static const uint16_t kDhcpClientPort = 68;
static const uint16_t kDhcpClientPort = 68;


static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
{
    uint32_t ip_offset = sizeof(ether_header);
    uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol);
    uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
    uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest);
    struct sock_filter filter_code[] = {
    struct sock_filter filter_code[] = {
        // Check the protocol is UDP.
        // Check the protocol is UDP.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  proto_offset),
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv4Protocol),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_UDP, 0, 6),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_UDP, 0, 6),


        // Check this is not a fragment.
        // Check this is not a fragment.
        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, flags_offset),
        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   0x1fff, 4, 0),
        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 4, 0),


        // Get the IP header length.
        // Get the IP header length.
        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, ip_offset),
        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),


        // Check the destination port.
        // Check the destination port.
        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, dport_indirect_offset),
        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),
        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),


        // Accept or reject.
        // Accept or reject.
@@ -93,17 +98,13 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
        return;
        return;
    }
    }


    uint32_t ipv6_offset = sizeof(ether_header);
    uint32_t ipv6_next_header_offset = ipv6_offset + offsetof(ip6_hdr, ip6_nxt);
    uint32_t icmp6_offset = ipv6_offset + sizeof(ip6_hdr);
    uint32_t icmp6_type_offset = icmp6_offset + offsetof(icmp6_hdr, icmp6_type);
    struct sock_filter filter_code[] = {
    struct sock_filter filter_code[] = {
        // Check IPv6 Next Header is ICMPv6.
        // Check IPv6 Next Header is ICMPv6.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  ipv6_next_header_offset),
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),


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


        // Accept or reject.
        // Accept or reject.
@@ -122,6 +123,81 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
    }
    }
}
}


// TODO: Move all this filter code into libnetutils.
static void android_net_utils_attachControlPacketFilter(
        JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
    if (hardwareAddressType != ARPHRD_ETHER) {
        jniThrowExceptionFmt(env, "java/net/SocketException",
                "attachControlPacketFilter only supports ARPHRD_ETHER");
        return;
    }

    // Capture all:
    //     - ARPs
    //     - DHCPv4 packets
    //     - Router Advertisements & Solicitations
    //     - Neighbor Advertisements & Solicitations
    //
    // tcpdump:
    //     arp or
    //     '(ip and udp port 68)' or
    //     '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
    struct sock_filter filter_code[] = {
        // Load the link layer next payload field.
        BPF_STMT(BPF_LD  | BPF_H   | BPF_ABS,  kEtherTypeOffset),

        // Accept all ARP.
        // TODO: Figure out how to better filter ARPs on noisy networks.
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),

        // If IPv4:
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),

        // Check the protocol is UDP.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv4Protocol),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_UDP, 0, 14),

        // Check this is not a fragment.
        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 12, 0),

        // Get the IP header length.
        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),

        // Check the source port.
        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPSrcPortIndirectOffset),
        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 8, 0),

        // Check the destination port.
        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 6, 7),

        // IPv6 ...
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
        // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 4),
        // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
        BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K,    ND_ROUTER_SOLICIT, 0, 2),
        BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K,    ND_NEIGHBOR_ADVERT, 1, 0),

        // Accept or reject.
        BPF_STMT(BPF_RET | BPF_K,              0xffff),
        BPF_STMT(BPF_RET | BPF_K,              0)
    };
    struct 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_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
        jint ifIndex)
        jint ifIndex)
{
{
@@ -263,6 +339,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
    { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
    { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
    { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
    { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
    { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
    { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
    { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
};
};


+3 −1
Original line number Original line Diff line number Diff line
@@ -26,8 +26,10 @@ import java.util.List;
 * Defines basic data and operations needed to build and use packets for the
 * Defines basic data and operations needed to build and use packets for the
 * DHCP protocol.  Subclasses create the specific packets used at each
 * DHCP protocol.  Subclasses create the specific packets used at each
 * stage of the negotiation.
 * stage of the negotiation.
 *
 * @hide
 */
 */
abstract class DhcpPacket {
public abstract class DhcpPacket {
    protected static final String TAG = "DhcpPacket";
    protected static final String TAG = "DhcpPacket";


    // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
    // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
+148 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2016 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.*;

import android.net.NetworkUtils;
import android.net.util.BlockingSocketReader;
import android.net.util.ConnectivityPacketSummary;
import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
import android.system.PacketSocketAddress;
import android.util.Log;
import android.util.LocalLog;

import libcore.io.IoBridge;
import libcore.util.HexEncoding;

import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.net.NetworkInterface;
import java.net.SocketException;


/**
 * Critical connectivity packet tracking daemon.
 *
 * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
 *
 * This class's constructor, start() and stop() methods must only be called
 * from the same thread on which the passed in |log| is accessed.
 *
 * Log lines include a hexdump of the packet, which can be decoded via:
 *
 *     echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
 *                       | text2pcap - -
 *                       | tcpdump -n -vv -e -r -
 *
 * @hide
 */
public class ConnectivityPacketTracker {
    private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
    private static final boolean DBG = false;
    private static final String MARK_START = "--- START ---";
    private static final String MARK_STOP = "--- STOP ---";

    private final String mTag;
    private final Handler mHandler;
    private final LocalLog mLog;
    private final BlockingSocketReader mPacketListener;

    public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
        final String ifname;
        final int ifindex;
        final byte[] hwaddr;
        final int mtu;

        try {
            ifname = netif.getName();
            ifindex = netif.getIndex();
            hwaddr = netif.getHardwareAddress();
            mtu = netif.getMTU();
        } catch (NullPointerException|SocketException e) {
            throw new IllegalArgumentException("bad network interface", e);
        }

        mTag = TAG + "." + ifname;
        mHandler = new Handler();
        mLog = log;
        mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
    }

    public void start() {
        mLog.log(MARK_START);
        mPacketListener.start();
    }

    public void stop() {
        mPacketListener.stop();
        mLog.log(MARK_STOP);
    }

    private final class PacketListener extends BlockingSocketReader {
        private final int mIfIndex;
        private final byte mHwAddr[];

        PacketListener(int ifindex, byte[] hwaddr, int mtu) {
            super(mtu);
            mIfIndex = ifindex;
            mHwAddr = hwaddr;
        }

        @Override
        protected FileDescriptor createSocket() {
            FileDescriptor s = null;
            try {
                // TODO: Evaluate switching to SOCK_DGRAM and changing the
                // BlockingSocketReader's read() to recvfrom(), so that this
                // might work on non-ethernet-like links (via SLL).
                s = Os.socket(AF_PACKET, SOCK_RAW, 0);
                NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
                Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
            } catch (ErrnoException | IOException e) {
                logError("Failed to create packet tracking socket: ", e);
                closeSocket(s);
                return null;
            }
            return s;
        }

        @Override
        protected void handlePacket(byte[] recvbuf, int length) {
            final String summary = ConnectivityPacketSummary.summarize(
                    mHwAddr, recvbuf, length);
            if (summary == null) return;

            if (DBG) Log.d(mTag, summary);
            addLogEntry(summary +
                        "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]");
        }

        @Override
        protected void logError(String msg, Exception e) {
            Log.e(mTag, msg, e);
            addLogEntry(msg + e);
        }

        private void addLogEntry(String entry) {
            mHandler.post(() -> mLog.log(entry));
        }
    }
}
+27 −1
Original line number Original line Diff line number Diff line
@@ -374,6 +374,7 @@ public class IpManager extends StateMachine {
    private static final int EVENT_DHCPACTION_TIMEOUT = 10;
    private static final int EVENT_DHCPACTION_TIMEOUT = 10;


    private static final int MAX_LOG_RECORDS = 500;
    private static final int MAX_LOG_RECORDS = 500;
    private static final int MAX_PACKET_RECORDS = 100;


    private static final boolean NO_CALLBACKS = false;
    private static final boolean NO_CALLBACKS = false;
    private static final boolean SEND_CALLBACKS = true;
    private static final boolean SEND_CALLBACKS = true;
@@ -399,6 +400,7 @@ public class IpManager extends StateMachine {
    private final WakeupMessage mDhcpActionTimeoutAlarm;
    private final WakeupMessage mDhcpActionTimeoutAlarm;
    private final AvoidBadWifiTracker mAvoidBadWifiTracker;
    private final AvoidBadWifiTracker mAvoidBadWifiTracker;
    private final LocalLog mLocalLog;
    private final LocalLog mLocalLog;
    private final LocalLog mConnectivityPacketLog;
    private final MessageHandlingLogger mMsgStateLogger;
    private final MessageHandlingLogger mMsgStateLogger;
    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();


@@ -439,6 +441,7 @@ public class IpManager extends StateMachine {
        mNwService = nwService;
        mNwService = nwService;


        mLocalLog = new LocalLog(MAX_LOG_RECORDS);
        mLocalLog = new LocalLog(MAX_LOG_RECORDS);
        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
        mMsgStateLogger = new MessageHandlingLogger();
        mMsgStateLogger = new MessageHandlingLogger();


        mNetlinkTracker = new NetlinkTracker(
        mNetlinkTracker = new NetlinkTracker(
@@ -609,7 +612,7 @@ public class IpManager extends StateMachine {
        }
        }


        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        pw.println("APF dump:");
        pw.println(mTag + " APF dump:");
        pw.increaseIndent();
        pw.increaseIndent();
        // Thread-unsafe access to mApfFilter but just used for debugging.
        // Thread-unsafe access to mApfFilter but just used for debugging.
        ApfFilter apfFilter = mApfFilter;
        ApfFilter apfFilter = mApfFilter;
@@ -625,6 +628,12 @@ public class IpManager extends StateMachine {
        pw.increaseIndent();
        pw.increaseIndent();
        mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
        mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
        pw.decreaseIndent();
        pw.decreaseIndent();

        pw.println();
        pw.println(mTag + " connectivity packet log:");
        pw.increaseIndent();
        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
        pw.decreaseIndent();
    }
    }




@@ -1220,6 +1229,7 @@ public class IpManager extends StateMachine {
    }
    }


    class RunningState extends State {
    class RunningState extends State {
        private ConnectivityPacketTracker mPacketTracker;
        private boolean mDhcpActionInFlight;
        private boolean mDhcpActionInFlight;


        @Override
        @Override
@@ -1232,6 +1242,9 @@ public class IpManager extends StateMachine {
                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
            }
            }


            mPacketTracker = createPacketTracker();
            if (mPacketTracker != null) mPacketTracker.start();

            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
                transitionTo(mStoppingState);
                transitionTo(mStoppingState);
@@ -1266,6 +1279,11 @@ public class IpManager extends StateMachine {
                mDhcpClient.doQuit();
                mDhcpClient.doQuit();
            }
            }


            if (mPacketTracker != null) {
                mPacketTracker.stop();
                mPacketTracker = null;
            }

            if (mApfFilter != null) {
            if (mApfFilter != null) {
                mApfFilter.shutdown();
                mApfFilter.shutdown();
                mApfFilter = null;
                mApfFilter = null;
@@ -1274,6 +1292,14 @@ public class IpManager extends StateMachine {
            resetLinkProperties();
            resetLinkProperties();
        }
        }


        private ConnectivityPacketTracker createPacketTracker() {
            try {
                return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
            } catch (IllegalArgumentException e) {
                return null;
            }
        }

        private void ensureDhcpAction() {
        private void ensureDhcpAction() {
            if (!mDhcpActionInFlight) {
            if (!mDhcpActionInFlight) {
                mCallback.onPreDhcpAction();
                mCallback.onPreDhcpAction();
Loading