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.IPV4_CHECKSUM_OFFSET;
28 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
29 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
30 import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
31 import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
32 import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
33 import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
34 
35 import android.net.MacAddress;
36 
37 import androidx.annotation.NonNull;
38 
39 import com.android.net.module.util.structs.EthernetHeader;
40 import com.android.net.module.util.structs.Ipv4Header;
41 import com.android.net.module.util.structs.Ipv6Header;
42 import com.android.net.module.util.structs.TcpHeader;
43 import com.android.net.module.util.structs.UdpHeader;
44 
45 import java.io.IOException;
46 import java.net.Inet4Address;
47 import java.net.Inet6Address;
48 import java.nio.BufferOverflowException;
49 import java.nio.ByteBuffer;
50 
51 /**
52  * The class is used to build a packet.
53  *
54  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55  * |                Layer 2 header (EthernetHeader)                | (optional)
56  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57  * |           Layer 3 header (Ipv4Header, Ipv6Header)             |
58  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59  * |           Layer 4 header (TcpHeader, UdpHeader)               |
60  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61  * |                           Payload                             | (optional)
62  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63  *
64  * Below is a sample code to build a packet.
65  *
66  * // Initialize builder
67  * final ByteBuffer buf = ByteBuffer.allocate(...);
68  * final PacketBuilder pb = new PacketBuilder(buf);
69  * // Write headers
70  * pb.writeL2Header(...);
71  * pb.writeIpHeader(...);
72  * pb.writeTcpHeader(...);
73  * // Write payload
74  * buf.putInt(...);
75  * buf.putShort(...);
76  * buf.putByte(...);
77  * // Finalize and use the packet
78  * pb.finalizePacket();
79  * sendPacket(buf);
80  */
81 public class PacketBuilder {
82     private static final int INVALID_OFFSET = -1;
83 
84     private final ByteBuffer mBuffer;
85 
86     private int mIpv4HeaderOffset = INVALID_OFFSET;
87     private int mIpv6HeaderOffset = INVALID_OFFSET;
88     private int mTcpHeaderOffset = INVALID_OFFSET;
89     private int mUdpHeaderOffset = INVALID_OFFSET;
90 
PacketBuilder(@onNull ByteBuffer buffer)91     public PacketBuilder(@NonNull ByteBuffer buffer) {
92         mBuffer = buffer;
93     }
94 
95     /**
96      * Write an ethernet header.
97      *
98      * @param srcMac source MAC address
99      * @param dstMac destination MAC address
100      * @param etherType ether type
101      */
writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType)102     public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
103             IOException {
104         final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType);
105         try {
106             ethHeader.writeToByteBuffer(mBuffer);
107         } catch (IllegalArgumentException | BufferOverflowException e) {
108             throw new IOException("Error writing to buffer: ", e);
109         }
110     }
111 
112     /**
113      * Write an IPv4 header.
114      * The IP header length and checksum are calculated and written back in #finalizePacket.
115      *
116      * @param tos type of service
117      * @param id the identification
118      * @param flagsAndFragmentOffset flags and fragment offset
119      * @param ttl time to live
120      * @param protocol protocol
121      * @param srcIp source IP address
122      * @param dstIp destination IP address
123      */
writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl, byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)124     public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl,
125             byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)
126             throws IOException {
127         mIpv4HeaderOffset = mBuffer.position();
128         final Ipv4Header ipv4Header = new Ipv4Header(tos,
129                 (short) 0 /* totalLength, calculate in #finalizePacket */, id,
130                 flagsAndFragmentOffset, ttl, protocol,
131                 (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp);
132 
133         try {
134             ipv4Header.writeToByteBuffer(mBuffer);
135         } catch (IllegalArgumentException | BufferOverflowException e) {
136             throw new IOException("Error writing to buffer: ", e);
137         }
138     }
139 
140     /**
141      * Write an IPv6 header.
142      * The IP header length is calculated and written back in #finalizePacket.
143      *
144      * @param vtf version, traffic class and flow label
145      * @param nextHeader the transport layer protocol
146      * @param hopLimit hop limit
147      * @param srcIp source IP address
148      * @param dstIp destination IP address
149      */
writeIpv6Header(int vtf, byte nextHeader, short hopLimit, @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)150     public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit,
151             @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)
152             throws IOException {
153         mIpv6HeaderOffset = mBuffer.position();
154         final Ipv6Header ipv6Header = new Ipv6Header(vtf,
155                 (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader,
156                 hopLimit, srcIp, dstIp);
157 
158         try {
159             ipv6Header.writeToByteBuffer(mBuffer);
160         } catch (IllegalArgumentException | BufferOverflowException e) {
161             throw new IOException("Error writing to buffer: ", e);
162         }
163     }
164 
165     /**
166      * Write a TCP header.
167      * The TCP header checksum is calculated and written back in #finalizePacket.
168      *
169      * @param srcPort source port
170      * @param dstPort destination port
171      * @param seq sequence number
172      * @param ack acknowledgement number
173      * @param tcpFlags tcp flags
174      * @param window window size
175      * @param urgentPointer urgent pointer
176      */
writeTcpHeader(short srcPort, short dstPort, short seq, short ack, byte tcpFlags, short window, short urgentPointer)177     public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack,
178             byte tcpFlags, short window, short urgentPointer) throws IOException {
179         mTcpHeaderOffset = mBuffer.position();
180         final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack,
181                 (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits,
182                 dataOffset is always 5(*4bytes) because options not supported */, window,
183                 (short) 0 /* checksum, calculate in #finalizePacket */,
184                 urgentPointer);
185 
186         try {
187             tcpHeader.writeToByteBuffer(mBuffer);
188         } catch (IllegalArgumentException | BufferOverflowException e) {
189             throw new IOException("Error writing to buffer: ", e);
190         }
191     }
192 
193     /**
194      * Write a UDP header.
195      * The UDP header length and checksum are calculated and written back in #finalizePacket.
196      *
197      * @param srcPort source port
198      * @param dstPort destination port
199      */
writeUdpHeader(short srcPort, short dstPort)200     public void writeUdpHeader(short srcPort, short dstPort) throws IOException {
201         mUdpHeaderOffset = mBuffer.position();
202         final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort,
203                 (short) 0 /* length, calculate in #finalizePacket */,
204                 (short) 0 /* checksum, calculate in #finalizePacket */);
205 
206         try {
207             udpHeader.writeToByteBuffer(mBuffer);
208         } catch (IllegalArgumentException | BufferOverflowException e) {
209             throw new IOException("Error writing to buffer: ", e);
210         }
211     }
212 
213     /**
214      * Finalize the packet.
215      *
216      * Call after writing L4 header (no payload) or payload to the buffer used by the builder.
217      * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
218      * after finalization.
219      */
220     @NonNull
finalizePacket()221     public ByteBuffer finalizePacket() throws IOException {
222         // [1] Finalize IPv4 or IPv6 header.
223         int ipHeaderOffset = INVALID_OFFSET;
224         if (mIpv4HeaderOffset != INVALID_OFFSET) {
225             ipHeaderOffset = mIpv4HeaderOffset;
226 
227             // Populate the IPv4 totalLength field.
228             mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
229                     (short) (mBuffer.position() - mIpv4HeaderOffset));
230 
231             // Populate the IPv4 header checksum field.
232             mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
233                     ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
234         } else if (mIpv6HeaderOffset != INVALID_OFFSET) {
235             ipHeaderOffset = mIpv6HeaderOffset;
236 
237             // Populate the IPv6 payloadLength field.
238             // The payload length doesn't include IPv6 header length. See rfc8200 section 3.
239             mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
240                     (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN));
241         } else {
242             throw new IOException("Packet is missing neither IPv4 nor IPv6 header");
243         }
244 
245         // [2] Finalize TCP or UDP header.
246         if (mTcpHeaderOffset != INVALID_OFFSET) {
247             // Populate the TCP header checksum field.
248             mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
249                     ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
250                     mBuffer.position() - mTcpHeaderOffset /* transportLen */));
251         } else if (mUdpHeaderOffset != INVALID_OFFSET) {
252             // Populate the UDP header length field.
253             mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
254                     (short) (mBuffer.position() - mUdpHeaderOffset));
255 
256             // Populate the UDP header checksum field.
257             mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
258                     ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
259         } else {
260             throw new IOException("Packet is missing neither TCP nor UDP header");
261         }
262 
263         mBuffer.flip();
264         return mBuffer;
265     }
266 
267     /**
268      * Allocate bytebuffer for building the packet.
269      *
270      * @param hasEther has ethernet header. Set this flag to indicate that the packet has an
271      *        ethernet header.
272      * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
273      *        currently supported.
274      * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
275      *        currently supported.
276      * @param payloadLen length of the payload.
277      */
278     @NonNull
allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen)279     public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
280         if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
281             throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
282         }
283 
284         if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
285             throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto);
286         }
287 
288         if (payloadLen < 0) {
289             throw new IllegalArgumentException("Invalid payload length " + payloadLen);
290         }
291 
292         int packetLen = 0;
293         if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
294         packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class)
295                 : Struct.getSize(Ipv6Header.class);
296         packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
297                 : Struct.getSize(UdpHeader.class);
298         packetLen += payloadLen;
299 
300         return ByteBuffer.allocate(packetLen);
301     }
302 }
303