-
Notifications
You must be signed in to change notification settings - Fork 33
/
IPAddress.java
564 lines (493 loc) · 19.1 KB
/
IPAddress.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
/*
* Copyright (c) 2000, 2017 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.messaging.jmq.util.net;
import java.io.*;
import java.util.Arrays;
import java.net.InetAddress;
import com.sun.messaging.jmq.util.Bits;
/**
* This class encapsulates an IP address which may be either IPv4 (32 bit) or IPv6 (128 bit). The JDK does not yet
* support IPv6 addresses, but the Swift packet format uses 128 bit addresses (in preparation for IPv6) and therefore we
* need this class. Internally everything is kept as an IPv6 address. If an IPv4 address is set (which will be the
* typical case until IPv6 is adopted), then it is converted to an IPv4-mapped IPv6 address. See RFC 2373 for more info
* on IPv6.
*
* Additionally we have added a third format called IPv4+MAC. In iMQ IP addresses are simply used as a unique identifier
* for clients. Since an IPv4 address is not suitably unique (for example when NAT is used) we add the ability to
* embedded a 40 bit MAC address (or psuedo MAC address) along with an IPv4 address in the 128 bits an IPv6 address
* gives us. The format is:
*
* +--------+--------+--------+--------+
* |11111111|00000000|00000000|00000000|
* +--------+--------+--------+--------+
* | 48 bit MAC address . . .
* +--------+--------+--------+--------+
* . . . |11111111|11111111|
* +--------+--------+--------+--------+
* | 32 bit IPv4 address |
* +--------+--------+--------+--------+
*
* The leading 11111111 is used in IPv6 to signify an IPv6 multicast address. Since a client will never be assigned a
* multicast address we can use this prefix to indicate an IPv4+MAC address.
*/
public class IPAddress implements Cloneable, Serializable {
private static final long serialVersionUID = -3749627756354392982L;
/**
* Size of an IPv4 address in bytes
*/
public static final int IPV4_SIZE = 4;
/**
* Size of an IPv6 address in bytes
*/
public static final int IPV6_SIZE = 16;
// Address types
public static final int UNKNOWN = 0;
public static final int IPV4 = 1; // IPv4-mapped IPv6
public static final int IPV6 = 2; // IPv6 unicast
public static final int IPV4MAC = 3; // iMQ IPV4 + MAC
// Format of address. One of above types
protected int type = UNKNOWN;
// Prefix used to create an IPv4-mapped IPv6 address
private static byte[] prefix = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, (byte) 0xFF, (byte) 0xFF };
// Prefix used to identify the iMQ IPv4+MAC format
private static byte[] IPv4MacPrefix = { (byte) 0xFF, 0x0, 0x0, 0x0 };
// A null IPv6 address
private static byte[] nullAddr = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, (byte) 0xFF, (byte) 0xFF, 0x0, 0x0, 0x0, 0x0 };
// 128 bit buffer to hold IP address. We always store address as IPv6
protected byte ip[] = new byte[IPV6_SIZE];
/**
* Construct an unititialized IP address
*/
public IPAddress() {
clear();
}
/**
* Clear the IP address.
*/
public void clear() {
System.arraycopy(nullAddr, 0, ip, 0, nullAddr.length);
type = IPV4;
}
/**
* Get the type of address we are encapsulating
*/
public int getType() {
if (type == UNKNOWN) {
if (isIPv4Mapped(ip)) {
type = IPV4;
} else if (isIPv4Mac(ip)) {
type = IPV4MAC;
} else {
type = IPV6;
}
}
return type;
}
/**
* Make a deep copy of the IP address
*/
@Override
public Object clone() {
IPAddress newIP = null;
try {
newIP = (IPAddress) super.clone();
newIP.ip = (this.ip.clone());
} catch (CloneNotSupportedException e) {
// this should never happen since we are Cloneable and so
// are arrays. We print a message just in case.
System.out.println("IPAddress: Could not clone: " + e);
}
return newIP;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof IPAddress) {
return isEqual(ip, ((IPAddress) obj).ip);
} else {
return false;
}
}
@Override
public int hashCode() {
if (ip == null) {
return 0;
}
return Arrays.hashCode(ip);
}
/**
* Check if the IP address is 0
*
* @return true if IP address is 0 false if IP address is not 0
*/
public static boolean isNull(byte[] addr) {
if (addr == null || isEqual(addr, nullAddr)) {
return true;
} else {
return false;
}
}
/**
* Check if two raw IP addresses are equal. Handles either or both buffers being null. If they are both null they are
* considered equal.
*
* @param ip1 Raw IP address to compare
* @param ip2 Raw IP address to compare
*/
private static boolean isEqual(byte[] ip1, byte[] ip2) {
if (ip1 == ip2) {
// Same buffer
return true;
} else if (ip1 == null || ip2 == null) {
// One null buffer
return false;
} else if (ip1.length == ip2.length) {
// Different buffers, same length, check content.
// We start at the end since that is most likely to be different
for (int n = ip1.length - 1; n >= 0; n--) {
if (ip1[n] != ip2[n]) {
return false;
}
}
// Content matches
return true;
} else {
// Different lengths
return false;
}
}
/**
* Get the IP address
*
* @return The raw IP address. This will always be an IPv6 (128 bit) address. It is a copy
*/
public byte[] getAddress() {
return ip.clone();
}
/**
* Get the IP address
*
* @return The raw IP address. This will always be an IPv6 (128 bit) address. It is not a copy. Caller should treat the
* buffer as read-only.
*/
public byte[] getAddressUnsafe() {
return ip;
}
/**
* Get the IP address as an IPv4 address.
*
* @return The raw IP address if the current address is an IPv4 address (or an IPv4-mapped IPv6) address.
*
* @throws IllegalArgumentException if the current address is not an IPv4 address.
*/
public byte[] getIPv4Address() {
if (getType() != IPV4 && getType() != IPV4MAC) {
throw new IllegalArgumentException("Address is not an IPv4 or IPv4-mapped IPv6 address");
}
byte[] buf = new byte[4];
// Copy lower four bytes into return buffer
System.arraycopy(ip, prefix.length, buf, 0, buf.length);
return buf;
}
/**
* Set a 48 bit MAC address into an iPv4-mapped IPv6 address.
*/
public void setMac(byte[] mac) {
if (getType() != IPV4 && getType() != IPV4MAC) {
throw new IllegalArgumentException("Address is not an IPv4 or IPv4-mapped IPv6 address");
}
if (mac.length != 6) {
throw new IllegalArgumentException("Invalid mac length: " + mac.length);
}
// Set initial 8 bits to 1 to indicate IPv4+Mac address
ip[0] = (byte) 0xFF;
// Copy Mac address into bytes 4-9
System.arraycopy(mac, 0, ip, 4, mac.length);
type = IPV4MAC;
}
/**
* Set a 48 bit MAC address into an iPv4-mapped IPv6 address. This must be called AFTER an IPv4 address is set.
*/
public byte[] getMac() {
if (getType() != IPV4MAC) {
throw new IllegalArgumentException();
}
byte[] mac = new byte[6];
// Copy Mac address into bytes 4-9
System.arraycopy(ip, 4, mac, 0, mac.length);
return mac;
}
/**
* Get the IP address as an IPv4 integer address.
*
* @return The integer IP address if the current address is an IPv4 address or an IPv4-mapped IPv6 address.
*
* @throws IllegalArgumentException if the current address is not an IPv4 address.
*/
public int getIPv4AddressAsInt() {
if (getType() != IPV4 && getType() != IPV4MAC) {
throw new IllegalArgumentException("Address is not an IPv4 or IPv4-mapped IPv6 address");
}
int address;
// Generate address from last 4 bytes of address buffer
address = ip[15] & 0xFF;
address |= ((ip[14] << 8) & 0xFF00);
address |= ((ip[13] << 16) & 0xFF0000);
address |= ((ip[12] << 24) & 0xFF000000);
return address;
}
/**
* Set the IP address.
*
* @param newip IP address in network byte order. This can be either a 32 bit IPv4 address or a 128 bit IPv6 address.
* Typically you'd use the buffer returned by InetAddress.getAddress(). If newip is null the IP address will be cleared.
*
* @throws IllegalArgumentException
*/
public void setAddress(byte[] newip) {
type = UNKNOWN;
if (newip == null) {
// Clear IP
clear();
} else if (newip.length == IPV4_SIZE) {
// IPv4 address. Map to IPv6
System.arraycopy(prefix, 0, ip, 0, prefix.length);
System.arraycopy(newip, 0, ip, prefix.length, newip.length);
type = IPV4;
} else if (newip.length == IPV6_SIZE) {
// IPv6 address. Use it.
System.arraycopy(newip, 0, ip, 0, newip.length);
type = IPV6;
} else {
throw new IllegalArgumentException("Bad IP address length: " + newip.length + ". Should be " + IPV4_SIZE + " or " + IPV6_SIZE + " bytes");
}
}
/**
* Write the raw IP address to the specified DataOutput. The address will be written as a 128 bit IPv6 or IPv4-mapped
* IPv6 address
*
* @param out DataOutput to write address to
*/
public void writeAddress(DataOutput out) throws IOException {
out.write(ip);
}
/**
* Read the raw IP address from the specified DataInput. The IP address is assumed to a 128 bit IPv6 address or
* IPv4-mapped IPv6 address (i.e. we are going to read 128 bits)
*
* @param in DataInput to read address from
*/
public void readAddress(DataInput in) throws IOException {
type = UNKNOWN;
in.readFully(ip);
}
/**
*
* Check if the passed address is an IPv4-mapped IPv6 address. <BR>
* IPv6 defines an address that holds an embedded IPv4 address. This is called an "IPv4-mapped IPv6 address". It is
* specified by a prefix of 80 0 bits and 16 1 bits. The last 32 bits are the IPv4 address. This method checks if the
* passed raw IP address is an IPv4-mapped address.
*
* @param addr Raw IPv6 address (128 bits)
*
* @return true If addr is an IPv4-mapped IPv6 address
*
*/
public static boolean isIPv4Mapped(byte[] addr) {
if (addr.length == IPV6_SIZE) {
// Check if prefixed by IPv4-mapped prefix
for (int n = 0; n < prefix.length; n++) {
if (addr[n] != prefix[n]) {
return false;
}
}
return true;
} else {
return false;
}
}
/**
*
* Check if the passed address is an iMQ IPv4+MAC address. <BR>
* iMQ defines an alternate format used to make the IPv4 address more unique. We basically bake a 40 bit MAC address (or
* psuedo MAC address) into the upper portion of the 128 bits. The format is 8 1 bits followed by 24 0 bits followed by
* the 48 bit MAC address followed by 16 1 bits followed by the 32 bit IPv4 address.
*
* @param addr Raw IPv6 address (128 bits)
*
* @return true If addr is an iMQ IPv4+MAC address
*
*/
public static boolean isIPv4Mac(byte[] addr) {
if (addr.length == IPV6_SIZE) {
// Check if prefixed by IPv4-mapped prefix
for (int n = 0; n < IPv4MacPrefix.length; n++) {
if (addr[n] != IPv4MacPrefix[n]) {
return false;
}
}
return true;
} else {
return false;
}
}
@Override
public String toString() {
return rawIPToString(ip, true, false);
}
/**
* Converts a raw IP address to a readable String.
*/
public static String rawIPToString(byte[] addr) {
return rawIPToString(addr, true, false);
}
/**
* Converts a raw IP address to a readable String.
*/
public static String rawIPToString(byte[] addr, boolean useDelim) {
return rawIPToString(addr, useDelim, false);
}
/**
* parses a rawIPToString back into an IP string MUST have been written with a delim.
*/
public static IPAddress readFromString(String str) {
/*
* "%d.%d.%d.%d".<br> If IPv6 address then it will be in colon seperated Hex: "%x:%x:%x:%x:%x:%x:%x:%x"<br> If iMQ
* IPv4+MAC it will be the IPv4 address followed by the mac address "%d.%d.%d.%d(%x:%x:%x:%x:%x:%x)"
*/
// OK - I should really do this with a regex
int indx = str.indexOf("(");
String macAddr = null;
byte addr[] = null;
if (indx != -1) {
String ips = str.substring(0, indx);
macAddr = str.substring(indx + 1, str.length() - 1);
String ip4[] = ips.split("\\.");
addr = new byte[4];
for (int i = 0; i < 4; i++) {
addr[i] = (byte) Integer.parseInt(ip4[i]);
}
} else {
indx = str.indexOf(":");
if (indx != -1) {
String ip6[] = str.split(":");
addr = new byte[16];
int val = 0;
for (int i = 0, j = 0; i < 8; i++) {
val = Integer.parseInt(ip6[i], 16);
addr[j++] = (byte) ((val >> 8) & 0xFF);
addr[j++] = (byte) (val & 0xFF);
}
} else {
// IPv4
String ip4[] = str.split("\\.");
addr = new byte[4];
for (int i = 0; i < 4; i++) {
addr[i] = (byte) Integer.parseInt(ip4[i]);
}
}
}
IPAddress ip = new IPAddress();
ip.setAddress(addr);
if (macAddr != null) {
String macbyte[] = macAddr.split("\\:");
byte[] mac = new byte[macbyte.length];
for (int i = 0; i < macbyte.length; i++) {
mac[i] = (byte) Integer.parseInt(macbyte[i].toUpperCase(), 16);
}
ip.setMac(mac);
}
return ip;
}
/**
* Converts a raw IP address to a String.
*
* @param addr Raw IP address in network byte order. May be either a 4 byte IPv4 address or a 16 byte IPv6 address.
* @param useDelim By default the returned string will use delimeters (. or :) to make the address more readable. If
* "useDelim" is false then the delimiters are not used which makes for a more compact string.
* @param hideMac True to NOT include the Mac portion of an IPV4+MAC address in the returned string. Otherwise the MAc
* address is included.
*
* @return The string representation of the address. If IPv4 address or IPv4-mapped IPv6 address then it will be in
* dotted-decimal format: "%d.%d.%d.%d".<br>
* If IPv6 address then it will be in colon seperated Hex: "%x:%x:%x:%x:%x:%x:%x:%x"<br>
* If iMQ IPv4+MAC it will be the IPv4 address followed by the mac address "%d.%d.%d.%d(%x:%x:%x:%x:%x:%x)" <br>
* Will return null if addr buf is not 4 or 16 bytes.
*/
public static String rawIPToString(byte[] addr, boolean useDelim, boolean hideMac) {
String DOT = ".";
String COLON = ":";
String OPENP = "(";
String CLOSEP = ")";
if (!useDelim) {
DOT = "";
COLON = "";
OPENP = "";
CLOSEP = "";
}
/*
* We mask with 0xFF to mask the extension of the sign bit that would occur when the byte is promoted to an int before
* conversion to a string
*/
if (addr == null) {
return "0.0.0.0";
} else if (addr.length == IPV4_SIZE) {
// IPv4 address. Use dotted decimal
return ((addr[0] & 0xFF)) + DOT + ((addr[1] & 0xFF)) + DOT + ((addr[2] & 0xFF)) + DOT + ((addr[3] & 0xFF));
} else if (isIPv4Mapped(addr) || (hideMac && isIPv4Mac(addr))) {
// IPv4-mapped IPv6 address. Use dotted decimal on lower 4 bytes
return ((addr[12] & 0xFF)) + DOT + ((addr[13] & 0xFF)) + DOT + ((addr[14] & 0xFF)) + DOT + ((addr[15] & 0xFF));
} else if (isIPv4Mac(addr)) {
// Colon hex for the Mac address, dotted decimal for IPv4
return ((addr[12] & 0xFF)) + DOT + ((addr[13] & 0xFF)) + DOT + ((addr[14] & 0xFF)) + DOT + ((addr[15] & 0xFF)) + OPENP
+ Integer.toHexString((addr[4] & 0xFF)) + COLON + Integer.toHexString((addr[5] & 0xFF)) + COLON + Integer.toHexString((addr[6] & 0xFF))
+ COLON + Integer.toHexString((addr[7] & 0xFF)) + COLON + Integer.toHexString((addr[8] & 0xFF)) + COLON
+ Integer.toHexString((addr[9] & 0xFF)) + CLOSEP;
} else if (addr.length == IPV6_SIZE) {
// IPv6 address. Use colon seperated hex
return Bits.toHexString(addr, useDelim);
} else {
return null;
}
}
/**
* Generate a 40 bit psuedo MAC node address for the node we are running on. The JDK provides no way to get the hardware
* MAC address for the system we are running on (see JDK RFE so we generate a random one.
*
*/
public static byte[] getRandomMac() {
byte[] nodebuf = Bits.randomBits(6);
// Make sure the upper most bit is on. This is the multicast
// bit on MAC addresses and ensures no clash with true hardware
// addresses.
nodebuf[0] = (byte) (nodebuf[0] | (byte) 0x80);
return nodebuf;
}
public static void main(String args[]) {
System.out.println(Bits.toHexString(getRandomMac(), true));
System.out.println(Bits.toHexString(getRandomMac(), true));
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
}
IPAddress ip = new IPAddress();
ip.setAddress(inet.getAddress());
System.out.println("IPv4: " + ip);
ip.setMac(getRandomMac());
System.out.println("IPv4Mac: " + ip);
}
}