hotfix BGP API fallback
This commit is contained in:
parent
b94dbb98a4
commit
ef060548e3
|
|
@ -8,6 +8,7 @@ import ru.kirillius.pf.sdn.External.API.Components.TDNS;
|
||||||
import ru.kirillius.pf.sdn.External.API.GitSubscription;
|
import ru.kirillius.pf.sdn.External.API.GitSubscription;
|
||||||
import ru.kirillius.pf.sdn.External.API.HEInfoProvider;
|
import ru.kirillius.pf.sdn.External.API.HEInfoProvider;
|
||||||
import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription;
|
import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription;
|
||||||
|
import ru.kirillius.pf.sdn.External.API.RIPEInfoProvider;
|
||||||
import ru.kirillius.pf.sdn.core.*;
|
import ru.kirillius.pf.sdn.core.*;
|
||||||
import ru.kirillius.pf.sdn.core.Auth.AuthManager;
|
import ru.kirillius.pf.sdn.core.Auth.AuthManager;
|
||||||
import ru.kirillius.pf.sdn.core.Auth.TokenService;
|
import ru.kirillius.pf.sdn.core.Auth.TokenService;
|
||||||
|
|
@ -80,7 +81,10 @@ public class App implements Context, Closeable {
|
||||||
*/
|
*/
|
||||||
private ServiceManager loadServiceManager() {
|
private ServiceManager loadServiceManager() {
|
||||||
var manager = new ServiceManager(this, List.of(AuthManager.class, ComponentHandlerService.class, TokenService.class, AppUpdateService.class, BGPInfoService.class, NetworkingService.class, SubscriptionService.class, ResourceUpdateService.class, WebService.class));
|
var manager = new ServiceManager(this, List.of(AuthManager.class, ComponentHandlerService.class, TokenService.class, AppUpdateService.class, BGPInfoService.class, NetworkingService.class, SubscriptionService.class, ResourceUpdateService.class, WebService.class));
|
||||||
manager.getService(BGPInfoService.class).setProvider(new HEInfoProvider());
|
var infoService = manager.getService(BGPInfoService.class);
|
||||||
|
infoService.addProvider(new HEInfoProvider());
|
||||||
|
infoService.addProvider(new RIPEInfoProvider());
|
||||||
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +127,6 @@ public class App implements Context, Closeable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var versions = Arrays.stream(listedFiles)
|
var versions = Arrays.stream(listedFiles)
|
||||||
.filter(file -> file.getName().endsWith(AppUpdateService.EXTENSION))
|
.filter(file -> file.getName().endsWith(AppUpdateService.EXTENSION))
|
||||||
.collect(Collectors.toMap(file -> file.getName().replace(Pattern.quote(AppUpdateService.EXTENSION), ""), file -> file));
|
.collect(Collectors.toMap(file -> file.getName().replace(Pattern.quote(AppUpdateService.EXTENSION), ""), file -> file));
|
||||||
|
|
@ -169,7 +172,6 @@ public class App implements Context, Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the application to exit, optionally restarting.
|
* Requests the application to exit, optionally restarting.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
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.ASInfoProvider;
|
||||||
|
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.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves ASN prefix information from RIPE public API.
|
||||||
|
*/
|
||||||
|
public class RIPEInfoProvider implements ASInfoProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches IPv4 prefixes announced by the specified autonomous system.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public List<IPv4Subnet> getPrefixes(int as) {
|
||||||
|
try (var client = HttpClient.newHttpClient()) {
|
||||||
|
var request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + as))
|
||||||
|
.header("Accept", "application/json").timeout(Duration.ofMinutes(2)).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPQueryInfo queryAddress(String address) {
|
||||||
|
var request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://stat.ripe.net/data/network-info/data.json?resource=" + address))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (var client = HttpClient.newHttpClient()) {
|
||||||
|
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
SystemLogger.error("Unable to get info about IP " + address + ", status " + response.statusCode(), CTX);
|
||||||
|
return emptyIPInfo();
|
||||||
|
}
|
||||||
|
var json = new JSONObject(new JSONTokener(response.body()));
|
||||||
|
var data = json.getJSONObject("data");
|
||||||
|
var asns = data.getJSONArray("asns");
|
||||||
|
var prefix = data.getString("prefix");
|
||||||
|
|
||||||
|
var asnList = new ArrayList<Integer>();
|
||||||
|
asns.forEach(obj -> asnList.add(Integer.parseInt(obj.toString())));
|
||||||
|
|
||||||
|
return IPQueryInfo.builder().prefixes(Collections.singletonList(new IPv4Subnet(prefix))).ASN(asnList).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
SystemLogger.error("Failed to query info about IP " + address + ": " + e.getMessage(), CTX);
|
||||||
|
return emptyIPInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses IPv4 prefix entries from the Hurricane Electric API response.
|
||||||
|
*/
|
||||||
|
private static @NotNull ArrayList<IPv4Subnet> getIPv4Subnets(InputStream inputStream) {
|
||||||
|
var json = new JSONObject(new JSONTokener(inputStream));
|
||||||
|
var data = json.getJSONObject("data");
|
||||||
|
var array = data.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 = RIPEInfoProvider.class.getSimpleName();
|
||||||
|
|
||||||
|
private static IPQueryInfo emptyIPInfo() {
|
||||||
|
return IPQueryInfo.builder()
|
||||||
|
.ASN(Collections.emptyList())
|
||||||
|
.prefixes(Collections.emptyList())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,59 @@
|
||||||
package ru.kirillius.pf.sdn.core.Networking;
|
package ru.kirillius.pf.sdn.core.Networking;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import ru.kirillius.pf.sdn.core.AppService;
|
import ru.kirillius.pf.sdn.core.AppService;
|
||||||
import ru.kirillius.pf.sdn.core.Context;
|
import ru.kirillius.pf.sdn.core.Context;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates asynchronous retrieval of BGP prefix data through a configurable provider.
|
* Delegates asynchronous retrieval of BGP prefix data through a configurable provider.
|
||||||
*/
|
*/
|
||||||
public class BGPInfoService extends AppService {
|
public class BGPInfoService extends AppService {
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
public void addProvider(ASInfoProvider provider) {
|
||||||
|
synchronized (providers) {
|
||||||
|
providers.add(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fallbackNextProvider() {
|
||||||
|
synchronized (providers) {
|
||||||
|
if (provider == null) {
|
||||||
|
provider = providers.getFirst();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < providers.size(); i++) {
|
||||||
|
var provider = providers.get(i);
|
||||||
|
if (provider == this.provider) {
|
||||||
|
var nextIndex = i + 1;
|
||||||
|
if (nextIndex >= providers.size()) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
this.provider = providers.get(nextIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<ASInfoProvider> providers = new ArrayList<>();
|
||||||
|
|
||||||
|
public ASInfoProvider getProvider() {
|
||||||
|
if (provider == null) {
|
||||||
|
if (providers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fallbackNextProvider();
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ASInfoProvider provider;
|
private ASInfoProvider provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,16 +63,15 @@ public class BGPInfoService extends AppService {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits a request to obtain prefixes for the provided autonomous system number.
|
* Submits a request to obtain prefixes for the provided autonomous system number.
|
||||||
*/
|
*/
|
||||||
public Future<List<IPv4Subnet>> getPrefixes(int as) {
|
public Future<List<IPv4Subnet>> getPrefixes(int as) {
|
||||||
return executor.submit(() -> provider.getPrefixes(as));
|
return executor.submit(() -> getProvider().getPrefixes(as));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<ASInfoProvider.IPQueryInfo> getAddressInfo(String address) {
|
public Future<ASInfoProvider.IPQueryInfo> getAddressInfo(String address) {
|
||||||
return executor.submit(() -> provider.queryAddress(address));
|
return executor.submit(() -> getProvider().queryAddress(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,5 +82,4 @@ public class BGPInfoService extends AppService {
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,18 +180,28 @@ public class NetworkingService extends AppService {
|
||||||
private void fetchPrefixes(List<Integer> systems) {
|
private void fetchPrefixes(List<Integer> systems) {
|
||||||
var service = context.getServiceManager().getService(BGPInfoService.class);
|
var service = context.getServiceManager().getService(BGPInfoService.class);
|
||||||
systems.forEach(as -> {
|
systems.forEach(as -> {
|
||||||
SystemLogger.message("Fetching AS" + as + " prefixes...", CTX);
|
var currentProvider = service.getProvider();
|
||||||
var future = service.getPrefixes(as);
|
var done = false;
|
||||||
|
do {
|
||||||
|
SystemLogger.message("Fetching AS" + as + " prefixes...", CTX);
|
||||||
|
var future = service.getPrefixes(as);
|
||||||
|
|
||||||
while (!future.isDone() && !future.isCancelled()) {
|
while (!future.isDone() && !future.isCancelled()) {
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var iPv4Subnets = future.get();
|
var iPv4Subnets = future.get();
|
||||||
prefixCache.put(as, iPv4Subnets);
|
prefixCache.put(as, iPv4Subnets);
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
done = true;
|
||||||
SystemLogger.error("Error happened while fetching AS" + as + " prefixes. Trying to use cached prefixes...", CTX, e);
|
break;
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
service.fallbackNextProvider();
|
||||||
|
SystemLogger.error("Error happened while fetching AS" + as + " prefixes. Trying to use fallback BGP info provider:" + service.getProvider().getClass().getSimpleName(), CTX, e);
|
||||||
|
}
|
||||||
|
} while (service.getProvider() != currentProvider);
|
||||||
|
if (!done) {
|
||||||
|
SystemLogger.error("Unable to fetch AS" + as + " prefixes from all providers. Trying to use cache...", CTX);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue