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.util;
18
19import android.net.dhcp.DhcpPacket;
20
21import java.net.InetAddress;
22import java.net.UnknownHostException;
23import java.nio.ByteBuffer;
24import java.nio.ByteOrder;
25import java.util.Arrays;
26import java.util.StringJoiner;
27
28import static android.system.OsConstants.*;
29import static android.net.util.NetworkConstants.*;
30
31
32/**
33 * Critical connectivity packet summarizing class.
34 *
35 * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
36 *
37 * @hide
38 */
39public class ConnectivityPacketSummary {
40    private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
41
42    private final byte[] mHwAddr;
43    private final byte[] mBytes;
44    private final int mLength;
45    private final ByteBuffer mPacket;
46    private final String mSummary;
47
48    public static String summarize(byte[] hwaddr, byte[] buffer) {
49        return summarize(hwaddr, buffer, buffer.length);
50    }
51
52    // Methods called herein perform some but by no means all error checking.
53    // They may throw runtime exceptions on malformed packets.
54    public static String summarize(byte[] hwaddr, byte[] buffer, int length) {
55        if ((hwaddr == null) || (hwaddr.length != ETHER_ADDR_LEN)) return null;
56        if (buffer == null) return null;
57        length = Math.min(length, buffer.length);
58        return (new ConnectivityPacketSummary(hwaddr, buffer, length)).toString();
59    }
60
61    private ConnectivityPacketSummary(byte[] hwaddr, byte[] buffer, int length) {
62        mHwAddr = hwaddr;
63        mBytes = buffer;
64        mLength = Math.min(length, mBytes.length);
65        mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
66        mPacket.order(ByteOrder.BIG_ENDIAN);
67
68        final StringJoiner sj = new StringJoiner(" ");
69        // TODO: support other link-layers, or even no link-layer header.
70        parseEther(sj);
71        mSummary = sj.toString();
72    }
73
74    public String toString() {
75        return mSummary;
76    }
77
78    private void parseEther(StringJoiner sj) {
79        if (mPacket.remaining() < ETHER_HEADER_LEN) {
80            sj.add("runt:").add(asString(mPacket.remaining()));
81            return;
82        }
83
84        mPacket.position(ETHER_SRC_ADDR_OFFSET);
85        final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
86        sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX");
87        sj.add(getMacAddressString(srcMac));
88
89        mPacket.position(ETHER_DST_ADDR_OFFSET);
90        final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
91        sj.add(">").add(getMacAddressString(dstMac));
92
93        mPacket.position(ETHER_TYPE_OFFSET);
94        final int etherType = asUint(mPacket.getShort());
95        switch (etherType) {
96            case ETHER_TYPE_ARP:
97                sj.add("arp");
98                parseARP(sj);
99                break;
100            case ETHER_TYPE_IPV4:
101                sj.add("ipv4");
102                parseIPv4(sj);
103                break;
104            case ETHER_TYPE_IPV6:
105                sj.add("ipv6");
106                parseIPv6(sj);
107                break;
108            default:
109                // Unknown ether type.
110                sj.add("ethtype").add(asString(etherType));
111                break;
112        }
113    }
114
115    private void parseARP(StringJoiner sj) {
116        if (mPacket.remaining() < ARP_PAYLOAD_LEN) {
117            sj.add("runt:").add(asString(mPacket.remaining()));
118            return;
119        }
120
121        if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER ||
122            asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 ||
123            asUint(mPacket.get()) != ETHER_ADDR_LEN ||
124            asUint(mPacket.get()) != IPV4_ADDR_LEN) {
125            sj.add("unexpected header");
126            return;
127        }
128
129        final int opCode = asUint(mPacket.getShort());
130
131        final String senderHwAddr = getMacAddressString(mPacket);
132        final String senderIPv4 = getIPv4AddressString(mPacket);
133        getMacAddressString(mPacket);  // target hardware address, unused
134        final String targetIPv4 = getIPv4AddressString(mPacket);
135
136        if (opCode == ARP_REQUEST) {
137            sj.add("who-has").add(targetIPv4);
138        } else if (opCode == ARP_REPLY) {
139            sj.add("reply").add(senderIPv4).add(senderHwAddr);
140        } else {
141            sj.add("unknown opcode").add(asString(opCode));
142        }
143    }
144
145    private void parseIPv4(StringJoiner sj) {
146        if (!mPacket.hasRemaining()) {
147            sj.add("runt");
148            return;
149        }
150
151        final int startOfIpLayer = mPacket.position();
152        final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4;
153        if (mPacket.remaining() < ipv4HeaderLength ||
154            mPacket.remaining() < IPV4_HEADER_MIN_LEN) {
155            sj.add("runt:").add(asString(mPacket.remaining()));
156            return;
157        }
158        final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength;
159
160        mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET);
161        final int flagsAndFragment = asUint(mPacket.getShort());
162        final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0;
163
164        mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET);
165        final int protocol = asUint(mPacket.get());
166
167        mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET);
168        final String srcAddr = getIPv4AddressString(mPacket);
169
170        mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET);
171        final String dstAddr = getIPv4AddressString(mPacket);
172
173        sj.add(srcAddr).add(">").add(dstAddr);
174
175        mPacket.position(startOfTransportLayer);
176        if (protocol == IPPROTO_UDP) {
177            sj.add("udp");
178            if (isFragment) sj.add("fragment");
179            else parseUDP(sj);
180        } else {
181            sj.add("proto").add(asString(protocol));
182            if (isFragment) sj.add("fragment");
183        }
184    }
185
186    private void parseIPv6(StringJoiner sj) {
187        if (mPacket.remaining() < IPV6_HEADER_LEN) {
188            sj.add("runt:").add(asString(mPacket.remaining()));
189            return;
190        }
191
192        final int startOfIpLayer = mPacket.position();
193
194        mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET);
195        final int protocol = asUint(mPacket.get());
196
197        mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET);
198        final String srcAddr = getIPv6AddressString(mPacket);
199        final String dstAddr = getIPv6AddressString(mPacket);
200
201        sj.add(srcAddr).add(">").add(dstAddr);
202
203        mPacket.position(startOfIpLayer + IPV6_HEADER_LEN);
204        if (protocol == IPPROTO_ICMPV6) {
205            sj.add("icmp6");
206            parseICMPv6(sj);
207        } else {
208            sj.add("proto").add(asString(protocol));
209        }
210    }
211
212    private void parseICMPv6(StringJoiner sj) {
213        if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) {
214            sj.add("runt:").add(asString(mPacket.remaining()));
215            return;
216        }
217
218        final int icmp6Type = asUint(mPacket.get());
219        final int icmp6Code = asUint(mPacket.get());
220        mPacket.getShort();  // checksum, unused
221
222        switch (icmp6Type) {
223            case ICMPV6_ROUTER_SOLICITATION:
224                sj.add("rs");
225                parseICMPv6RouterSolicitation(sj);
226                break;
227            case ICMPV6_ROUTER_ADVERTISEMENT:
228                sj.add("ra");
229                parseICMPv6RouterAdvertisement(sj);
230                break;
231            case ICMPV6_NEIGHBOR_SOLICITATION:
232                sj.add("ns");
233                parseICMPv6NeighborMessage(sj);
234                break;
235            case ICMPV6_NEIGHBOR_ADVERTISEMENT:
236                sj.add("na");
237                parseICMPv6NeighborMessage(sj);
238                break;
239            default:
240                sj.add("type").add(asString(icmp6Type));
241                sj.add("code").add(asString(icmp6Code));
242                break;
243        }
244    }
245
246    private void parseICMPv6RouterSolicitation(StringJoiner sj) {
247        final int RESERVED = 4;
248        if (mPacket.remaining() < RESERVED) {
249            sj.add("runt:").add(asString(mPacket.remaining()));
250            return;
251        }
252
253        mPacket.position(mPacket.position() + RESERVED);
254        parseICMPv6NeighborDiscoveryOptions(sj);
255    }
256
257    private void parseICMPv6RouterAdvertisement(StringJoiner sj) {
258        final int FLAGS_AND_TIMERS = 3 * 4;
259        if (mPacket.remaining() < FLAGS_AND_TIMERS) {
260            sj.add("runt:").add(asString(mPacket.remaining()));
261            return;
262        }
263
264        mPacket.position(mPacket.position() + FLAGS_AND_TIMERS);
265        parseICMPv6NeighborDiscoveryOptions(sj);
266    }
267
268    private void parseICMPv6NeighborMessage(StringJoiner sj) {
269        final int RESERVED = 4;
270        final int minReq = RESERVED + IPV6_ADDR_LEN;
271        if (mPacket.remaining() < minReq) {
272            sj.add("runt:").add(asString(mPacket.remaining()));
273            return;
274        }
275
276        mPacket.position(mPacket.position() + RESERVED);
277        sj.add(getIPv6AddressString(mPacket));
278        parseICMPv6NeighborDiscoveryOptions(sj);
279    }
280
281    private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) {
282        // All ND options are TLV, where T is one byte and L is one byte equal
283        // to the length of T + L + V in units of 8 octets.
284        while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) {
285            final int ndType = asUint(mPacket.get());
286            final int ndLength = asUint(mPacket.get());
287            final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
288            if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
289                sj.add("<malformed>");
290                break;
291            }
292            final int position = mPacket.position();
293
294            switch (ndType) {
295                    case ICMPV6_ND_OPTION_SLLA:
296                        sj.add("slla");
297                        sj.add(getMacAddressString(mPacket));
298                        break;
299                    case ICMPV6_ND_OPTION_TLLA:
300                        sj.add("tlla");
301                        sj.add(getMacAddressString(mPacket));
302                        break;
303                    case ICMPV6_ND_OPTION_MTU:
304                        sj.add("mtu");
305                        final short reserved = mPacket.getShort();
306                        sj.add(asString(mPacket.getInt()));
307                        break;
308                    default:
309                        // Skip.
310                        break;
311            }
312
313            mPacket.position(position + ndBytes);
314        }
315    }
316
317    private void parseUDP(StringJoiner sj) {
318        if (mPacket.remaining() < UDP_HEADER_LEN) {
319            sj.add("runt:").add(asString(mPacket.remaining()));
320            return;
321        }
322
323        final int previous = mPacket.position();
324        final int srcPort = asUint(mPacket.getShort());
325        final int dstPort = asUint(mPacket.getShort());
326        sj.add(asString(srcPort)).add(">").add(asString(dstPort));
327
328        mPacket.position(previous + UDP_HEADER_LEN);
329        if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) {
330            sj.add("dhcp4");
331            parseDHCPv4(sj);
332        }
333    }
334
335    private void parseDHCPv4(StringJoiner sj) {
336        final DhcpPacket dhcpPacket;
337        try {
338            dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2);
339            sj.add(dhcpPacket.toString());
340        } catch (DhcpPacket.ParseException e) {
341            sj.add("parse error: " + e);
342        }
343    }
344
345    private static String getIPv4AddressString(ByteBuffer ipv4) {
346        return getIpAddressString(ipv4, IPV4_ADDR_LEN);
347    }
348
349    private static String getIPv6AddressString(ByteBuffer ipv6) {
350        return getIpAddressString(ipv6, IPV6_ADDR_LEN);
351    }
352
353    private static String getIpAddressString(ByteBuffer ip, int byteLength) {
354        if (ip == null || ip.remaining() < byteLength) return "invalid";
355
356        byte[] bytes = new byte[byteLength];
357        ip.get(bytes, 0, byteLength);
358        try {
359            InetAddress addr = InetAddress.getByAddress(bytes);
360            return addr.getHostAddress();
361        } catch (UnknownHostException uhe) {
362            return "unknown";
363        }
364    }
365
366    private static String getMacAddressString(ByteBuffer mac) {
367        if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid";
368
369        byte[] bytes = new byte[ETHER_ADDR_LEN];
370        mac.get(bytes, 0, bytes.length);
371        Object[] printableBytes = new Object[bytes.length];
372        int i = 0;
373        for (byte b : bytes) printableBytes[i++] = new Byte(b);
374
375        final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
376        return String.format(MAC48_FORMAT, printableBytes);
377    }
378}
379