Skip to content

Commit

Permalink
[feature/nro-profile] Add reverse domain check (fixes #27).
Browse files Browse the repository at this point in the history
  • Loading branch information
tomhrr committed Feb 20, 2024
1 parent 123b080 commit 5c2a5a7
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 1 deletion.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -207,6 +207,12 @@
<version>4.1</version>
</dependency>

<dependency>
<groupId>net.ripe.ipresource</groupId>
<artifactId>ipresource</artifactId>
<version>1.46</version>
</dependency>

<!-- Testing -->
<dependency>
<groupId>org.testng</groupId>
Expand Down
110 changes: 110 additions & 0 deletions src/main/java/net/apnic/rdap/conformance/Utils.java
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import com.google.gson.Gson;
import com.google.common.util.concurrent.ListenableFuture;
Expand Down Expand Up @@ -528,4 +529,113 @@ public static boolean matchesSearch(final String strPattern,
| Pattern.UNICODE_CASE);
return pattern.matcher(value).matches();
}

/**
* <p>ipv4ArpaToPrefix</p>
*
* Converts an IPv4 reverse domain name into an address prefix.
*
* @param str The reverse domain name.
* @return The address prefix for the domain name.
*/
private static String ipv4ArpaToPrefix(final String str) {
Pattern pattern = Pattern.compile("([0-9\\.]+)\\.in-addr\\.arpa");
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
String numbers = matcher.group(1);
String[] numberList = numbers.split("\\.");
int prefixLength = numberList.length * 8;
int zeroes = (32 - prefixLength) / 8;
if (zeroes < 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = numberList.length - 1; i >= 0; i--) {
sb.append(numberList[i]);
if (!(numberList.length == 4 && i == 0)) {
sb.append(".");
}
}
while (zeroes-- > 0) {
sb.append("0");
if (zeroes > 0) {
sb.append(".");
}
}
sb.append("/");
sb.append(prefixLength);
return sb.toString();
} else {
return "";
}
}

/**
* <p>ipv6ArpaToPrefix</p>
*
* Converts an IPv6 reverse domain name into an address prefix.
*
* @param str The reverse domain name.
* @return The address prefix for the domain name.
*/
private static String ipv6ArpaToPrefix(final String str) {
// my ($nums) = ($arpa =~ /^(.*)\.ip6\.arpa/i);
// $nums =~ s/\.//g;
// my $len = (length $nums);
// my $prefix_len = $len * 4;
// $nums = reverse $nums;
// $nums .= '0' x (4 - (($len % 4) || 4));
// my $addr = join ':', ($nums =~ /(.{4})/g);
// if ((length $addr) < 39) {
// $addr .= '::';
// }
// $addr .= '/'.$prefix_len;
// return $addr;

Pattern pattern = Pattern.compile("([0-9A-Fa-f\\.]+)\\.ip6\\.arpa");
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
String numbers = matcher.group(1);
numbers = numbers.replaceAll("\\.", "");
int len = numbers.length();
int prefixLength = len * 4;
StringBuilder numberSb =
new StringBuilder(numbers).reverse();
int subtract = len % 4;
if (subtract == 0) {
subtract = 4;
}
int zeroes = 4 - subtract;
while (zeroes-- > 0) {
numberSb.append("0");
}
String[] segments =
numberSb.toString().split("(?<=\\G.{4})");
String result = String.join(":", segments);
if (result.length() < 39) {
result = result + "::";
}
result = result + "/" + prefixLength;
return result.toLowerCase();
} else {
return "";
}
}

/**
* <p>arpaToPrefix</p>
*
* Converts a reverse domain name into an address prefix. If the
* domain name is invalid, this will return an empty string.
*
* @param str The reverse domain name.
* @return The address prefix for the domain name.
*/
public static String arpaToPrefix(final String str) {
if (str.contains(".in-addr.arpa")) {
return ipv4ArpaToPrefix(str);
} else {
return ipv6ArpaToPrefix(str);
}
}
}
Expand Up @@ -11,6 +11,7 @@
import net.apnic.rdap.conformance.Utils;
import net.apnic.rdap.conformance.valuetest.Variant;
import net.apnic.rdap.conformance.valuetest.StringTest;
import net.apnic.rdap.conformance.valuetest.ReverseDomainMatch;

/**
* <p>Domain class.</p>
Expand Down Expand Up @@ -48,7 +49,7 @@ public boolean run(final Context context, final Result proto,
domainNames.setSearchDetails(key, pattern);
}

return Utils.runTestList(
boolean res = Utils.runTestList(
context, proto, data, knownAttributes, checkUnknown,
Arrays.asList(
new ScalarAttribute("objectClassName",
Expand All @@ -63,6 +64,11 @@ public boolean run(final Context context, final Result proto,
new StandardObject()
)
);

ReverseDomainMatch rdmTest = new ReverseDomainMatch();
boolean res2 = rdmTest.run(context, proto, data);

return res && res2;
}

/**
Expand Down
@@ -0,0 +1,114 @@
package net.apnic.rdap.conformance.valuetest;

import net.apnic.rdap.conformance.Result;
import net.apnic.rdap.conformance.Context;
import net.apnic.rdap.conformance.ValueTest;
import net.apnic.rdap.conformance.Utils;

import net.ripe.ipresource.IpResource;

import java.util.*;

/**
* <p>ReverseDomainMatch class.</p>
*
* See RFC 9083 [5.3].
*
* @author Tom Harrison <tomh@apnic.net>
* @version 0.7-SNAPSHOT
*/
public final class ReverseDomainMatch implements ValueTest {
/**
* <p>Constructor for ReverseDomainMatch.</p>
*/
public ReverseDomainMatch() { }

/** {@inheritDoc} */
public boolean run(final Context context, final Result proto,
final Object argData) {
Map<String, Object> object =
Utils.castToMap(context, proto, argData);
if (object == null) {
return false;
}

/* Get the domain name for this object. */
String domainName = Utils.castToString(object.get("ldhName"));
if (domainName == null) {
return true;
}
if (!domainName.matches(".*\\.arpa\\.?")) {
return true;
}
String prefix = Utils.arpaToPrefix(domainName);
if (prefix.equals("")) {
Result res = new Result(proto);
res.setStatus(Result.Status.Failure);
res.setInfo("invalid reverse domain name");
context.addResult(res);
return false;
}

IpResource domainIp;
try {
domainIp = IpResource.parse(prefix);
} catch (Exception e) {
Result res = new Result(proto);
res.setStatus(Result.Status.Failure);
res.setInfo("invalid reverse domain name");
context.addResult(res);
return false;
}

/* Get the network for this object (if present). */
Map<String, Object> networkObject =
Utils.castToMap(context, proto, object.get("network"));
if (networkObject == null) {
return true;
}

String startAddress =
Utils.castToString(networkObject.get("startAddress"));
if (startAddress == null) {
Result res = new Result(proto);
res.setStatus(Result.Status.Failure);
res.setInfo("network does not contain start address");
context.addResult(res);
return false;
}
String endAddress =
Utils.castToString(networkObject.get("endAddress"));
if (endAddress == null) {
Result res = new Result(proto);
res.setStatus(Result.Status.Failure);
res.setInfo("network does not contain end address");
context.addResult(res);
return false;
}

IpResource networkIp;
try {
networkIp = IpResource.parse(startAddress + "-" +
endAddress);
} catch (Exception e) {
Result res = new Result(proto);
res.setStatus(Result.Status.Failure);
res.setInfo("invalid network start/end address");
context.addResult(res);
return false;
}

/* Confirm that the domain range is contained within the
* network. */
Result res = new Result(proto);
boolean contains = networkIp.contains(domainIp);
if (contains) {
res.setStatus(Result.Status.Success);
} else {
res.setStatus(Result.Status.Failure);
}
res.setInfo("network range contains domain range");
context.addResult(res);
return contains;
}
}
72 changes: 72 additions & 0 deletions src/test/java/net/apnic/rdap/conformance/UtilsTest.java
@@ -0,0 +1,72 @@
package net.apnic.rdap.conformance;

import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertNull;

import net.apnic.rdap.conformance.Utils;
import java.io.*;

public class UtilsTest
{
public UtilsTest()
{
}

@Test
public void testIpv4ArpaToPrefix() throws Exception
{
String s;

s = Utils.arpaToPrefix("in-addr.arpa");
assertEquals(s, "",
"Got empty string for invalid domain");

s = Utils.arpaToPrefix("1.in-addr.arpa");
assertEquals(s, "1.0.0.0/8",
"Got correct prefix for a /8");

s = Utils.arpaToPrefix("123.123.in-addr.arpa");
assertEquals(s, "123.123.0.0/16",
"Got correct prefix for a /16");

s = Utils.arpaToPrefix("1.2.3.in-addr.arpa");
assertEquals(s, "3.2.1.0/24",
"Got correct prefix for a /24");

s = Utils.arpaToPrefix("4.3.2.1.in-addr.arpa");
assertEquals(s, "1.2.3.4/32",
"Got correct prefix for a /32");

s = Utils.arpaToPrefix("5.4.3.2.1.in-addr.arpa");
assertEquals(s, "",
"Got empty string for invalid domain (too many segments)");

s = Utils.arpaToPrefix("a.b.c.in-addr.arpa");
assertEquals(s, "",
"Got empty string for invalid domain (contains letters)");
}

@Test
public void testIpv6ArpaToPrefix() throws Exception
{
String s;

s = Utils.arpaToPrefix("ip6.arpa");
assertEquals(s, "",
"Got empty string for invalid domain");

s = Utils.arpaToPrefix("a.ip6.arpa");
assertEquals(s, "a000::/4",
"Got correct prefix for a /4");

s = Utils.arpaToPrefix("a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.ip6.arpa");
assertEquals(s, "dcba:dcba:dcba:dcba:dcba:dcba:dcba:dcba/128",
"Got correct prefix for a /128");

s = Utils.arpaToPrefix("a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.d.a.b.c.ip6.arpa");
assertEquals(s, "cbad:cbad:cbad:cbad:cbad:cbad:cbad:cba0/124",
"Got correct prefix for a /124");
}
}

0 comments on commit 5c2a5a7

Please sign in to comment.