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