Browse Source

Implement sync from NEONETWORK (disabled) and general cleanup

jrb0001 1 year ago
parent
commit
26fb6dbe97

+ 17 - 2
pom.xml

@@ -26,7 +26,22 @@
         <dependency>
             <groupId>com.github.seancfoley</groupId>
             <artifactId>ipaddress</artifactId>
-            <version>5.0.2</version>
+            <version>5.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.json.bind</groupId>
+            <artifactId>javax.json.bind-api</artifactId>
+            <version>1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse</groupId>
+            <artifactId>yasson</artifactId>
+            <version>1.0.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>javax.json</artifactId>
+            <version>1.1.4</version>
         </dependency>
     </dependencies>
 
@@ -56,4 +71,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
+</project>

+ 6 - 9
src/main/java/dn42/registry/sync/Main.java

@@ -22,6 +22,7 @@ import com.beust.jcommander.ParameterException;
 import dn42.registry.sync.converters.ChaosvpnRegistry;
 import dn42.registry.sync.converters.Dn42Registry;
 import dn42.registry.sync.converters.IcvpnRegistry;
+import dn42.registry.sync.converters.NeonetworkRegistry;
 import dn42.registry.sync.data.Attribute;
 import dn42.registry.sync.data.RegistryObject;
 import dn42.registry.sync.utils.Utils;
@@ -82,13 +83,9 @@ public class Main {
     }
 
     private static boolean filterDn42(RegistryObject obj) {
-        return Utils.getValues(obj, "source").isEmpty()
-                || Utils.getValues(obj, "source").contains("DN42")
-                || Utils.getValues(obj, "source").contains("AFRINIC")
-                || Utils.getValues(obj, "source").contains("APNIC")
-                || Utils.getValues(obj, "source").contains("ARIN")
-                || Utils.getValues(obj, "source").contains("LACNIC")
-                || Utils.getValues(obj, "source").contains("RIPE");
+        return !Utils.getValues(obj, "source").contains(IcvpnRegistry.SOURCE)
+                && !Utils.getValues(obj, "source").contains(ChaosvpnRegistry.SOURCE)
+                && !Utils.getValues(obj, "source").contains(NeonetworkRegistry.SOURCE);
     }
 
     private static Stream<RegistryObject> importRegistry(RegistryImporter importer) {
@@ -101,7 +98,7 @@ public class Main {
                 case "inet6num":
                 case "inetnum":
                     List<Attribute> attributes = new ArrayList<>(object.getAttributes());
-                    attributes.set(0, new Attribute(object.getType(), Utils.cidrToRange(Utils.getPrimaryKeyValue(object))));
+                    attributes.set(0, new Attribute(object.getType(), Utils.prefixToRange(Utils.parsePrefixTruncating(Utils.getPrimaryKeyValue(object)))));
                     return new RegistryObject(object.getType(), attributes, object.getFilename());
                 default:
                     return object;
@@ -122,7 +119,7 @@ public class Main {
                                     case "cidr":
                                     case "route6":
                                     case "route":
-                                        return new Attribute(a.getKey(), Utils.cidrToShortCidr(a.getValue()));
+                                        return new Attribute(a.getKey(), Utils.prefixToCompactCidr(Utils.parsePrefixTruncating(a.getValue())));
                                     default:
                                         return a;
                                 }

+ 21 - 17
src/main/java/dn42/registry/sync/converters/ChaosvpnRegistry.java

@@ -22,7 +22,7 @@ import dn42.registry.sync.data.Attribute;
 import dn42.registry.sync.data.RegistryObject;
 import dn42.registry.sync.utils.SimpleSpliterator;
 import dn42.registry.sync.utils.Utils;
-import static dn42.registry.sync.utils.Utils.cidrToRange;
+import inet.ipaddr.IPAddress;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -46,6 +46,8 @@ import java.util.stream.StreamSupport;
  */
 public class ChaosvpnRegistry implements RegistryImporter {
 
+    public static final String SOURCE = "CHAOSVPN";
+
     private static final Pattern NAME_PATTERN = Pattern.compile("^\\s*\\[\\s*(\\S+)\\s*\\]\\s*$");
     private static final Pattern IPV4_PATTERN = Pattern.compile("^\\s*network\\s*=\\s*(\\S+)\\s*$");
     private static final Pattern IPV6_PATTERN = Pattern.compile("^\\s*network6\\s*=\\s*(\\S+)\\s*$");
@@ -96,15 +98,17 @@ public class ChaosvpnRegistry implements RegistryImporter {
                     if (nameMatcher.matches()) {
                         name = nameMatcher.group(1);
                     } else if (ipv4Matcher.matches()) {
-                        if (NamingRestrictions.isIpAllowedForChaosvpn(ipv4Matcher.group(1))) {
-                            action.accept(createInetnum(ipv4Matcher.group(1), name));
+                        IPAddress prefix = Utils.parsePrefixTruncating(ipv4Matcher.group(1));
+                        if (NamingRestrictions.isIpv4AllowedForChaosvpn(prefix)) {
+                            action.accept(createInetnum(prefix, name));
                         }
                     } else if (ipv6Matcher.matches()) {
-                        if (NamingRestrictions.isIpAllowedForChaosvpn(ipv6Matcher.group(1))) {
-                            action.accept(createInetnum(ipv6Matcher.group(1), name));
+                        IPAddress prefix = Utils.parsePrefixTruncating(ipv6Matcher.group(1));
+                        if (NamingRestrictions.isIpv6AllowedForChaosvpn(prefix)) {
+                            action.accept(createInetnum(prefix, name));
                             int length = Integer.parseInt(ipv6Matcher.group(1).split("/")[1]);
-                            if (length >= 32 && length <= 64) {
-                                action.accept(createRoute(ipv6Matcher.group(1), name));
+                            if (length <= 64) {
+                                action.accept(createRoute(prefix, name));
                             }
                         }
                     } else {
@@ -115,24 +119,24 @@ public class ChaosvpnRegistry implements RegistryImporter {
         }, false);
     }
 
-    private RegistryObject createInetnum(String cidr, String name) {
-        String type = cidr.contains(":") ? "inet6num" : "inetnum";
+    private RegistryObject createInetnum(IPAddress prefix, String name) {
+        String type = prefix.isIPv6() ? "inet6num" : "inetnum";
         List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute(type, cidrToRange(cidr)));
-        attributes.add(new Attribute("cidr", Utils.cidrToShortCidr(cidr)));
-        attributes.add(new Attribute("netname", "CHAOSVPN-" + name.toUpperCase().replace("_", "-")));
+        attributes.add(new Attribute(type, Utils.prefixToRange(prefix)));
+        attributes.add(new Attribute("cidr", Utils.prefixToCompactCidr(prefix)));
+        attributes.add(new Attribute("netname", SOURCE + "-" + name.toUpperCase().replace("_", "-")));
         attributes.add(new Attribute("status", "ASSIGNED"));
         addDefaultAttributes(attributes);
         return new RegistryObject(type, attributes, url.toString());
     }
 
-    private RegistryObject createRoute(String cidr, String name) {
-        String type = cidr.contains(":") ? "route6" : "route";
+    private RegistryObject createRoute(IPAddress prefix, String name) {
+        String type = prefix.isIPv6() ? "route6" : "route";
         List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute(type, Utils.cidrToShortCidr(cidr)));
+        attributes.add(new Attribute(type, Utils.prefixToCompactCidr(prefix)));
         attributes.add(new Attribute("max-length", "64"));
         origins.stream().map(origin -> new Attribute("origin", origin)).forEach(attributes::add);
-        attributes.add(new Attribute("descr", "CHAOSVPN-" + name.toUpperCase().replace("_", "-")));
+        attributes.add(new Attribute("descr", SOURCE + "-" + name.toUpperCase().replace("_", "-")));
         addDefaultAttributes(attributes);
         return new RegistryObject(type, attributes, url.toString());
     }
@@ -140,6 +144,6 @@ public class ChaosvpnRegistry implements RegistryImporter {
     private void addDefaultAttributes(List<Attribute> attributes) {
         attributes.add(new Attribute("remarks", "Imported from chaosvpn, do not edit!"));
         attributes.add(new Attribute("mnt-by", "DN42-MNT"));
-        attributes.add(new Attribute("source", "CHAOSVPN"));
+        attributes.add(new Attribute("source", SOURCE));
     }
 }

+ 1 - 1
src/main/java/dn42/registry/sync/converters/Dn42Registry.java

@@ -67,7 +67,7 @@ public class Dn42Registry implements RegistryImporter, RegistryExporter {
                 filter(File::isFile)
                 .map(file -> {
                     try {
-                        return parseObject(Files.readAllLines(file.toPath(), StandardCharsets.UTF_8), file.getAbsolutePath().replaceAll("^" + dir.getParentFile().getAbsolutePath().replace("\\", "\\\\") + "/", ""));
+                        return parseObject(Files.readAllLines(file.toPath(), StandardCharsets.UTF_8), dir.getName() + "/" + file.getName());
                     } catch (RuntimeException | IOException ex) {
                         throw new RuntimeException("Failed to load file " + file.getAbsolutePath() + ": " + ex.getMessage(), ex);
                     }

+ 21 - 16
src/main/java/dn42/registry/sync/converters/IcvpnRegistry.java

@@ -21,6 +21,7 @@ import dn42.registry.sync.RegistryImporter;
 import dn42.registry.sync.data.Attribute;
 import dn42.registry.sync.data.RegistryObject;
 import dn42.registry.sync.utils.Utils;
+import inet.ipaddr.IPAddress;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -43,6 +44,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
  */
 public class IcvpnRegistry implements RegistryImporter {
 
+    public static final String SOURCE = "ICVPN";
+
     private final File baseDir;
 
     public IcvpnRegistry(File baseDir) {
@@ -79,7 +82,7 @@ public class IcvpnRegistry implements RegistryImporter {
                 createSubnetObjects("ipv6", tree, path, asn, nameservers),
                 createDomains(tree, path, nameservers),
                 asn
-                        .filter(NamingRestrictions::isAutNumAllowedForIcvpn)
+                        .filter(NamingRestrictions::isAsnAllowedForIcvpn)
                         .map(as -> Stream.of(createAutNum(as, path)))
                         .orElseGet(() -> Stream.empty()),
                 createDelegations(tree, path, asn, nameservers)
@@ -89,7 +92,8 @@ public class IcvpnRegistry implements RegistryImporter {
     private Stream<RegistryObject> createSubnetObjects(String familyNode, Map<String, Object> tree, String path, Optional<Long> asn, List<String> nameservers) {
         return ((Map<String, List<String>>) tree.getOrDefault("networks", Collections.emptyMap()))
                 .getOrDefault(familyNode, Collections.emptyList()).stream()
-                .filter(NamingRestrictions::isIpAllowedForIcvpn)
+                .map(Utils::parsePrefixTruncating)
+                .filter(prefix -> NamingRestrictions.isIpv6AllowedForIcvpn(prefix) || NamingRestrictions.isIpv4AllowedForIcvpn(prefix))
                 .flatMap(createSubnetObjectsCreator(nameservers, asn, path));
     }
 
@@ -108,29 +112,30 @@ public class IcvpnRegistry implements RegistryImporter {
                 .map(e -> {
                     long as = Long.parseLong(e.getKey().toString());
                     return e.getValue().stream()
-                            .filter(NamingRestrictions::isIpAllowedForIcvpn)
+                            .map(Utils::parsePrefixTruncating)
+                            .filter(prefix -> NamingRestrictions.isIpv6AllowedForIcvpn(prefix) || NamingRestrictions.isIpv4AllowedForIcvpn(prefix))
                             .flatMap(createSubnetObjectsCreator(nameservers, Optional.of(as), path));
                 }).flatMap(Function.identity());
     }
 
-    private Function<String, Stream<RegistryObject>> createSubnetObjectsCreator(List<String> nameservers, Optional<Long> asn, String path) {
-        return asn.map((Function<Long, Function<String, Stream<RegistryObject>>>) autnum -> {
+    private Function<IPAddress, Stream<RegistryObject>> createSubnetObjectsCreator(List<String> nameservers, Optional<Long> asn, String path) {
+        return asn.map((Function<Long, Function<IPAddress, Stream<RegistryObject>>>) autnum -> {
             return prefix -> {
                 return Stream.concat(
-                        createInetnum(prefix.contains(":") ? "inet6num" : "inetnum", prefix, nameservers, path),
-                        createRoute(prefix.contains(":") ? "route6" : "route", prefix, autnum, path)
+                        createInetnum(prefix.isIPv6() ? "inet6num" : "inetnum", prefix, nameservers, path),
+                        createRoute(prefix.isIPv6() ? "route6" : "route", prefix, autnum, path)
                 );
             };
         }).orElseGet(() -> prefix -> {
-            return createInetnum(prefix.contains(":") ? "inet6num" : "inetnum", prefix, nameservers, path);
+            return createInetnum(prefix.isIPv6() ? "inet6num" : "inetnum", prefix, nameservers, path);
         });
     }
 
-    private Stream<RegistryObject> createInetnum(String schemaKey, String prefix, List<String> nameservers, String path) {
+    private Stream<RegistryObject> createInetnum(String schemaKey, IPAddress prefix, List<String> nameservers, String path) {
         List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute(schemaKey, Utils.cidrToRange(prefix.toLowerCase())));
-        attributes.add(new Attribute("cidr", Utils.cidrToShortCidr(prefix)));
-        attributes.add(new Attribute("netname", "ICVPN-" + path.toUpperCase().replace('/', '-')));
+        attributes.add(new Attribute(schemaKey, Utils.prefixToRange(prefix)));
+        attributes.add(new Attribute("cidr", Utils.prefixToCompactCidr(prefix)));
+        attributes.add(new Attribute("netname", SOURCE + "-" + path.toUpperCase().replace('/', '-')));
         nameservers.stream()
                 .map(Utils::createNserverAttributeForRegistrySync)
                 .forEach(attributes::add);
@@ -138,9 +143,9 @@ public class IcvpnRegistry implements RegistryImporter {
         return Stream.of(new RegistryObject(schemaKey, attributes, path));
     }
 
-    private Stream<RegistryObject> createRoute(String schemaKey, String prefix, long asn, String path) {
+    private Stream<RegistryObject> createRoute(String schemaKey, IPAddress prefix, long asn, String path) {
         List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute(schemaKey, Utils.cidrToShortCidr(prefix)));
+        attributes.add(new Attribute(schemaKey, Utils.prefixToCompactCidr(prefix)));
         attributes.add(new Attribute("origin", "AS" + asn));
         addDefaultAttributes(attributes, path);
         return Stream.of(new RegistryObject(schemaKey, attributes, path));
@@ -161,7 +166,7 @@ public class IcvpnRegistry implements RegistryImporter {
     private RegistryObject createAutNum(long asn, String path) {
         List<Attribute> attributes = new ArrayList<>();
         attributes.add(new Attribute("aut-num", "AS" + asn));
-        attributes.add(new Attribute("as-name", "ICVPN-" + path.toUpperCase().replace('/', '-')));
+        attributes.add(new Attribute("as-name", SOURCE + "-" + path.toUpperCase().replace('/', '-')));
         addDefaultAttributes(attributes, path);
         return new RegistryObject("aut-num", attributes, path);
     }
@@ -170,6 +175,6 @@ public class IcvpnRegistry implements RegistryImporter {
         attributes.add(new Attribute("remarks", "Imported from icvpn-meta, do not edit!"));
         attributes.add(new Attribute("remarks", "File: " + path));
         attributes.add(new Attribute("mnt-by", "DN42-MNT"));
-        attributes.add(new Attribute("source", "ICVPN"));
+        attributes.add(new Attribute("source", SOURCE));
     }
 }

+ 57 - 33
src/main/java/dn42/registry/sync/converters/NamingRestrictions.java

@@ -17,6 +17,8 @@
  */
 package dn42.registry.sync.converters;
 
+import dn42.registry.sync.utils.Utils;
+import inet.ipaddr.IPAddress;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -25,7 +27,6 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.regex.Pattern;
 
 /**
  *
@@ -33,13 +34,18 @@ import java.util.regex.Pattern;
  */
 public class NamingRestrictions {
 
-    private static final Pattern ALLOWED_IPV4_PREFIXES_ICVPN = Pattern.compile("^10\\.(.*/(9|[1-2][0-9]|3[0-2]))$");
-    private static final Pattern ALLOWED_IPV4_PREFIXES_CHAOSVPN_NATIVE = Pattern.compile("^172\\.(31)\\..*/(1[7-9]|2[0-9]|3[0-2])$");
-    private static final Pattern ALLOWED_IPV4_PREFIXES_CHAOSVPN_DELEGATED = Pattern.compile("^10\\.10[0-3]\\..*/(1[5-9]|2[0-9]|3[0-2])$");
-    private static final Pattern ALLOWED_IPV6_PREFIXES = Pattern.compile("^fd[0-9a-f]{2}:.*/(3[2-9]|[4-9][0-9]|1[0-1][0-9]|12[0-8])$");
     private static final Set<String> ICANN_TLDS;
     private static final Set<String> RESERVED_TLDS = new HashSet<>(Arrays.asList("dn42", "hack", "rzl"));
 
+    private static final IPAddress DN42_4 = Utils.parsePrefixTruncating("172.16.0.0/13");
+    private static final IPAddress DN42_6 = Utils.parsePrefixTruncating("fd00::/8");
+    private static final IPAddress ICVPN_4 = Utils.parsePrefixTruncating("10.0.0.0/8");
+    private static final IPAddress ICVPN_6 = Utils.parsePrefixTruncating("fd00::/8");
+    private static final Set<IPAddress> CHAOSVPN_4 = Set.of(Utils.parsePrefixTruncating("172.31.0.0/16"), Utils.parsePrefixTruncating("10.100.0.0/14"));
+    private static final IPAddress CHAOSVPN_6 = Utils.parsePrefixTruncating("fd00::/8");
+    private static final IPAddress NEONETWORK_4 = Utils.parsePrefixTruncating("10.127.0.0/16");
+    private static final IPAddress NEONETWORK_6 = Utils.parsePrefixTruncating("fd10:127::/32");
+
     static {
         Set<String> tlds = new HashSet<>();
         try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL("https://data.iana.org/TLD/tlds-alpha-by-domain.txt").openStream()))) {
@@ -67,39 +73,57 @@ public class NamingRestrictions {
         }
     }
 
-    private static boolean isIpv6Allowed(String prefix) {
-        return ALLOWED_IPV6_PREFIXES.matcher(prefix).matches();
+    public static boolean isAsnAllowedForDn42(long asn) {
+        return 4242420000L <= asn && asn <= 4242429999L;
     }
 
-    public static boolean isIpAllowedForIcvpn(String prefix) {
-        if (isIpv6Allowed(prefix)) {
-            return true;
-        } else if (ALLOWED_IPV4_PREFIXES_ICVPN.matcher(prefix).matches()) {
-            return !isIpAllowedForChaosvpn(prefix);
-        } else {
-            return false;
-        }
+    public static boolean isIpv6AllowedForIcvpn(IPAddress prefix) {
+        return isMoreSpecific(ICVPN_6, prefix)
+                && !isMoreSpecific(NEONETWORK_6, prefix);
     }
 
-    public static boolean isIpAllowedForChaosvpn(String prefix) {
-        if (isIpv6Allowed(prefix)) {
-            return true;
-        } else if (ALLOWED_IPV4_PREFIXES_CHAOSVPN_NATIVE.matcher(prefix).matches()) {
-            return true;
-        } else if (ALLOWED_IPV4_PREFIXES_CHAOSVPN_DELEGATED.matcher(prefix).matches()) {
-            return true;
-        } else {
-            return false;
-        }
+    public static boolean isIpv4AllowedForIcvpn(IPAddress prefix) {
+        return isMoreSpecific(ICVPN_4, prefix)
+                && !isMoreSpecific(DN42_4, prefix)
+                && !isMoreSpecific(CHAOSVPN_4, prefix)
+                && !isMoreSpecific(NEONETWORK_4, prefix);
     }
 
-    public static boolean isAutNumAllowedForIcvpn(long autnum) {
-        if (autnum >= 4242420000L && autnum <= 4242429999L) {
-            // dn42 32b range.
-            return false;
-        } else {
-            // clearnet and dn42 16b is fine.
-            return true;
-        }
+    public static boolean isAsnAllowedForIcvpn(long asn) {
+        return !isAsnAllowedForDn42(asn)
+                && !isAsnAllowedForChaosvpn(asn)
+                && !isAsnAllowedForNeonetwork(asn);
+    }
+
+    public static boolean isIpv6AllowedForChaosvpn(IPAddress prefix) {
+        return isMoreSpecific(CHAOSVPN_6, prefix);
+    }
+
+    public static boolean isIpv4AllowedForChaosvpn(IPAddress prefix) {
+        return isMoreSpecific(CHAOSVPN_4, prefix);
+    }
+
+    public static boolean isAsnAllowedForChaosvpn(long asn) {
+        return false;
+    }
+
+    public static boolean isIpv6AllowedForNeonetwork(IPAddress prefix) {
+        return isMoreSpecific(NEONETWORK_6, prefix);
+    }
+
+    public static boolean isIpv4AllowedForNeonetwork(IPAddress prefix) {
+        return isMoreSpecific(NEONETWORK_4, prefix);
+    }
+
+    public static boolean isAsnAllowedForNeonetwork(long asn) {
+        return 4201270000L <= asn && asn <= 4201279999L;
+    }
+
+    private static boolean isMoreSpecific(IPAddress filter, IPAddress prefix) {
+        return filter.contains(prefix) && !filter.equals(prefix);
+    }
+
+    private static boolean isMoreSpecific(Set<IPAddress> filter, IPAddress prefix) {
+        return filter.stream().anyMatch(allowed -> isMoreSpecific(allowed, prefix));
     }
 }

+ 447 - 0
src/main/java/dn42/registry/sync/converters/NeonetworkRegistry.java

@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2018 Jean-Rémy Buchs <jrb0001@692b8c32.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+package dn42.registry.sync.converters;
+
+import dn42.registry.sync.RegistryImporter;
+import dn42.registry.sync.data.Attribute;
+import dn42.registry.sync.data.RegistryObject;
+import dn42.registry.sync.utils.Utils;
+import inet.ipaddr.IPAddress;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.adapter.JsonbAdapter;
+import javax.json.bind.annotation.JsonbTypeAdapter;
+
+/**
+ *
+ * @author Jean-Rémy Buchs <jrb0001@692b8c32.de>
+ */
+public class NeonetworkRegistry implements RegistryImporter {
+
+    public static final String SOURCE = "NEONETWORK";
+
+    private final File baseDir;
+
+    public NeonetworkRegistry(File baseDir) {
+        this.baseDir = baseDir;
+
+        if (!baseDir.exists()) {
+            throw new RuntimeException("Directory " + baseDir.getAbsolutePath() + " doesn't exist.");
+        }
+    }
+
+    @Override
+    public Stream<RegistryObject> importRegistry() {
+        File file = new File(baseDir, "neonetwork.json");
+        try {
+            Data json = JsonbBuilder.create().fromJson(new FileReader(file, StandardCharsets.UTF_8), Data.class);
+
+            checkMetadata(json.getMetadata());
+
+            return importPeople(json.getPeople().entrySet().stream());
+        } catch (RuntimeException | IOException ex) {
+            throw new RuntimeException("Failed to load file " + file.getAbsolutePath() + ": " + ex.getMessage(), ex);
+        }
+    }
+
+    private void checkMetadata(Metadata metadata) {
+        Instant generated = Instant.ofEpochSecond(metadata.getGenerated());
+        Instant valid = Instant.ofEpochSecond(metadata.getValid());
+        if (generated.isAfter(Instant.now())) {
+            throw new IllegalArgumentException("generated=" + generated + " is after now.");
+        }
+        if (valid.isBefore(Instant.now())) {
+            throw new IllegalArgumentException("valid=" + valid + " is before now.");
+        }
+    }
+
+    private Stream<RegistryObject> importPeople(Stream<Map.Entry<String, Person>> people) {
+        return people.flatMap(entry -> {
+            Person person = entry.getValue();
+            String nichdl = getNicHdl(person.getInfo());
+
+            return Stream.concat(
+                    importPerson(nichdl, person.getInfo()),
+                    person.getAsns().stream().flatMap(asn -> importAsn(asn, nichdl))
+            );
+        });
+    }
+
+    private Stream<RegistryObject> importPerson(String nichdl, PersonInfo person) {
+        return Stream.of(
+                new RegistryObject("person", Stream.of(
+                        Stream.of(
+                                new Attribute("person", person.getName()),
+                                new Attribute("nic-hdl", nichdl)
+                        ),
+                        importOptionalAttribute("remarks", person.getDesc()),
+                        importContacts(person.getContact().stream()),
+                        getDefaultAttributes()
+                ).flatMap(Function.identity()).collect(Collectors.toUnmodifiableList()), "neonetwork")
+        );
+    }
+
+    private Stream<Attribute> importContacts(Stream<String> contacts) {
+        return contacts.flatMap(contact -> {
+            String[] parts = contact.split(":", 2);
+            String key = parts[0].trim();
+            String value = parts[1].trim();
+            if (value.isEmpty()) {
+                return Stream.empty();
+            } else if ("EMAIL".equalsIgnoreCase(key)) {
+                return Stream.of(new Attribute("e-mail", value));
+            } else {
+                return Stream.of(new Attribute("contact", key.toLowerCase() + ":" + value));
+            }
+        });
+    }
+
+    private Stream<RegistryObject> importAsn(Asn asn, String nichdl) {
+        return Stream.of(
+                importAsnEntry(asn, nichdl),
+                asn.getRoutes().getIpv6().stream()
+                        .flatMap(importRouteEntry6(nichdl, asn.getAsn())),
+                asn.getRoutes().getIpv4().stream()
+                        .flatMap(importRouteEntry4(nichdl, asn.getAsn()))
+        ).flatMap(Function.identity());
+    }
+
+    private Stream<RegistryObject> importAsnEntry(Asn asn, String nichdl) {
+        if (NamingRestrictions.isAsnAllowedForNeonetwork(asn.getAsn())) {
+            return Stream.of(
+                    new RegistryObject("aut-num", Stream.of(
+                            Stream.of(
+                                    new Attribute("aut-num", "AS" + asn.getAsn()),
+                                    new Attribute("as-name", asn.getName())
+                            ),
+                            importOptionalAttribute("descr", asn.getDesc()),
+                            Stream.of(
+                                    new Attribute("admin-c", nichdl),
+                                    new Attribute("tech-c", nichdl)
+                            ),
+                            getDefaultAttributes()
+                    ).flatMap(Function.identity()).collect(Collectors.toUnmodifiableList()), "neonetwork")
+            );
+        } else {
+            return Stream.empty();
+        }
+    }
+
+    private Function<Route, Stream<RegistryObject>> importRouteEntry6(String nichdl, long asn) {
+        return route -> {
+            if (NamingRestrictions.isIpv6AllowedForNeonetwork(route.getPrefix())) {
+                return Stream.of(
+                        importInetnum(route, nichdl, "inet6num"),
+                        importRoute(route, asn, nichdl, "route6")
+                );
+            } else {
+                return Stream.empty();
+            }
+        };
+    }
+
+    private Function<Route, Stream<RegistryObject>> importRouteEntry4(String nichdl, long asn) {
+        return route -> {
+            if (NamingRestrictions.isIpv4AllowedForNeonetwork(route.getPrefix())) {
+                return Stream.of(
+                        importInetnum(route, nichdl, "inetnum"),
+                        importRoute(route, asn, nichdl, "route")
+                );
+            } else {
+                return Stream.empty();
+            }
+        };
+    }
+
+    private RegistryObject importInetnum(Route route, String nichdl, String schemaKey) {
+        return new RegistryObject(schemaKey, Stream.concat(
+                Stream.of(
+                        new Attribute(schemaKey, Utils.prefixToRange(route.getPrefix())),
+                        new Attribute("cidr", Utils.prefixToCompactCidr(route.getPrefix())),
+                        new Attribute("netname", route.getNetname()),
+                        new Attribute("admin-c", nichdl),
+                        new Attribute("tech-c", nichdl)
+                ),
+                getDefaultAttributes()
+        ).collect(Collectors.toUnmodifiableList()), "neonetwork");
+    }
+
+    private RegistryObject importRoute(Route route, long asn, String nichdl, String schemaKey) {
+        return new RegistryObject(schemaKey, Stream.concat(
+                Stream.of(
+                        new Attribute(schemaKey, Utils.prefixToCompactCidr(route.getPrefix())),
+                        new Attribute("origin", "AS" + asn),
+                        new Attribute("max-length", String.valueOf(route.getMaxLength())),
+                        new Attribute("admin-c", nichdl),
+                        new Attribute("tech-c", nichdl)
+                ),
+                getDefaultAttributes()
+        ).collect(Collectors.toUnmodifiableList()), "neonetwork");
+    }
+
+    private Stream<Attribute> importOptionalAttribute(String key, String value) {
+        if (value.isEmpty()) {
+            return Stream.empty();
+        } else {
+            return Stream.of(new Attribute(key, value));
+        }
+    }
+
+    private String getNicHdl(PersonInfo person) {
+        return person.getNic_hdl().toUpperCase().replaceAll("[^A-Z0-9]", "-") + "-" + SOURCE;
+    }
+
+    private Stream<Attribute> getDefaultAttributes() {
+        return Stream.of(
+                new Attribute("remarks", "Imported from neonetwork, do not edit!"),
+                new Attribute("mnt-by", "DN42-MNT"),
+                new Attribute("source", SOURCE)
+        );
+    }
+
+    protected static class Data {
+
+        private Metadata metadata;
+        private Map<String, Person> people;
+
+        public Metadata getMetadata() {
+            return metadata;
+        }
+
+        public void setMetadata(Metadata metadata) {
+            this.metadata = metadata;
+        }
+
+        public Map<String, Person> getPeople() {
+            return people;
+        }
+
+        public void setPeople(Map<String, Person> people) {
+            this.people = people;
+        }
+    }
+
+    protected static class Metadata {
+
+        private int generated;
+        private int valid;
+
+        public int getGenerated() {
+            return generated;
+        }
+
+        public void setGenerated(int generated) {
+            this.generated = generated;
+        }
+
+        public int getValid() {
+            return valid;
+        }
+
+        public void setValid(int valid) {
+            this.valid = valid;
+        }
+    }
+
+    protected static class Person {
+
+        private PersonInfo info;
+        private List<Asn> asns;
+
+        public PersonInfo getInfo() {
+            return info;
+        }
+
+        public void setInfo(PersonInfo info) {
+            this.info = info;
+        }
+
+        public List<Asn> getAsns() {
+            return asns;
+        }
+
+        public void setAsns(List<Asn> asns) {
+            this.asns = asns;
+        }
+    }
+
+    protected static class PersonInfo {
+
+        private String name;
+        private String desc;
+        private List<String> contact;
+        private List<String> babel;
+        private String nic_hdl;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public void setDesc(String desc) {
+            this.desc = desc;
+        }
+
+        public List<String> getContact() {
+            return contact;
+        }
+
+        public void setContact(List<String> contact) {
+            this.contact = contact;
+        }
+
+        public List<String> getBabel() {
+            return babel;
+        }
+
+        public void setBabel(List<String> babel) {
+            this.babel = babel;
+        }
+
+        public String getNic_hdl() {
+            return nic_hdl;
+        }
+
+        public void setNic_hdl(String nic_hdl) {
+            this.nic_hdl = nic_hdl;
+        }
+    }
+
+    protected static class Asn {
+
+        private long asn;
+        private String name;
+        private String desc;
+        private Routes routes;
+
+        public long getAsn() {
+            return asn;
+        }
+
+        public void setAsn(long asn) {
+            this.asn = asn;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public void setDesc(String desc) {
+            this.desc = desc;
+        }
+
+        public Routes getRoutes() {
+            return routes;
+        }
+
+        public void setRoutes(Routes routes) {
+            this.routes = routes;
+        }
+    }
+
+    protected static class Routes {
+
+        private List<Route> ipv6;
+        private List<Route> ipv4;
+
+        public List<Route> getIpv6() {
+            return ipv6;
+        }
+
+        public void setIpv6(List<Route> ipv6) {
+            this.ipv6 = ipv6;
+        }
+
+        public List<Route> getIpv4() {
+            return ipv4;
+        }
+
+        public void setIpv4(List<Route> ipv4) {
+            this.ipv4 = ipv4;
+        }
+    }
+
+    protected static class Route {
+
+        @JsonbTypeAdapter(PrefixAdapter.class)
+        private IPAddress prefix;
+        private String netname;
+        private int maxLength;
+
+        public IPAddress getPrefix() {
+            return prefix;
+        }
+
+        public void setPrefix(IPAddress prefix) {
+            this.prefix = prefix;
+        }
+
+        public String getNetname() {
+            return netname;
+        }
+
+        public void setNetname(String netname) {
+            this.netname = netname;
+        }
+
+        public int getMaxLength() {
+            return maxLength;
+        }
+
+        public void setMaxLength(int maxLength) {
+            this.maxLength = maxLength;
+        }
+    }
+
+    protected static class PrefixAdapter implements JsonbAdapter<IPAddress, String> {
+
+        @Override
+        public String adaptToJson(IPAddress obj) throws Exception {
+            return obj.toCompressedString();
+        }
+
+        @Override
+        public IPAddress adaptFromJson(String obj) throws Exception {
+            return Utils.parsePrefixTruncating(obj);
+        }
+    }
+}

+ 18 - 4
src/main/java/dn42/registry/sync/utils/Utils.java

@@ -20,6 +20,7 @@ package dn42.registry.sync.utils;
 import dn42.registry.sync.data.Attribute;
 import dn42.registry.sync.data.RegistryObject;
 import inet.ipaddr.AddressStringException;
+import inet.ipaddr.IPAddress;
 import inet.ipaddr.IPAddressString;
 import inet.ipaddr.IncompatibleAddressException;
 import java.net.Inet6Address;
@@ -61,6 +62,7 @@ public class Utils {
                 .collect(Collectors.toList());
     }
 
+    @Deprecated
     public static String cidrToRange(String cidr) {
         try {
             byte[] addr = InetAddress.getByName(cidr.split("/")[0]).getAddress();
@@ -79,11 +81,23 @@ public class Utils {
         }
     }
 
-    public static String cidrToShortCidr(String addr) {
-        if (addr.contains("::")) {
-            return addr;
+    public static String prefixToRange(IPAddress cidr) {
+        if (cidr.isIPv4()) {
+            return cidr.toZeroHost().withoutPrefixLength().toCompressedString() + " - " + cidr.toMaxHost().withoutPrefixLength().toCompressedString();
         } else {
-            return addr.toLowerCase().replaceAll("((?:(?::0|0:0?)\\b){2,}):?(?!\\S*\\b\\1:0\\b)(\\S*)", "::$2");
+            return cidr.toZeroHost().withoutPrefixLength().toFullString() + " - " + cidr.toMaxHost().withoutPrefixLength().toFullString();
+        }
+    }
+
+    public static String prefixToCompactCidr(IPAddress cidr) {
+        return cidr.toCompressedString();
+    }
+
+    public static IPAddress parsePrefixTruncating(String prefix) {
+        try {
+            return new IPAddressString(prefix).toAddress().toPrefixBlock();
+        } catch (AddressStringException | IncompatibleAddressException ex) {
+            throw new IllegalArgumentException("Invalid prefix: " + prefix, ex);
         }
     }