View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.darwin.system;
19  
20  import java.nio.ByteBuffer;
21  
22  /**
23   * Some IP protocol-related helper methods.
24   *
25   * @author Christian Kohlschütter
26   */
27  public final class IPUtil {
28    /**
29     * The length (in bytes) of the "domain" header used in loopback packet systems like UTUN_CONTROL.
30     */
31    public static final int DOMAIN_HEADER_LENGTH = 4; // bytes
32  
33    /**
34     * The identifier for AF_INET (at least on Darwin).
35     */
36    public static final int DOMAIN_AF_INET = 2;
37  
38    /**
39     * The length (in bytes) of an IPv4 header without options.
40     */
41    public static final int IPV4_DEFAULT_HEADER_SIZE = 20; // bytes
42  
43    /**
44     * The ICMP protocol.
45     */
46    public static final byte AF_INET_PROTOCOL_ICMP = 1;
47  
48    private IPUtil() {
49      throw new IllegalStateException("No instances");
50    }
51  
52    /**
53     * Computes the checksum for an IPv4 header, and overwrites any existing checksum with the correct
54     * one.
55     *
56     * @param bb The buffer containing the IPv4 header
57     * @param start The beginning position of the header in the buffer.
58     * @param end The end position (exclusive) of the header in the buffer.
59     * @return The computed 16-bit checksum
60     */
61    public static int checksumIPv4header(ByteBuffer bb, int start, int end) {
62      return checksumIPstyle(bb, start, end, 10);
63    }
64  
65    /**
66     * Computes the checksum for an ICMP header, and overwrites any existing checksum with the correct
67     * one.
68     *
69     * Also see <a href="https://datatracker.ietf.org/doc/html/rfc792">RFC 792</a>.
70     *
71     * @param bb The buffer containing the ICMP header
72     * @param start The beginning position of the header in the buffer.
73     * @param end The end position (exclusive) of the header in the buffer.
74     * @return The computed 16-bit checksum
75     */
76    public static int checksumICMPheader(ByteBuffer bb, int start, int end) {
77      return checksumIPstyle(bb, start, end, 2);
78    }
79  
80    /**
81     * Computes the 16-bit checksum for some header used in IP networking, and overwrites any existing
82     * checksum with the correct one.
83     *
84     * Also see <a href="https://datatracker.ietf.org/doc/html/rfc1071">RFC 1071</a>.
85     *
86     * @param bb The buffer containing the ICMP header
87     * @param start The beginning position of the header in the buffer.
88     * @param end The end position (exclusive) of the header in the buffer.
89     * @param checksumOffset The offset from start for an existing 16-bit checksum that is to be
90     *          ignored.
91     * @return The computed 16-bit checksum
92     */
93    private static int checksumIPstyle(ByteBuffer bb, int start, int end, int checksumOffset) {
94      final int checksumAt = start + checksumOffset;
95      int sum = 0;
96  
97      if (checksumOffset >= end) {
98        throw new IllegalArgumentException("checksumOffset");
99      }
100 
101     // While we could pretend the checksum is 0 (by ignoring the computation at position
102     // checksumAt), we zero it out here, and later put the correct checksum back in.
103     // This should not only be faster than two for-loops or checking the position prior to adding,
104     // it also puts the correct checksum in place, which can come in handy when composing packets.
105     // It is also the recommended strategy as per RFC 1071. The downside is that we modify the
106     // contents of the buffer, but that's OK since we control the API.
107     bb.putShort(checksumAt, (short) 0);
108 
109     for (int i = start; i < end; i += 2) {
110       int v = bb.getShort(i) & 0xFFFF;
111 
112       sum += v;
113 
114       int overflow = (sum & ~0xFFFF);
115       if (overflow != 0) {
116         // overflow -> add carry and trim to 16-bit
117         sum = (sum + (overflow >>> 16)) & 0xFFFF;
118       }
119     }
120 
121     int checksum = (~sum) & 0xFFFF;
122 
123     // fix checksum
124     bb.putShort(checksumAt, (short) checksum);
125 
126     return checksum;
127   }
128 
129   /**
130    * Put (write) an IPv4 header to the given byte buffer, using the given parameters.
131    *
132    * This should write exactly 20 bytes to the buffer. The buffer position then is at the end of the
133    * header.
134    *
135    * @param bb The target byte buffer.
136    * @param payloadLength The length of the payload (excluding the IPv4 header).
137    * @param protocol The protocol identifier.
138    * @param srcIP The source IPv4 address.
139    * @param dstIP The destination IPv4 address.
140    */
141   public static void putIPv4Header(ByteBuffer bb, int payloadLength, byte protocol, int srcIP,
142       int dstIP) {
143     bb.put((byte) 0x45); // IPv4, 5*4=20 bytes header
144     bb.put((byte) 0); // TOS/DSCP
145     bb.putShort((short) (20 + payloadLength)); // total length = header + payload
146     bb.putShort((short) 0); // identification
147     bb.putShort((short) 0); // flags and fragment offset
148     bb.put((byte) 65); // TTL
149     bb.put(protocol); // protocol (e.g., ICMP)
150     bb.putShort((short) 0); // header checksum (placeholder)
151     bb.putInt(srcIP);
152     bb.putInt(dstIP);
153     // end of header (20 bytes)
154   }
155 
156   /**
157    * Put (write) an ICMP echo response header to the given byte buffer, using the given parameters.
158    *
159    * @param bb The target byte buffer.
160    * @param echoIdentifier The identifier, from the ICMP echo request.
161    * @param sequenceNumber The sequence number, from the ICMP echo request.
162    * @param payload The payload, from the ICMP echo request.
163    */
164   public static void putICMPEchoResponse(ByteBuffer bb, short echoIdentifier, short sequenceNumber,
165       ByteBuffer payload) {
166     bb.put((byte) 0); // Echo response
167     bb.put((byte) 0); // Echo has no other code
168     bb.putShort((short) 0); // ICMP checksum (placeholder)
169     bb.putShort(echoIdentifier); // ICMP echo identifier
170     bb.putShort(sequenceNumber); // ICMP echo sequence number
171     bb.put(payload);
172   }
173 }