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