1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.ip;
18
19import static android.system.OsConstants.*;
20
21import android.net.NetworkUtils;
22import android.net.util.BlockingSocketReader;
23import android.net.util.ConnectivityPacketSummary;
24import android.os.Handler;
25import android.system.ErrnoException;
26import android.system.Os;
27import android.system.PacketSocketAddress;
28import android.util.Log;
29import android.util.LocalLog;
30
31import libcore.io.IoBridge;
32import libcore.util.HexEncoding;
33
34import java.io.FileDescriptor;
35import java.io.InterruptedIOException;
36import java.io.IOException;
37import java.net.NetworkInterface;
38import java.net.SocketException;
39
40
41/**
42 * Critical connectivity packet tracking daemon.
43 *
44 * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
45 *
46 * This class's constructor, start() and stop() methods must only be called
47 * from the same thread on which the passed in |log| is accessed.
48 *
49 * Log lines include a hexdump of the packet, which can be decoded via:
50 *
51 *     echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
52 *                       | text2pcap - -
53 *                       | tcpdump -n -vv -e -r -
54 *
55 * @hide
56 */
57public class ConnectivityPacketTracker {
58    private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
59    private static final boolean DBG = false;
60    private static final String MARK_START = "--- START ---";
61    private static final String MARK_STOP = "--- STOP ---";
62
63    private final String mTag;
64    private final Handler mHandler;
65    private final LocalLog mLog;
66    private final BlockingSocketReader mPacketListener;
67
68    public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
69        final String ifname;
70        final int ifindex;
71        final byte[] hwaddr;
72        final int mtu;
73
74        try {
75            ifname = netif.getName();
76            ifindex = netif.getIndex();
77            hwaddr = netif.getHardwareAddress();
78            mtu = netif.getMTU();
79        } catch (NullPointerException|SocketException e) {
80            throw new IllegalArgumentException("bad network interface", e);
81        }
82
83        mTag = TAG + "." + ifname;
84        mHandler = new Handler();
85        mLog = log;
86        mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
87    }
88
89    public void start() {
90        mLog.log(MARK_START);
91        mPacketListener.start();
92    }
93
94    public void stop() {
95        mPacketListener.stop();
96        mLog.log(MARK_STOP);
97    }
98
99    private final class PacketListener extends BlockingSocketReader {
100        private final int mIfIndex;
101        private final byte mHwAddr[];
102
103        PacketListener(int ifindex, byte[] hwaddr, int mtu) {
104            super(mtu);
105            mIfIndex = ifindex;
106            mHwAddr = hwaddr;
107        }
108
109        @Override
110        protected FileDescriptor createSocket() {
111            FileDescriptor s = null;
112            try {
113                // TODO: Evaluate switching to SOCK_DGRAM and changing the
114                // BlockingSocketReader's read() to recvfrom(), so that this
115                // might work on non-ethernet-like links (via SLL).
116                s = Os.socket(AF_PACKET, SOCK_RAW, 0);
117                NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
118                Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
119            } catch (ErrnoException | IOException e) {
120                logError("Failed to create packet tracking socket: ", e);
121                closeSocket(s);
122                return null;
123            }
124            return s;
125        }
126
127        @Override
128        protected void handlePacket(byte[] recvbuf, int length) {
129            final String summary = ConnectivityPacketSummary.summarize(
130                    mHwAddr, recvbuf, length);
131            if (summary == null) return;
132
133            if (DBG) Log.d(mTag, summary);
134            addLogEntry(summary +
135                        "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]");
136        }
137
138        @Override
139        protected void logError(String msg, Exception e) {
140            Log.e(mTag, msg, e);
141            addLogEntry(msg + e);
142        }
143
144        private void addLogEntry(String entry) {
145            mHandler.post(() -> mLog.log(entry));
146        }
147    }
148}
149