промежуточный коммит

This commit is contained in:
kirillius 2025-09-11 19:25:09 +03:00
parent 08c8fe2f7c
commit 47bd3ef9d6
15 changed files with 398 additions and 33 deletions

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ build/
.DS_Store .DS_Store
/.idea/ /.idea/
/.mvn/ /.mvn/
/config.json

28
app/pom.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.kirillius</groupId>
<artifactId>pf-sdn</artifactId>
<version>0.1.0.0</version>
</parent>
<artifactId>app</artifactId>
<properties>
<maven.compiler.source>24</maven.compiler.source>
<maven.compiler.target>24</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>ru.kirillius</groupId>
<artifactId>core</artifactId>
<version>0.1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -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;
}

View File

@ -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<IPv4Subnet> 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<IPv4Subnet> getIPv4Subnets(InputStream inputStream) {
var json = new JSONObject(new JSONTokener(inputStream));
var array = json.getJSONArray("prefixes");
var list = new ArrayList<IPv4Subnet>();
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();
}

View File

@ -10,6 +10,7 @@ import ru.kirillius.json.JSONProperty;
import ru.kirillius.json.JSONSerializable; import ru.kirillius.json.JSONSerializable;
import ru.kirillius.json.JSONUtility; import ru.kirillius.json.JSONUtility;
import ru.kirillius.pf.sdn.core.Auth.AuthToken; import ru.kirillius.pf.sdn.core.Auth.AuthToken;
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
import java.io.*; import java.io.*;
import java.util.Collections; import java.util.Collections;
@ -39,13 +40,33 @@ public class Config {
@Setter @Setter
@Getter @Getter
@JSONProperty @JSONProperty
private String passwordHash = null; private String passwordHash = "";
@Setter @Setter
@Getter @Getter
@JSONProperty @JSONProperty
private int httpPort = 8081; 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() { public void update() {
try { try {
store(this, loadedConfigFile); store(this, loadedConfigFile);

View File

@ -2,6 +2,8 @@ package ru.kirillius.pf.sdn.core;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import ru.kirillius.pf.sdn.core.Auth.AuthManager; 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 { public interface Context {
Config getConfig(); Config getConfig();
@ -10,4 +12,8 @@ public interface Context {
Server getServer(); Server getServer();
AutonomousSystemInformationService getAutonomousSystemInformationService();
NetworkManager getNetworkManager();
} }

View File

@ -0,0 +1,7 @@
package ru.kirillius.pf.sdn.core.Networking;
import java.util.List;
public interface AutonomousSystemInfoProvider {
List<IPv4Subnet> getPrefixes(int as);
}

View File

@ -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<List<IPv4Subnet>> getPrefixes(int as) {
return executor.submit(() -> provider.getPrefixes(as));
}
@Override
public void close() throws IOException {
executor.shutdown();
}
}

View File

@ -1,18 +1,46 @@
package ru.kirillius.pf.sdn.core.Networking; package ru.kirillius.pf.sdn.core.Networking;
import lombok.Getter; import lombok.Getter;
import ru.kirillius.json.JSONSerializer;
import ru.kirillius.json.SerializationException;
import ru.kirillius.pf.sdn.core.Util.IPv4Util; import ru.kirillius.pf.sdn.core.Util.IPv4Util;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class IPv4Subnet { public class IPv4Subnet {
public final static class Serializer implements JSONSerializer<IPv4Subnet> {
@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; private final long address;
@Getter @Getter
private final int prefixLength; 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) { public IPv4Subnet(String subnet) {
var split = subnet.split(Pattern.quote("/")); var split = subnet.split(Pattern.quote("/"));
if (split.length != 2) { if (split.length != 2) {
@ -45,7 +73,7 @@ public class IPv4Subnet {
public boolean overlaps(IPv4Subnet subnet) { public boolean overlaps(IPv4Subnet subnet) {
var minPrefixLength = Math.min(prefixLength, subnet.prefixLength); var minPrefixLength = Math.min(prefixLength, subnet.prefixLength);
var commonMask = IPv4Util.calculateMask(minPrefixLength); var commonMask = IPv4Util.calculateMask(minPrefixLength);
if (commonMask != prefixLength) { if (minPrefixLength != prefixLength) {
return false; //can't overlap larger prefix return false; //can't overlap larger prefix
} }

View File

@ -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<Future<?>> 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<Integer, List<IPv4Subnet>> 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<String>();
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<Integer> 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();
}
}

View File

@ -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<Integer> ASN = new ArrayList<>();
@Getter
@Setter
@JSONArrayProperty(type = IPv4Subnet.class, serializer = IPv4Subnet.Serializer.class)
private List<IPv4Subnet> subnets = new ArrayList<>();
@Getter
@Setter
@JSONArrayProperty(type = String.class)
private List<String> 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());
}
}

View File

@ -1,5 +0,0 @@
package ru.kirillius.pf.sdn.core.Util;
public class BGPUtility {
}

View File

@ -13,7 +13,7 @@ class IPv4SubnetTest {
var validPrefixes = List.of(0, 5, 20, 32); var validPrefixes = List.of(0, 5, 20, 32);
var invalidPrefixes = List.of(-1, 33, 800); var invalidPrefixes = List.of(-1, 33, 800);
var validAddresses = List.of("1.2.3.4", "0.0.0.0", "255.255.255.255"); 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 -> { validPrefixes.forEach(prefix -> {
@ -27,7 +27,7 @@ class IPv4SubnetTest {
try { try {
var subnet = new IPv4Subnet(address, prefix); var subnet = new IPv4Subnet(address, prefix);
throw new AssertionError(); throw new AssertionError();
} catch (Exception e) { } catch (Throwable e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class); assertThat(e).isInstanceOf(IllegalArgumentException.class);
} }
}); });
@ -38,7 +38,7 @@ class IPv4SubnetTest {
try { try {
var subnet = new IPv4Subnet(address, prefix); var subnet = new IPv4Subnet(address, prefix);
throw new AssertionError(); throw new AssertionError();
} catch (Exception e) { } catch (Throwable e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class); assertThat(e).isInstanceOf(IllegalArgumentException.class);
} }
}); });
@ -49,7 +49,7 @@ class IPv4SubnetTest {
try { try {
var subnet = new IPv4Subnet(address, prefix); var subnet = new IPv4Subnet(address, prefix);
throw new AssertionError(); throw new AssertionError();
} catch (Exception e) { } catch (Throwable e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class); assertThat(e).isInstanceOf(IllegalArgumentException.class);
} }
}); });

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.kirillius</groupId>
<artifactId>pf-sdn</artifactId>
<version>0.1.0.0</version>
</parent>
<artifactId>launcher</artifactId>
</project>

12
pom.xml
View File

@ -10,9 +10,9 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>web-client</module> <module>web-client</module>
<module>launcher</module>
<module>core</module> <module>core</module>
<module>web-server</module> <module>web-server</module>
<module>app</module>
</modules> </modules>
<properties> <properties>
@ -94,12 +94,7 @@
<artifactId>hibernate-commons</artifactId> <artifactId>hibernate-commons</artifactId>
<version>2.2.0.0</version> <version>2.2.0.0</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server --> <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server -->
<dependency> <dependency>
@ -112,9 +107,10 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.34</version> <version>1.18.40</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>