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