mirror of https://github.com/jitsi/jitsi-meet
Adds Nat64InfoModule which resolves IPv6 addresses for IPv4 addresses in IPv6 only network where jitsi-meet deployment does not provide any IPv6 addresses as ICE candidates.pull/2677/head jitsi-meet_2924
parent
0456df239f
commit
968b279b37
@ -0,0 +1,238 @@ |
||||
/* |
||||
* Copyright @ 2018-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.jitsi.meet.sdk.net; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
|
||||
/** |
||||
* Constructs IPv6 addresses for IPv4 addresses in the NAT64 environment. |
||||
* |
||||
* NAT64 translates IPv4 to IPv6 addresses by adding "well known" prefix and |
||||
* suffix configured by the administrator. Those are figured out by discovering |
||||
* both IPv6 and IPv4 addresses of a host and then trying to find a place where |
||||
* the IPv4 address fits into the format described here: |
||||
* https://tools.ietf.org/html/rfc6052#section-2.2
|
||||
*/ |
||||
public class NAT64AddrInfo { |
||||
/** |
||||
* Coverts bytes array to upper case HEX string. |
||||
* |
||||
* @param bytes an array of bytes to be converted |
||||
* @return ex. "010AFF" for an array of {1, 10, 255}. |
||||
*/ |
||||
static String bytesToHexString(byte[] bytes) { |
||||
StringBuilder hexStr = new StringBuilder(); |
||||
|
||||
for (byte b : bytes) { |
||||
hexStr.append(String.format("%02X", b)); |
||||
} |
||||
|
||||
return hexStr.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Tries to discover the NAT64 prefix/suffix based on the IPv4 and IPv6 |
||||
* addresses resolved for given {@code host}. |
||||
* |
||||
* @param host the host for which the code will try to discover IPv4 and |
||||
* IPv6 addresses which then will be used to figure out the NAT64 prefix. |
||||
* @return {@link NAT64AddrInfo} instance if the NAT64 prefix/suffix was |
||||
* successfully discovered or {@code null} if it failed for any reason. |
||||
* @throws UnknownHostException thrown by {@link InetAddress#getAllByName}. |
||||
*/ |
||||
public static NAT64AddrInfo discover(String host) |
||||
throws UnknownHostException { |
||||
InetAddress ipv4 = null; |
||||
InetAddress ipv6 = null; |
||||
|
||||
for(InetAddress addr : InetAddress.getAllByName(host)) { |
||||
byte[] bytes = addr.getAddress(); |
||||
|
||||
if (bytes.length == 4) { |
||||
ipv4 = addr; |
||||
} else if (bytes.length == 16) { |
||||
ipv6 = addr; |
||||
} |
||||
} |
||||
|
||||
if (ipv4 != null && ipv6 != null) { |
||||
return figureOutNAT64AddrInfo(ipv4.getAddress(), ipv6.getAddress()); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Based on IPv4 and IPv6 addresses of the same host, the method will make |
||||
* an attempt to figure out what are the NAT64 prefix and suffix. |
||||
* |
||||
* @param ipv4AddrBytes the IPv4 address of the same host in NAT64 network, |
||||
* as returned by {@link InetAddress#getAddress()}. |
||||
* @param ipv6AddrBytes the IPv6 address of the same host in NAT64 network, |
||||
* as returned by {@link InetAddress#getAddress()}. |
||||
* @return {@link NAT64AddrInfo} instance which contains the prefix/suffix |
||||
* of the current NAT64 network or {@code null} if the prefix could not be |
||||
* found. |
||||
*/ |
||||
static NAT64AddrInfo figureOutNAT64AddrInfo( |
||||
byte[] ipv4AddrBytes, |
||||
byte[] ipv6AddrBytes) { |
||||
String ipv6Str = bytesToHexString(ipv6AddrBytes); |
||||
String ipv4Str = bytesToHexString(ipv4AddrBytes); |
||||
|
||||
// NAT64 address format:
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |32| prefix |v4(32) | u | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |40| prefix |v4(24) | u |(8)| suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |48| prefix |v4(16) | u | (16) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |56| prefix |(8)| u | v4(24) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |64| prefix | u | v4(32) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |96| prefix | v4(32) |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
int prefixLength = 96; |
||||
int suffixLength = 0; |
||||
String prefix = null; |
||||
String suffix = null; |
||||
|
||||
if (ipv4Str.equalsIgnoreCase(ipv6Str.substring(prefixLength / 4))) { |
||||
prefix = ipv6Str.substring(0, prefixLength / 4); |
||||
} else { |
||||
// Cut out the 'u' octet
|
||||
ipv6Str = ipv6Str.substring(0, 16) + ipv6Str.substring(18); |
||||
|
||||
for (prefixLength = 64, suffixLength = 6; prefixLength >= 32; ) { |
||||
if (ipv4Str.equalsIgnoreCase( |
||||
ipv6Str.substring( |
||||
prefixLength / 4, prefixLength / 4 + 8))) { |
||||
prefix = ipv6Str.substring(0, prefixLength / 4); |
||||
suffix = ipv6Str.substring(ipv6Str.length() - suffixLength); |
||||
break; |
||||
} |
||||
|
||||
prefixLength -= 8; |
||||
suffixLength += 2; |
||||
} |
||||
} |
||||
|
||||
return prefix != null ? new NAT64AddrInfo(prefix, suffix) : null; |
||||
} |
||||
|
||||
/** |
||||
* An overload for {@link #hexStringToIPv6String(StringBuilder)}. |
||||
* |
||||
* @param hexStr a hex representation of IPv6 address bytes. |
||||
* @return an IPv6 address string. |
||||
*/ |
||||
static String hexStringToIPv6String(String hexStr) { |
||||
return hexStringToIPv6String(new StringBuilder(hexStr)); |
||||
} |
||||
|
||||
/** |
||||
* Converts from HEX representation of IPv6 address bytes into IPv6 address |
||||
* string which includes the ':' signs. |
||||
* |
||||
* @param str a hex representation of IPv6 address bytes. |
||||
* @return eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C |
||||
*/ |
||||
static String hexStringToIPv6String(StringBuilder str) { |
||||
for (int i = 32 - 4; i > 0; i -= 4) { |
||||
str.insert(i, ":"); |
||||
} |
||||
|
||||
return str.toString().toUpperCase(); |
||||
} |
||||
|
||||
/** |
||||
* Parses an IPv4 address string and returns it's byte array representation. |
||||
* |
||||
* @param ipv4Address eg. '192.168.3.23' |
||||
* @return byte representation of given IPv4 address string. |
||||
* @throws IllegalArgumentException if the address is not in valid format. |
||||
*/ |
||||
static byte[] ipv4AddressStringToBytes(String ipv4Address) { |
||||
InetAddress address; |
||||
|
||||
try { |
||||
address = InetAddress.getByName(ipv4Address); |
||||
} catch (UnknownHostException e) { |
||||
throw new IllegalArgumentException( |
||||
"Invalid IP address: " + ipv4Address, e); |
||||
} |
||||
|
||||
byte[] bytes = address.getAddress(); |
||||
|
||||
if (bytes.length != 4) { |
||||
throw new IllegalArgumentException( |
||||
"Not an IPv4 address: " + ipv4Address); |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
/** |
||||
* The NAT64 prefix added to construct IPv6 from an IPv4 address. |
||||
*/ |
||||
private final String prefix; |
||||
|
||||
/** |
||||
* The NAT64 suffix (if any) used to construct IPv6 from an IPv4 address. |
||||
*/ |
||||
private final String suffix; |
||||
|
||||
/** |
||||
* Creates new instance of {@link NAT64AddrInfo}. |
||||
* |
||||
* @param prefix the NAT64 prefix. |
||||
* @param suffix the NAT64 suffix. |
||||
*/ |
||||
private NAT64AddrInfo(String prefix, String suffix) { |
||||
this.prefix = prefix; |
||||
this.suffix = suffix; |
||||
} |
||||
|
||||
/** |
||||
* Based on the NAT64 prefix and suffix will create an IPv6 representation |
||||
* of the given IPv4 address. |
||||
* |
||||
* @param ipv4Address eg. '192.34.2.3' |
||||
* @return IPv6 address string eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C |
||||
* @throws IllegalArgumentException if given string is not a valid IPv4 |
||||
* address. |
||||
*/ |
||||
public String getIPv6Address(String ipv4Address) { |
||||
byte[] ipv4AddressBytes = ipv4AddressStringToBytes(ipv4Address); |
||||
StringBuilder newIPv6Str = new StringBuilder(); |
||||
|
||||
newIPv6Str.append(prefix); |
||||
newIPv6Str.append(bytesToHexString(ipv4AddressBytes)); |
||||
|
||||
if (suffix != null) { |
||||
// Insert the 'u' octet.
|
||||
newIPv6Str.insert(16, "00"); |
||||
newIPv6Str.append(suffix); |
||||
} |
||||
|
||||
return hexStringToIPv6String(newIPv6Str); |
||||
} |
||||
} |
@ -0,0 +1,122 @@ |
||||
/* |
||||
* Copyright @ 2018-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.jitsi.meet.sdk.net; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import com.facebook.react.bridge.Promise; |
||||
import com.facebook.react.bridge.ReactApplicationContext; |
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule; |
||||
import com.facebook.react.bridge.ReactMethod; |
||||
|
||||
import java.net.UnknownHostException; |
||||
|
||||
/** |
||||
* This module exposes the functionality of creating an IPv6 representation |
||||
* of IPv4 addresses in NAT64 environment. |
||||
* |
||||
* See[1] and [2] for more info on what NAT64 is. |
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/ |
||||
public class NAT64AddrInfoModule extends ReactContextBaseJavaModule { |
||||
/** |
||||
* The host for which the module wil try to resolve both IPv4 and IPv6 |
||||
* addresses in order to figure out the NAT64 prefix. |
||||
*/ |
||||
private final static String HOST = "nat64.jitsi.net"; |
||||
|
||||
/** |
||||
* How long is the {@link NAT64AddrInfo} instance valid. |
||||
*/ |
||||
private final static long INFO_LIFETIME = 60 * 1000; |
||||
|
||||
/** |
||||
* The name of this module. |
||||
*/ |
||||
private final static String MODULE_NAME = "NAT64AddrInfo"; |
||||
|
||||
/** |
||||
* The {@code Log} tag {@code NAT64AddrInfoModule} is to log messages with. |
||||
*/ |
||||
private final static String TAG = MODULE_NAME; |
||||
|
||||
/** |
||||
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix. |
||||
*/ |
||||
private NAT64AddrInfo info; |
||||
|
||||
/** |
||||
* When {@link #info} was created. |
||||
*/ |
||||
private long infoTimestamp; |
||||
|
||||
/** |
||||
* Creates new {@link NAT64AddrInfoModule}. |
||||
* |
||||
* @param reactContext the react context to be used by the new module |
||||
* instance. |
||||
*/ |
||||
public NAT64AddrInfoModule(ReactApplicationContext reactContext) { |
||||
super(reactContext); |
||||
} |
||||
|
||||
/** |
||||
* Tries to obtain IPv6 address for given IPv4 address in NAT64 environment. |
||||
* |
||||
* @param ipv4Address IPv4 address string. |
||||
* @param promise a {@link Promise} which will be resolved either with IPv6 |
||||
* address for given IPv4 address or with {@code null} if no |
||||
* {@link NAT64AddrInfo} was resolved for the current network. Will be |
||||
* rejected if given {@code ipv4Address} is not a valid IPv4 address. |
||||
*/ |
||||
@ReactMethod |
||||
public void getIPv6Address(String ipv4Address, final Promise promise) { |
||||
// Reset if cached for too long.
|
||||
if (System.currentTimeMillis() - infoTimestamp > INFO_LIFETIME) { |
||||
info = null; |
||||
} |
||||
|
||||
if (info == null) { |
||||
String host = HOST; |
||||
|
||||
try { |
||||
info = NAT64AddrInfo.discover(host); |
||||
} catch (UnknownHostException e) { |
||||
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e); |
||||
} |
||||
infoTimestamp = System.currentTimeMillis(); |
||||
} |
||||
|
||||
String result; |
||||
|
||||
try { |
||||
result = info == null ? null : info.getIPv6Address(ipv4Address); |
||||
} catch (IllegalArgumentException exc) { |
||||
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc); |
||||
|
||||
// We don't want to reject. It's not a big deal if there's no IPv6
|
||||
// address resolved.
|
||||
result = null; |
||||
} |
||||
promise.resolve(result); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return MODULE_NAME; |
||||
} |
||||
} |
@ -0,0 +1,150 @@ |
||||
/* |
||||
* Copyright @ 2017-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.jitsi.meet.sdk.net; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.net.UnknownHostException; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* Tests for {@link NAT64AddrInfo} class. |
||||
*/ |
||||
public class NAT64AddrInfoTest { |
||||
/** |
||||
* Test case for the 96 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test96Prefix() { |
||||
testPrefixSuffix( |
||||
"260777000000000400000000", "", "203.0.113.1", "23.17.23.3"); |
||||
} |
||||
|
||||
/** |
||||
* Test case for the 64 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test64Prefix() { |
||||
String prefix = "1FF2A227B3AAF3D2"; |
||||
String suffix = "BB87C8"; |
||||
|
||||
testPrefixSuffix(prefix, suffix, "48.46.87.34", "23.87.145.4"); |
||||
} |
||||
|
||||
/** |
||||
* Test case for the 56 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test56Prefix() { |
||||
String prefix = "1FF2A227B3AAF3"; |
||||
String suffix = "A2BB87C8"; |
||||
|
||||
testPrefixSuffix(prefix, suffix, "34.72.234.255", "1.235.3.65"); |
||||
} |
||||
|
||||
/** |
||||
* Test case for the 48 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test48Prefix() { |
||||
String prefix = "1FF2A227B3AA"; |
||||
String suffix = "72A2BB87C8"; |
||||
|
||||
testPrefixSuffix(prefix, suffix, "97.54.3.23", "77.49.0.33"); |
||||
} |
||||
|
||||
/** |
||||
* Test case for the 40 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test40Prefix() { |
||||
String prefix = "1FF2A227B3"; |
||||
String suffix = "D972A2BB87C8"; |
||||
|
||||
testPrefixSuffix(prefix, suffix, "10.23.56.121", "97.65.32.21"); |
||||
} |
||||
|
||||
/** |
||||
* Test case for the 32 prefix length. |
||||
*/ |
||||
@Test |
||||
public void test32Prefix() |
||||
throws UnknownHostException { |
||||
String prefix = "1FF2A227"; |
||||
String suffix = "20D972A2BB87C8"; |
||||
|
||||
testPrefixSuffix(prefix, suffix, "162.63.65.189", "135.222.84.206"); |
||||
} |
||||
|
||||
private static String buildIPv6Addr( |
||||
String prefix, String suffix, String ipv4Hex) { |
||||
String ipv6Str = prefix + ipv4Hex + suffix; |
||||
|
||||
if (suffix.length() > 0) { |
||||
ipv6Str = new StringBuilder(ipv6Str).insert(16, "00").toString(); |
||||
} |
||||
|
||||
return ipv6Str; |
||||
} |
||||
|
||||
private void testPrefixSuffix( |
||||
String prefix, String suffix, String ipv4, String otherIPv4) { |
||||
byte[] ipv4Bytes = NAT64AddrInfo.ipv4AddressStringToBytes(ipv4); |
||||
String ipv4String = NAT64AddrInfo.bytesToHexString(ipv4Bytes); |
||||
String ipv6Str = buildIPv6Addr(prefix, suffix, ipv4String); |
||||
|
||||
BigInteger ipv6Address = new BigInteger(ipv6Str, 16); |
||||
|
||||
NAT64AddrInfo nat64AddrInfo |
||||
= NAT64AddrInfo.figureOutNAT64AddrInfo( |
||||
ipv4Bytes, ipv6Address.toByteArray()); |
||||
|
||||
assertNotNull("Failed to figure out NAT64 info", nat64AddrInfo); |
||||
|
||||
String newIPv6 = nat64AddrInfo.getIPv6Address(ipv4); |
||||
|
||||
assertEquals( |
||||
NAT64AddrInfo.hexStringToIPv6String(ipv6Address.toString(16)), |
||||
newIPv6); |
||||
|
||||
byte[] ipv4Addr2 = NAT64AddrInfo.ipv4AddressStringToBytes(otherIPv4); |
||||
String ipv4Addr2Hex = NAT64AddrInfo.bytesToHexString(ipv4Addr2); |
||||
|
||||
newIPv6 = nat64AddrInfo.getIPv6Address(otherIPv4); |
||||
|
||||
assertEquals( |
||||
NAT64AddrInfo.hexStringToIPv6String( |
||||
buildIPv6Addr(prefix, suffix, ipv4Addr2Hex)), |
||||
newIPv6); |
||||
} |
||||
|
||||
@Test |
||||
public void testInvalidIPv4Format() { |
||||
testInvalidIPv4Format("256.1.2.3"); |
||||
testInvalidIPv4Format("FE80:CD00:0000:0CDA:1357:0000:212F:749C"); |
||||
} |
||||
|
||||
private void testInvalidIPv4Format(String ipv4Str) { |
||||
try { |
||||
NAT64AddrInfo.ipv4AddressStringToBytes(ipv4Str); |
||||
fail("Did not throw IllegalArgumentException"); |
||||
} catch (IllegalArgumentException exc) { |
||||
/* OK */ |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue