• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.net.module.util;
18 
19 import static android.system.OsConstants.IPPROTO_IP;
20 import static android.system.OsConstants.IPPROTO_IPV6;
21 import static android.system.OsConstants.IPPROTO_TCP;
22 import static android.system.OsConstants.IPPROTO_UDP;
23 
24 import static com.android.net.module.util.IpUtils.ipChecksum;
25 import static com.android.net.module.util.IpUtils.tcpChecksum;
26 import static com.android.net.module.util.IpUtils.udpChecksum;
27 import static com.android.net.module.util.NetworkStackConstants.IPPROTO_FRAGMENT;
28 import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
29 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
30 import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_HEADER_LEN;
31 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
32 import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
33 import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
34 import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
35 import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
36 import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
37 
38 import android.net.MacAddress;
39 
40 import androidx.annotation.NonNull;
41 
42 import com.android.net.module.util.structs.EthernetHeader;
43 import com.android.net.module.util.structs.FragmentHeader;
44 import com.android.net.module.util.structs.Ipv4Header;
45 import com.android.net.module.util.structs.Ipv6Header;
46 import com.android.net.module.util.structs.TcpHeader;
47 import com.android.net.module.util.structs.UdpHeader;
48 
49 import java.io.IOException;
50 import java.net.Inet4Address;
51 import java.net.Inet6Address;
52 import java.nio.BufferOverflowException;
53 import java.nio.ByteBuffer;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.List;
57 import java.util.Random;
58 
59 /**
60  * The class is used to build a packet.
61  *
62  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63  * |                Layer 2 header (EthernetHeader)                | (optional)
64  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
65  * |           Layer 3 header (Ipv4Header, Ipv6Header)             |
66  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
67  * |           Layer 4 header (TcpHeader, UdpHeader)               |
68  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
69  * |                           Payload                             | (optional)
70  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71  *
72  * Below is a sample code to build a packet.
73  *
74  * // Initialize builder
75  * final ByteBuffer buf = ByteBuffer.allocate(...);
76  * final PacketBuilder pb = new PacketBuilder(buf);
77  * // Write headers
78  * pb.writeL2Header(...);
79  * pb.writeIpHeader(...);
80  * pb.writeTcpHeader(...);
81  * // Write payload
82  * buf.putInt(...);
83  * buf.putShort(...);
84  * buf.putByte(...);
85  * // Finalize and use the packet
86  * pb.finalizePacket();
87  * sendPacket(buf);
88  */
89 public class PacketBuilder {
90     private static final int INVALID_OFFSET = -1;
91 
92     private final ByteBuffer mBuffer;
93 
94     private int mIpv4HeaderOffset = INVALID_OFFSET;
95     private int mIpv6HeaderOffset = INVALID_OFFSET;
96     private int mTcpHeaderOffset = INVALID_OFFSET;
97     private int mUdpHeaderOffset = INVALID_OFFSET;
98 
PacketBuilder(@onNull ByteBuffer buffer)99     public PacketBuilder(@NonNull ByteBuffer buffer) {
100         mBuffer = buffer;
101     }
102 
103     /**
104      * Write an ethernet header.
105      *
106      * @param srcMac source MAC address
107      * @param dstMac destination MAC address
108      * @param etherType ether type
109      */
writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType)110     public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
111             IOException {
112         final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType);
113         try {
114             ethHeader.writeToByteBuffer(mBuffer);
115         } catch (IllegalArgumentException | BufferOverflowException e) {
116             throw new IOException("Error writing to buffer: ", e);
117         }
118     }
119 
120     /**
121      * Write an IPv4 header.
122      * The IP header length and checksum are calculated and written back in #finalizePacket.
123      *
124      * @param tos type of service
125      * @param id the identification
126      * @param flagsAndFragmentOffset flags and fragment offset
127      * @param ttl time to live
128      * @param protocol protocol
129      * @param srcIp source IP address
130      * @param dstIp destination IP address
131      */
writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl, byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)132     public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl,
133             byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)
134             throws IOException {
135         mIpv4HeaderOffset = mBuffer.position();
136         final Ipv4Header ipv4Header = new Ipv4Header(tos,
137                 (short) 0 /* totalLength, calculate in #finalizePacket */, id,
138                 flagsAndFragmentOffset, ttl, protocol,
139                 (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp);
140 
141         try {
142             ipv4Header.writeToByteBuffer(mBuffer);
143         } catch (IllegalArgumentException | BufferOverflowException e) {
144             throw new IOException("Error writing to buffer: ", e);
145         }
146     }
147 
148     /**
149      * Write an IPv6 header.
150      * The IP header length is calculated and written back in #finalizePacket.
151      *
152      * @param vtf version, traffic class and flow label
153      * @param nextHeader the transport layer protocol
154      * @param hopLimit hop limit
155      * @param srcIp source IP address
156      * @param dstIp destination IP address
157      */
writeIpv6Header(int vtf, byte nextHeader, short hopLimit, @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)158     public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit,
159             @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)
160             throws IOException {
161         mIpv6HeaderOffset = mBuffer.position();
162         final Ipv6Header ipv6Header = new Ipv6Header(vtf,
163                 (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader,
164                 hopLimit, srcIp, dstIp);
165 
166         try {
167             ipv6Header.writeToByteBuffer(mBuffer);
168         } catch (IllegalArgumentException | BufferOverflowException e) {
169             throw new IOException("Error writing to buffer: ", e);
170         }
171     }
172 
173     /**
174      * Write a TCP header.
175      * The TCP header checksum is calculated and written back in #finalizePacket.
176      *
177      * @param srcPort source port
178      * @param dstPort destination port
179      * @param seq sequence number
180      * @param ack acknowledgement number
181      * @param tcpFlags tcp flags
182      * @param window window size
183      * @param urgentPointer urgent pointer
184      */
writeTcpHeader(short srcPort, short dstPort, short seq, short ack, byte tcpFlags, short window, short urgentPointer)185     public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack,
186             byte tcpFlags, short window, short urgentPointer) throws IOException {
187         mTcpHeaderOffset = mBuffer.position();
188         final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack,
189                 (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits,
190                 dataOffset is always 5(*4bytes) because options not supported */, window,
191                 (short) 0 /* checksum, calculate in #finalizePacket */,
192                 urgentPointer);
193 
194         try {
195             tcpHeader.writeToByteBuffer(mBuffer);
196         } catch (IllegalArgumentException | BufferOverflowException e) {
197             throw new IOException("Error writing to buffer: ", e);
198         }
199     }
200 
201     /**
202      * Write a UDP header.
203      * The UDP header length and checksum are calculated and written back in #finalizePacket.
204      *
205      * @param srcPort source port
206      * @param dstPort destination port
207      */
writeUdpHeader(short srcPort, short dstPort)208     public void writeUdpHeader(short srcPort, short dstPort) throws IOException {
209         mUdpHeaderOffset = mBuffer.position();
210         final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort,
211                 (short) 0 /* length, calculate in #finalizePacket */,
212                 (short) 0 /* checksum, calculate in #finalizePacket */);
213 
214         try {
215             udpHeader.writeToByteBuffer(mBuffer);
216         } catch (IllegalArgumentException | BufferOverflowException e) {
217             throw new IOException("Error writing to buffer: ", e);
218         }
219     }
220 
writeFragmentHeader(ByteBuffer buffer, short nextHeader, int offset, boolean mFlag, int id)221     private void writeFragmentHeader(ByteBuffer buffer, short nextHeader, int offset,
222             boolean mFlag, int id) throws IOException {
223         if ((offset & 7) != 0) {
224             throw new IOException("Invalid offset value, must be multiple of 8");
225         }
226         final FragmentHeader fragmentHeader = new FragmentHeader(nextHeader,
227                 offset | (mFlag ? 1 : 0), id);
228         try {
229             fragmentHeader.writeToByteBuffer(buffer);
230         } catch (IllegalArgumentException | BufferOverflowException e) {
231             throw new IOException("Error writing to buffer: ", e);
232         }
233     }
234 
235     /**
236      * Finalize the packet.
237      *
238      * Call after writing L4 header (no payload) or payload to the buffer used by the builder.
239      * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
240      * after finalization.
241      */
242     @NonNull
finalizePacket()243     public ByteBuffer finalizePacket() throws IOException {
244         // If the packet is finalized with L2 mtu greater than or equal to its current size, it will
245         // either return a List of size 1 or throw an IOException if something goes wrong.
246         return finalizePacket(mBuffer.position()).get(0);
247     }
248 
249     /**
250      * Finalizes the packet with specified link MTU.
251      *
252      * Call after writing L4 header (no payload) or L4 payload to the buffer used by the builder.
253      * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
254      * after finalization.
255      *
256      * @param l2mtu the maximum size, in bytes, of each individual packet. If the packet size
257      *              exceeds the l2mtu, it will be fragmented into smaller packets.
258      * @return a list of packet(s), each containing a portion of the original L3 payload.
259      */
260     @NonNull
finalizePacket(int l2mtu)261     public List<ByteBuffer> finalizePacket(int l2mtu) throws IOException {
262         // [1] Finalize IPv4 or IPv6 header.
263         int ipHeaderOffset = INVALID_OFFSET;
264         if (mIpv4HeaderOffset != INVALID_OFFSET) {
265             if (mBuffer.position() > l2mtu) {
266                 throw new IOException("IPv4 fragmentation is not supported");
267             }
268 
269             ipHeaderOffset = mIpv4HeaderOffset;
270 
271             // Populate the IPv4 totalLength field.
272             mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
273                     (short) (mBuffer.position() - mIpv4HeaderOffset));
274 
275             // Populate the IPv4 header checksum field.
276             mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
277                     ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
278         } else if (mIpv6HeaderOffset != INVALID_OFFSET) {
279             ipHeaderOffset = mIpv6HeaderOffset;
280 
281             // Populate the IPv6 payloadLength field.
282             // The payload length doesn't include IPv6 header length. See rfc8200 section 3.
283             mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
284                     (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN));
285         } else {
286             throw new IOException("Packet is missing neither IPv4 nor IPv6 header");
287         }
288 
289         // [2] Finalize TCP or UDP header.
290         final int ipPayloadOffset;
291         if (mTcpHeaderOffset != INVALID_OFFSET) {
292             ipPayloadOffset = mTcpHeaderOffset;
293             // Populate the TCP header checksum field.
294             mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
295                     ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
296                     mBuffer.position() - mTcpHeaderOffset /* transportLen */));
297         } else if (mUdpHeaderOffset != INVALID_OFFSET) {
298             ipPayloadOffset = mUdpHeaderOffset;
299             // Populate the UDP header length field.
300             mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
301                     (short) (mBuffer.position() - mUdpHeaderOffset));
302 
303             // Populate the UDP header checksum field.
304             mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
305                     ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
306         } else {
307             throw new IOException("Packet has neither TCP nor UDP header");
308         }
309 
310         if (mBuffer.position() <= l2mtu) {
311             mBuffer.flip();
312             return Arrays.asList(mBuffer);
313         }
314 
315         // IPv6 Packet is fragmented into multiple smaller packets that would fit within the link
316         // MTU.
317         // Refer to https://tools.ietf.org/html/rfc2460
318         //
319         // original packet:
320         // +------------------+--------------+--------------+--//--+----------+
321         // |  Unfragmentable  |    first     |    second    |      |   last   |
322         // |       Part       |   fragment   |   fragment   | .... | fragment |
323         // +------------------+--------------+--------------+--//--+----------+
324         //
325         // fragment packets:
326         // +------------------+--------+--------------+
327         // |  Unfragmentable  |Fragment|    first     |
328         // |       Part       | Header |   fragment   |
329         // +------------------+--------+--------------+
330         //
331         // +------------------+--------+--------------+
332         // |  Unfragmentable  |Fragment|    second    |
333         // |       Part       | Header |   fragment   |
334         // +------------------+--------+--------------+
335         //                       o
336         //                       o
337         //                       o
338         // +------------------+--------+----------+
339         // |  Unfragmentable  |Fragment|   last   |
340         // |       Part       | Header | fragment |
341         // +------------------+--------+----------+
342         final List<ByteBuffer> fragments = new ArrayList<>();
343         final int totalPayloadLen = mBuffer.position() - ipPayloadOffset;
344         final int perPacketPayloadLen = l2mtu - ipPayloadOffset - IPV6_FRAGMENT_HEADER_LEN;
345         final short protocol = (short) Byte.toUnsignedInt(
346                 mBuffer.get(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET));
347         Random random = new Random();
348         final int id = random.nextInt(Integer.MAX_VALUE);
349         int startOffset = 0;
350         // Copy the packet content to a byte array.
351         byte[] packet = new byte[mBuffer.position()];
352         // The ByteBuffer#get(int index, byte[] dst) method is only available in API level 35 and
353         // above. Here, we use a more primitive approach: reposition the ByteBuffer to the beginning
354         // before copying, then return its position to the end afterward.
355         mBuffer.position(0);
356         mBuffer.get(packet);
357         mBuffer.position(packet.length);
358         while (startOffset < totalPayloadLen) {
359             int copyPayloadLen = Math.min(perPacketPayloadLen, totalPayloadLen - startOffset);
360             // The data portion must be broken into segments aligned with 8-octet boundaries.
361             // Therefore, the payload length should be a multiple of 8 bytes for all fragments
362             // except the last one.
363             // See https://datatracker.ietf.org/doc/html/rfc791 section 3.2
364             if (copyPayloadLen != totalPayloadLen - startOffset) {
365                 copyPayloadLen &= ~7;
366             }
367             ByteBuffer fragment = ByteBuffer.allocate(ipPayloadOffset + IPV6_FRAGMENT_HEADER_LEN
368                     + copyPayloadLen);
369             fragment.put(packet, 0, ipPayloadOffset);
370             writeFragmentHeader(fragment, protocol, startOffset,
371                     startOffset + copyPayloadLen < totalPayloadLen, id);
372             fragment.put(packet, ipPayloadOffset + startOffset, copyPayloadLen);
373             fragment.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
374                     (short) (IPV6_FRAGMENT_HEADER_LEN + copyPayloadLen));
375             fragment.put(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET, (byte) IPPROTO_FRAGMENT);
376             fragment.flip();
377             fragments.add(fragment);
378             startOffset += copyPayloadLen;
379         }
380 
381         return fragments;
382     }
383 
384     /**
385      * Allocate bytebuffer for building the packet.
386      *
387      * @param hasEther has ethernet header. Set this flag to indicate that the packet has an
388      *        ethernet header.
389      * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
390      *        currently supported.
391      * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
392      *        currently supported.
393      * @param payloadLen length of the payload.
394      */
395     @NonNull
allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen)396     public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
397         if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
398             throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
399         }
400 
401         if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
402             throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto);
403         }
404 
405         if (payloadLen < 0) {
406             throw new IllegalArgumentException("Invalid payload length " + payloadLen);
407         }
408 
409         int packetLen = 0;
410         if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
411         packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class)
412                 : Struct.getSize(Ipv6Header.class);
413         packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
414                 : Struct.getSize(UdpHeader.class);
415         packetLen += payloadLen;
416 
417         return ByteBuffer.allocate(packetLen);
418     }
419 }
420