1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.darwin.system;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertNotEquals;
22 import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24 import static org.junit.jupiter.api.Assertions.fail;
25 import static org.junit.jupiter.api.Assumptions.assumeTrue;
26
27 import java.io.IOException;
28 import java.net.Inet4Address;
29 import java.net.InetAddress;
30 import java.net.SocketException;
31 import java.net.UnknownHostException;
32 import java.nio.ByteBuffer;
33 import java.nio.ByteOrder;
34 import java.time.Duration;
35 import java.util.Objects;
36 import java.util.concurrent.CompletableFuture;
37 import java.util.concurrent.TimeUnit;
38
39 import org.junit.jupiter.api.Test;
40 import org.newsclub.net.unix.AFSYSTEMSocketAddress;
41 import org.newsclub.net.unix.AFSYSTEMSocketAddress.SysAddr;
42 import org.newsclub.net.unix.AFSocketCapability;
43 import org.newsclub.net.unix.AFSocketCapabilityRequirement;
44
45 import com.kohlschutter.testutil.ExecutionEnvironmentRequirement;
46 import com.kohlschutter.testutil.ExecutionEnvironmentRequirement.Rule;
47
48
49
50
51
52
53
54
55
56 @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
57 public class UtunTest {
58 private static final Inet4Address UTUN_SRC_IP;
59 private static final Inet4Address UTUN_DST_IP;
60
61 static {
62 try {
63 UTUN_SRC_IP = (Inet4Address) InetAddress.getByName("169.254.3.4");
64 UTUN_DST_IP = (Inet4Address) InetAddress.getByName("169.254.3.5");
65 } catch (UnknownHostException e) {
66 throw new IllegalStateException(e);
67 }
68 }
69
70
71
72
73
74
75
76 private static Object unchecked(Object v) {
77 return v;
78 }
79
80
81
82
83
84
85
86 private static int getAddressAsInt(Inet4Address addr) {
87
88 return addr.hashCode();
89 }
90
91 @SuppressWarnings({
92 "checkstyle:VariableDeclarationUsageDistance", "PMD.JUnitTestContainsTooManyAsserts",
93 "PMD.AvoidBranchingStatementAsLastInLoop"})
94 @Test
95 @ExecutionEnvironmentRequirement(root = Rule.REQUIRED)
96 @AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_DARWIN)
97 public void testTunnelPingPong() throws Exception {
98 try (AFSYSTEMDatagramSocket socket = AFSYSTEMDatagramSocket.newInstance()) {
99 int id = socket.getNodeIdentity(WellKnownKernelControlNames.UTUN_CONTROL);
100
101
102
103 try {
104 socket.connect(AFSYSTEMSocketAddress.ofSysAddrIdUnit(SysAddr.AF_SYS_CONTROL, id, 0));
105 } catch (SocketException e) {
106 assumeTrue(false, "Could not connect to UTUN_CONTROL: " + e);
107 return;
108 }
109
110 assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
111
112 AFSYSTEMSocketAddress rsa = socket.getRemoteSocketAddress();
113 Objects.requireNonNull(rsa);
114
115 assertEquals(SysAddr.AF_SYS_CONTROL, rsa.getSysAddr());
116 assertEquals(id, rsa.getId());
117 assertNotEquals(0, rsa.getUnit());
118
119 String utun = "utun" + (rsa.getUnit() - 1);
120
121
122 Process p = Runtime.getRuntime().exec(new String[] {
123 "/sbin/ifconfig", utun, UTUN_SRC_IP.getHostAddress(), UTUN_DST_IP.getHostAddress()});
124 int rcIfconfig;
125 try {
126 rcIfconfig = p.waitFor();
127 } finally {
128 p.destroyForcibly();
129 }
130
131 assertEquals(0, rcIfconfig, "Could not set IP address for " + utun);
132
133 AFSYSTEMDatagramChannel channel = socket.getChannel();
134 ByteBuffer bb = ByteBuffer.allocateDirect(1500).order(ByteOrder.BIG_ENDIAN);
135
136 CompletableFuture<Boolean> ping = CompletableFuture.supplyAsync(() -> {
137 try {
138 return UTUN_DST_IP.isReachable(1000);
139 } catch (IOException e) {
140 e.printStackTrace();
141 return false;
142 }
143 });
144
145 while (channel.read(bb) >= 0) {
146 bb.flip();
147
148
149
150 int totalSize = bb.remaining();
151
152
153 int domain = bb.getInt();
154 assertEquals(IPUtil.DOMAIN_AF_INET, domain, "Expect domain 2 (AF_INET)");
155
156 int ipHeaderStartPos = bb.position();
157
158 int versionAndIHL = bb.get() & 0xFF;
159 int version = versionAndIHL >> 4;
160 assertEquals(4, version, "expect IPv4 packet");
161
162
163
164 int ihl = versionAndIHL & 0b1111;
165 int ihlBytes = ihl * 32 / 8;
166 assertTrue(ihlBytes >= 20, "expect (at least) 20 bytes header length");
167
168 int tosDSCP = (bb.get() & 0xFF);
169 unchecked(tosDSCP);
170
171 int totalLen = (bb.getShort() & 0xFFFF);
172 assertEquals(totalSize - 4, totalLen);
173
174 int identification = (bb.getShort() & 0xFFFF);
175 unchecked(identification);
176
177 int flagsAndFragmentOffset = (bb.getShort() & 0xFFFF);
178 int flags = flagsAndFragmentOffset >> 13;
179 int fragmentOffset = flagsAndFragmentOffset & 0b1_1111_1111_1111;
180 assertEquals(0, flags);
181 assertEquals(0, fragmentOffset);
182
183 int ttl = bb.get() & 0xFF;
184 assertNotEquals(0, ttl);
185
186 int protocol = bb.get() & 0xFF;
187 assertEquals(IPUtil.AF_INET_PROTOCOL_ICMP, protocol);
188
189 int headerChecksum = bb.getShort() & 0xFFFF;
190
191
192 int srcIP = bb.getInt();
193 int dstIP = bb.getInt();
194
195 assertEquals(getAddressAsInt(UTUN_SRC_IP), srcIP);
196 assertEquals(getAddressAsInt(UTUN_DST_IP), dstIP);
197
198
199
200
201 int remainingHeaderLength = ihlBytes - 20;
202 if (remainingHeaderLength > 0) {
203 System.err.println("Warning: Found unexpected Options section in IPv4 header; len="
204 + remainingHeaderLength);
205 bb.position(bb.position() + remainingHeaderLength);
206 }
207
208
209
210 int computedHeaderChecksum = IPUtil.checksumIPv4header(bb, ipHeaderStartPos, bb
211 .position());
212 assertEquals(computedHeaderChecksum, headerChecksum);
213
214 int icmpSize = bb.remaining();
215
216
217 int icmpBeginPosition = bb.position();
218
219
220 int icmpType = bb.get() & 0xFF;
221 assertEquals(8, icmpType);
222
223 int icmpCode = bb.get() & 0xFF;
224 assertEquals(0, icmpCode);
225
226 int icmpChecksum = bb.getShort() & 0xFFFF;
227
228 int icmpEchoIdentifier = bb.getShort() & 0xFFFF;
229 int icmpEchoSequenceNumber = bb.getShort() & 0xFFFF;
230
231 unchecked(icmpEchoIdentifier);
232 assertEquals(1, icmpEchoSequenceNumber);
233
234 int icmpChecksumComputed =
235 IPUtil.checksumICMPheader(bb, icmpBeginPosition, bb.position() + bb.remaining());
236 assertEquals(icmpChecksumComputed, icmpChecksum);
237
238
239
240
241 ByteBuffer response = ByteBuffer.allocate(IPUtil.DOMAIN_HEADER_LENGTH
242 + IPUtil.IPV4_DEFAULT_HEADER_SIZE + icmpSize).order(ByteOrder.BIG_ENDIAN);
243 response.putInt(IPUtil.DOMAIN_AF_INET);
244 IPUtil.putIPv4Header(response, icmpSize, IPUtil.AF_INET_PROTOCOL_ICMP, dstIP, srcIP);
245
246 int responsePayloadStart = response.position();
247 IPUtil.checksumIPv4header(response, IPUtil.DOMAIN_HEADER_LENGTH, responsePayloadStart);
248
249 IPUtil.putICMPEchoResponse(response, (short) icmpEchoIdentifier,
250 (short) icmpEchoSequenceNumber, bb);
251 assertEquals(0, bb.remaining());
252 int responsePayloadEnd = response.position();
253
254 IPUtil.checksumICMPheader(response, responsePayloadStart, responsePayloadEnd);
255
256 response.flip();
257 int written = channel.write(response);
258 bb.clear();
259
260 assertEquals(response.capacity(), written);
261
262 assertTrue(ping.get(1, TimeUnit.SECONDS));
263
264 return;
265 }
266
267 fail("Nothing received");
268 });
269 }
270 }
271 }