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 
17 package android.net.util;
18 
19 import android.net.dhcp.DhcpPacket;
20 import android.net.MacAddress;
21 
22 import java.net.InetAddress;
23 import java.net.UnknownHostException;
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.util.Arrays;
27 import java.util.StringJoiner;
28 
29 import static android.system.OsConstants.*;
30 import static android.net.util.NetworkConstants.*;
31 
32 
33 /**
34  * Critical connectivity packet summarizing class.
35  *
36  * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
37  *
38  * @hide
39  */
40 public class ConnectivityPacketSummary {
41     private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
42 
43     private final byte[] mHwAddr;
44     private final byte[] mBytes;
45     private final int mLength;
46     private final ByteBuffer mPacket;
47     private final String mSummary;
48 
summarize(MacAddress hwaddr, byte[] buffer)49     public static String summarize(MacAddress hwaddr, byte[] buffer) {
50         return summarize(hwaddr, buffer, buffer.length);
51     }
52 
53     // Methods called herein perform some but by no means all error checking.
54     // They may throw runtime exceptions on malformed packets.
summarize(MacAddress macAddr, byte[] buffer, int length)55     public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
56         if ((macAddr == null) || (buffer == null)) return null;
57         length = Math.min(length, buffer.length);
58         return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
59     }
60 
ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length)61     private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
62         mHwAddr = macAddr.toByteArray();
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 
toString()74     public String toString() {
75         return mSummary;
76     }
77 
parseEther(StringJoiner sj)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 
parseARP(StringJoiner sj)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 
parseIPv4(StringJoiner sj)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 
parseIPv6(StringJoiner sj)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 
parseICMPv6(StringJoiner sj)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 
parseICMPv6RouterSolicitation(StringJoiner sj)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 
parseICMPv6RouterAdvertisement(StringJoiner sj)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 
parseICMPv6NeighborMessage(StringJoiner sj)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 
parseICMPv6NeighborDiscoveryOptions(StringJoiner sj)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 
parseUDP(StringJoiner sj)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 
parseDHCPv4(StringJoiner sj)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 
getIPv4AddressString(ByteBuffer ipv4)345     private static String getIPv4AddressString(ByteBuffer ipv4) {
346         return getIpAddressString(ipv4, IPV4_ADDR_LEN);
347     }
348 
getIPv6AddressString(ByteBuffer ipv6)349     private static String getIPv6AddressString(ByteBuffer ipv6) {
350         return getIpAddressString(ipv6, IPV6_ADDR_LEN);
351     }
352 
getIpAddressString(ByteBuffer ip, int byteLength)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 
getMacAddressString(ByteBuffer mac)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