diff --git a/.gitignore b/.gitignore index 502a62f..8bc310c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ build/ .DS_Store /.idea/ /.mvn/ +/config.json diff --git a/app/pom.xml b/app/pom.xml new file mode 100644 index 0000000..8a3b0d0 --- /dev/null +++ b/app/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + ru.kirillius + pf-sdn + 0.1.0.0 + + + app + + + 24 + 24 + UTF-8 + + + + ru.kirillius + core + 0.1.0.0 + compile + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/kirillius/pf/sdn/App.java b/app/src/main/java/ru/kirillius/pf/sdn/App.java new file mode 100644 index 0000000..a1a465b --- /dev/null +++ b/app/src/main/java/ru/kirillius/pf/sdn/App.java @@ -0,0 +1,62 @@ +package ru.kirillius.pf.sdn; + +import lombok.Getter; +import org.eclipse.jetty.server.Server; +import ru.kirillius.pf.sdn.External.API.HENetBGPInfoProvider; +import ru.kirillius.pf.sdn.core.Auth.AuthManager; +import ru.kirillius.pf.sdn.core.Config; +import ru.kirillius.pf.sdn.core.Context; +import ru.kirillius.pf.sdn.core.Networking.AutonomousSystemInformationService; +import ru.kirillius.pf.sdn.core.Networking.NetworkManager; + +import java.io.File; +import java.io.IOException; + +public class App implements Context { + public static void main(String[] args) { + new App(); + } + + private final static File configFile = new File("config.json"); + + public App() { + try { + config = Config.load(configFile); + } catch (IOException e) { + config = new Config(); + try { + Config.store(config, configFile); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + authManager = new AuthManager(this); + server = new Server(); + autonomousSystemInformationService = new AutonomousSystemInformationService(); + autonomousSystemInformationService.setProvider(new HENetBGPInfoProvider()); + networkManager = new NetworkManager(this); + var inputResources = networkManager.getInputResources(); + inputResources.add(config.getCustomResources()); + networkManager.triggerUpdate(); + + while (networkManager.isUpdatingNow()) { + Thread.yield(); + } + + return; + } + + @Getter + private final NetworkManager networkManager; + @Getter + private Config config; + @Getter + private final AuthManager authManager; + @Getter + private final Server server; + @Getter + private final AutonomousSystemInformationService autonomousSystemInformationService; + + +} diff --git a/app/src/main/java/ru/kirillius/pf/sdn/External/API/HENetBGPInfoProvider.java b/app/src/main/java/ru/kirillius/pf/sdn/External/API/HENetBGPInfoProvider.java new file mode 100644 index 0000000..ec76931 --- /dev/null +++ b/app/src/main/java/ru/kirillius/pf/sdn/External/API/HENetBGPInfoProvider.java @@ -0,0 +1,56 @@ +package ru.kirillius.pf.sdn.External.API; + +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.json.JSONObject; +import org.json.JSONTokener; +import ru.kirillius.pf.sdn.core.Networking.AutonomousSystemInfoProvider; +import ru.kirillius.pf.sdn.core.Networking.IPv4Subnet; +import ru.kirillius.utils.logging.SystemLogger; + +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HENetBGPInfoProvider implements AutonomousSystemInfoProvider { + @Override + @SneakyThrows + public List getPrefixes(int as) { + try (var client = HttpClient.newHttpClient()) { + var request = HttpRequest.newBuilder().uri(URI.create("https://bgp.he.net/super-lg/report/api/v1/prefixes/originated/" + as)).header("Accept", "application/json").GET().build(); + var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() == 200) { + try (var inputStream = response.body()) { + return getIPv4Subnets(inputStream); + } + } else { + SystemLogger.error("Unable to get info about AS" + as, CTX); + return Collections.emptyList(); + } + } + } + + private static @NotNull ArrayList getIPv4Subnets(InputStream inputStream) { + var json = new JSONObject(new JSONTokener(inputStream)); + var array = json.getJSONArray("prefixes"); + var list = new ArrayList(); + array.forEach(obj -> { + var jo = (JSONObject) obj; + + var prefix = jo.getString("Prefix"); + if (prefix.indexOf('.') == -1) { + return; + } + list.add(new IPv4Subnet(prefix)); + + }); + return list; + } + + private final static String CTX = HENetBGPInfoProvider.class.getSimpleName(); +} diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java index b0e5f9d..11c9579 100644 --- a/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java @@ -10,6 +10,7 @@ import ru.kirillius.json.JSONProperty; import ru.kirillius.json.JSONSerializable; import ru.kirillius.json.JSONUtility; import ru.kirillius.pf.sdn.core.Auth.AuthToken; +import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle; import java.io.*; import java.util.Collections; @@ -39,13 +40,33 @@ public class Config { @Setter @Getter @JSONProperty - private String passwordHash = null; + private String passwordHash = ""; @Setter @Getter @JSONProperty private int httpPort = 8081; + @Setter + @Getter + @JSONProperty + private boolean mergeSubnets = true; + + @Setter + @Getter + @JSONProperty + private int mergeSubnetsCount = 10; + + @Setter + @Getter + @JSONProperty + private NetworkResourceBundle customResources = new NetworkResourceBundle(); + + @Setter + @Getter + @JSONProperty + private NetworkResourceBundle filteredResources = new NetworkResourceBundle(); + public void update() { try { store(this, loadedConfigFile); diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java index a029ce5..4327a74 100644 --- a/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java @@ -2,6 +2,8 @@ package ru.kirillius.pf.sdn.core; import org.eclipse.jetty.server.Server; import ru.kirillius.pf.sdn.core.Auth.AuthManager; +import ru.kirillius.pf.sdn.core.Networking.AutonomousSystemInformationService; +import ru.kirillius.pf.sdn.core.Networking.NetworkManager; public interface Context { Config getConfig(); @@ -10,4 +12,8 @@ public interface Context { Server getServer(); + AutonomousSystemInformationService getAutonomousSystemInformationService(); + + NetworkManager getNetworkManager(); + } diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInfoProvider.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInfoProvider.java new file mode 100644 index 0000000..3d1c3ce --- /dev/null +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInfoProvider.java @@ -0,0 +1,7 @@ +package ru.kirillius.pf.sdn.core.Networking; + +import java.util.List; + +public interface AutonomousSystemInfoProvider { + List getPrefixes(int as); +} diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInformationService.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInformationService.java new file mode 100644 index 0000000..bb39c77 --- /dev/null +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/AutonomousSystemInformationService.java @@ -0,0 +1,34 @@ +package ru.kirillius.pf.sdn.core.Networking; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +@NoArgsConstructor +public class AutonomousSystemInformationService implements Closeable { + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + + @Getter + @Setter + private AutonomousSystemInfoProvider provider = null; + + + public Future> getPrefixes(int as) { + return executor.submit(() -> provider.getPrefixes(as)); + } + + @Override + public void close() throws IOException { + executor.shutdown(); + } + + +} diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java index a007038..d4bd9a2 100644 --- a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java @@ -1,18 +1,46 @@ package ru.kirillius.pf.sdn.core.Networking; import lombok.Getter; +import ru.kirillius.json.JSONSerializer; +import ru.kirillius.json.SerializationException; import ru.kirillius.pf.sdn.core.Util.IPv4Util; +import java.util.Objects; import java.util.regex.Pattern; public class IPv4Subnet { + public final static class Serializer implements JSONSerializer { + + @Override + public Object serialize(IPv4Subnet subnet) throws SerializationException { + return subnet.toString(); + } + + @Override + public IPv4Subnet deserialize(Object o, Class aClass) throws SerializationException { + return new IPv4Subnet((String) o); + } + } + private final long address; @Getter private final int prefixLength; + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + IPv4Subnet that = (IPv4Subnet) o; + return address == that.address && prefixLength == that.prefixLength; + } + + @Override + public int hashCode() { + return Objects.hash(address, prefixLength); + } + public IPv4Subnet(String subnet) { var split = subnet.split(Pattern.quote("/")); if (split.length != 2) { @@ -45,7 +73,7 @@ public class IPv4Subnet { public boolean overlaps(IPv4Subnet subnet) { var minPrefixLength = Math.min(prefixLength, subnet.prefixLength); var commonMask = IPv4Util.calculateMask(minPrefixLength); - if (commonMask != prefixLength) { + if (minPrefixLength != prefixLength) { return false; //can't overlap larger prefix } diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkManager.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkManager.java new file mode 100644 index 0000000..214e451 --- /dev/null +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkManager.java @@ -0,0 +1,105 @@ +package ru.kirillius.pf.sdn.core.Networking; + +import lombok.Getter; +import ru.kirillius.pf.sdn.core.Context; + +import java.io.Closeable; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class NetworkManager implements Closeable { + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + private Context context; + + public NetworkManager(Context context) { + this.context = context; + } + + private final AtomicReference> updateProcess = new AtomicReference<>(); + private final AtomicBoolean cacheInvalid = new AtomicBoolean(true); + @Getter + private final NetworkResourceBundle inputResources = new NetworkResourceBundle(); + @Getter + private final NetworkResourceBundle outputResources = new NetworkResourceBundle(); + + public boolean isUpdatingNow() { + var future = updateProcess.get(); + return future != null && !future.isDone() && !future.isCancelled(); + } + + private final Map> prefixCache = new ConcurrentHashMap<>(); + + public synchronized void triggerUpdate() { + if (isUpdatingNow()) { + return; + } + updateProcess.set(executor.submit(() -> { + var config = context.getConfig(); + var filteredResources = config.getFilteredResources(); + var asn = new ArrayList<>(inputResources.getASN()); + asn.removeAll(filteredResources.getASN()); + + + if (cacheInvalid.get()) { + fetchPrefixes(asn); + } + + var subnets = new HashSet<>(inputResources.getSubnets()); + prefixCache.values().forEach(subnets::addAll); + filteredResources.getSubnets().forEach(subnets::remove); + + var domains = new HashSet<>(inputResources.getDomains()); + filteredResources.getDomains().forEach(domains::remove); + //check overlaps + + var domainsToRemove = new HashSet(); + for (var domainToMatch : domains) { + var pattern = "." + domainToMatch; + for (var domain : domains) { + if (domain.endsWith(pattern)) { + domainsToRemove.add(domain); + } + } + } + + domains.removeAll(domainsToRemove); + + outputResources.setASN(Collections.unmodifiableList(asn)); + outputResources.setSubnets(subnets.stream().toList()); + outputResources.setDomains(domains.stream().toList()); + + })); + } + + public void invalidateCache() { + cacheInvalid.set(true); + } + + private void fetchPrefixes(List systems) { + systems.forEach(as -> { + var service = context.getAutonomousSystemInformationService(); + var future = service.getPrefixes(as); + + while (!future.isDone() && !future.isCancelled()) { + Thread.yield(); + } + + try { + var iPv4Subnets = future.get(); + prefixCache.put(as, iPv4Subnets); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + } + + + @Override + public void close() throws IOException { + executor.shutdown(); + } +} diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkResourceBundle.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkResourceBundle.java new file mode 100644 index 0000000..c29939d --- /dev/null +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/NetworkResourceBundle.java @@ -0,0 +1,40 @@ +package ru.kirillius.pf.sdn.core.Networking; + +import lombok.*; +import ru.kirillius.json.JSONArrayProperty; +import ru.kirillius.json.JSONSerializable; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JSONSerializable +public class NetworkResourceBundle { + @Getter + @Setter + @JSONArrayProperty(type = Integer.class) + private List ASN = new ArrayList<>(); + @Getter + @Setter + @JSONArrayProperty(type = IPv4Subnet.class, serializer = IPv4Subnet.Serializer.class) + private List subnets = new ArrayList<>(); + @Getter + @Setter + @JSONArrayProperty(type = String.class) + private List domains = new ArrayList<>(); + + public void clear() { + ASN.clear(); + subnets.clear(); + domains.clear(); + } + + public void add(NetworkResourceBundle networkResourceBundle) { + ASN.addAll(networkResourceBundle.getASN()); + subnets.addAll(networkResourceBundle.getSubnets()); + domains.addAll(networkResourceBundle.getDomains()); + } + +} diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java deleted file mode 100644 index d5df553..0000000 --- a/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.kirillius.pf.sdn.core.Util; - -public class BGPUtility { - -} diff --git a/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java b/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java index 078471c..cc080ee 100644 --- a/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java +++ b/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java @@ -13,7 +13,7 @@ class IPv4SubnetTest { var validPrefixes = List.of(0, 5, 20, 32); var invalidPrefixes = List.of(-1, 33, 800); var validAddresses = List.of("1.2.3.4", "0.0.0.0", "255.255.255.255"); - var invalidAddresses = List.of("1.2.3.04", "0.0.0", "255.255.255.255.255", "1.2.3.256", "-1.0.0.0"); + var invalidAddresses = List.of("1", "0.0.0", "255.255.255.255.255", "1.2.3.256", "-1.0.0.0"); validPrefixes.forEach(prefix -> { @@ -27,7 +27,7 @@ class IPv4SubnetTest { try { var subnet = new IPv4Subnet(address, prefix); throw new AssertionError(); - } catch (Exception e) { + } catch (Throwable e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } }); @@ -38,7 +38,7 @@ class IPv4SubnetTest { try { var subnet = new IPv4Subnet(address, prefix); throw new AssertionError(); - } catch (Exception e) { + } catch (Throwable e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } }); @@ -49,7 +49,7 @@ class IPv4SubnetTest { try { var subnet = new IPv4Subnet(address, prefix); throw new AssertionError(); - } catch (Exception e) { + } catch (Throwable e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } }); diff --git a/launcher/pom.xml b/launcher/pom.xml deleted file mode 100644 index 9126b2c..0000000 --- a/launcher/pom.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - 4.0.0 - - ru.kirillius - pf-sdn - 0.1.0.0 - - - launcher - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 268337f..94bdf3d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,9 +10,9 @@ pom web-client - launcher core web-server + app @@ -94,12 +94,7 @@ hibernate-commons 2.2.0.0 - - - org.javassist - javassist - 3.29.2-GA - + @@ -112,9 +107,10 @@ org.projectlombok lombok - 1.18.34 + 1.18.40 provided + \ No newline at end of file