Compare commits
1 Commits
feature/re
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
52f57ea7c3 |
|
|
@ -14,7 +14,6 @@ import ru.kirillius.pf.sdn.core.Auth.AuthManager;
|
|||
import ru.kirillius.pf.sdn.core.Auth.TokenService;
|
||||
import ru.kirillius.pf.sdn.core.Networking.BGPInfoService;
|
||||
import ru.kirillius.pf.sdn.core.Networking.NetworkingService;
|
||||
import ru.kirillius.pf.sdn.core.Networking.ResolverService;
|
||||
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
||||
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
|
||||
import ru.kirillius.pf.sdn.core.Util.Wait;
|
||||
|
|
@ -81,7 +80,7 @@ public class App implements Context, Closeable {
|
|||
* Instantiates all application services and performs initial wiring.
|
||||
*/
|
||||
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, ResolverService.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));
|
||||
var infoService = manager.getService(BGPInfoService.class);
|
||||
infoService.addProvider(new HEInfoProvider());
|
||||
infoService.addProvider(new RIPEInfoProvider());
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ public class SubscriptionManager implements RPC {
|
|||
@JRPCArgument(name = "ASN") JSONArray ASN,
|
||||
@JRPCArgument(name = "subnets") JSONArray subnets,
|
||||
@JRPCArgument(name = "addresses") JSONArray addresses,
|
||||
@JRPCArgument(name = "autoResolve") boolean autoResolve,
|
||||
@JRPCArgument(name = "storage") String storage,
|
||||
@JRPCArgument(name = "subscribe") boolean subscribe) throws IOException {
|
||||
var repositoryConfig = findLocalRepo(storage);
|
||||
|
|
@ -114,7 +113,6 @@ public class SubscriptionManager implements RPC {
|
|||
.subnets(merged)
|
||||
.domains(domainList)
|
||||
.description(description)
|
||||
.resolveDomains(autoResolve)
|
||||
.build()
|
||||
).toString(2));
|
||||
}
|
||||
|
|
@ -126,6 +124,7 @@ public class SubscriptionManager implements RPC {
|
|||
context.getServiceManager().getService(SubscriptionService.class).triggerUpdate();
|
||||
}
|
||||
|
||||
|
||||
private RepositoryConfig findLocalRepo(String storage) throws FileNotFoundException {
|
||||
var optional = context.getConfig().getSubscriptions().stream().filter(repositoryConfig -> repositoryConfig.getType().equals(LocalFilesystemSubscription.class) && repositoryConfig.getName().equals(storage)).findFirst();
|
||||
if (optional.isEmpty()) {
|
||||
|
|
@ -135,6 +134,7 @@ public class SubscriptionManager implements RPC {
|
|||
return optional.get();
|
||||
}
|
||||
|
||||
|
||||
@JRPCMethod
|
||||
@ProtectedMethod
|
||||
public void removeLocalResourceFile(
|
||||
|
|
|
|||
|
|
@ -40,37 +40,6 @@ public class Config {
|
|||
@JSONProperty
|
||||
private volatile boolean cachingAS = true;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JSONProperty(required = false)
|
||||
private volatile boolean cachingDomains = true;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JSONArrayProperty(type = String.class, required = false)
|
||||
private volatile List<String> domainResolvers = List.of("8.8.8.8", "77.88.8.8");
|
||||
|
||||
/**
|
||||
* Time in minutes
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JSONProperty(required = false)
|
||||
private volatile int domainLookupInterval = 5;
|
||||
|
||||
// @Getter
|
||||
// @Setter
|
||||
// @JSONProperty(required = false)
|
||||
// private volatile int autoLookupPrefixLength = 24;
|
||||
|
||||
/**
|
||||
* Time in hours
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JSONProperty(required = false)
|
||||
private volatile int domainsTimeToLive = 48;
|
||||
|
||||
/**
|
||||
* Update ASN prefixes every N hours
|
||||
*/
|
||||
|
|
@ -93,6 +62,7 @@ public class Config {
|
|||
* Path where to store temporary data
|
||||
*/
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@JSONProperty
|
||||
|
|
|
|||
|
|
@ -34,11 +34,6 @@ public class NetworkResourceBundle {
|
|||
@JSONArrayProperty(type = String.class)
|
||||
private List<String> domains = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JSONProperty(required = false)
|
||||
private boolean resolveDomains = false;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof NetworkResourceBundle that)) return false;
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
|
|||
import ru.kirillius.pf.sdn.core.Util.IPv4Util;
|
||||
import ru.kirillius.utils.logging.SystemLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
|
@ -27,20 +22,19 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
* Builds the effective set of network resources by combining subscriptions, caches, and filters.
|
||||
*/
|
||||
public class NetworkingService extends AppService {
|
||||
private final static String CTX = NetworkingService.class.getSimpleName();
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final File domainCacheFile;
|
||||
private final File asCacheFile;
|
||||
private final static String CTX = NetworkingService.class.getSimpleName();
|
||||
|
||||
private final File cacheFile;
|
||||
private final EventListener<NetworkResourceBundle> resourceUpdateSubscription;
|
||||
private final EventListener<ContextEventsHandler.ConfigChangeContext> configChangeSubscription;
|
||||
private final AtomicReference<Future<?>> updateProcess = new AtomicReference<>();
|
||||
@Getter
|
||||
private final NetworkResourceBundle inputResources = new NetworkResourceBundle();
|
||||
@Getter
|
||||
private final NetworkResourceBundle outputResources = new NetworkResourceBundle();
|
||||
|
||||
private final Map<Integer, List<IPv4Subnet>> prefixCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, ResolverCacheEntry> domainCache = new ConcurrentHashMap<>();
|
||||
private void rebuildInputs() {
|
||||
inputResources.clear();
|
||||
inputResources.add(context.getConfig().getCustomResources());
|
||||
inputResources.add(context.getServiceManager().getService(SubscriptionService.class).getOutputResources());
|
||||
triggerUpdate(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the networking service, wiring subscriptions and restoring cached state.
|
||||
|
|
@ -57,71 +51,26 @@ public class NetworkingService extends AppService {
|
|||
NetworkingService.this.rebuildInputs();
|
||||
}
|
||||
});
|
||||
domainCacheFile = new File(context.getConfig().getCacheDirectory(), "domain-cache.json");
|
||||
asCacheFile = new File(context.getConfig().getCacheDirectory(), "as-cache.json");
|
||||
if (asCacheFile.exists() && context.getConfig().isCachingAS()) {
|
||||
cacheFile = new File(context.getConfig().getCacheDirectory(), "as-cache.json");
|
||||
if (cacheFile.exists() && context.getConfig().isCachingAS()) {
|
||||
SystemLogger.message("Loading as cache file", CTX);
|
||||
try (var is = new FileInputStream(asCacheFile)) {
|
||||
try (var is = new FileInputStream(cacheFile)) {
|
||||
var json = new JSONObject(new JSONTokener(is));
|
||||
json.keySet().forEach(key -> {
|
||||
var as = Integer.parseInt(key);
|
||||
prefixCache.put(as, JSONUtility.deserializeCollection(json.getJSONArray(key), IPv4Subnet.class, null).toList());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
SystemLogger.error("Failed to load as cache file " + asCacheFile.getPath(), CTX, e);
|
||||
SystemLogger.error("Failed to load as cache file " + cacheFile.getPath(), CTX, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (domainCacheFile.exists() && context.getConfig().isCachingAS()) {
|
||||
SystemLogger.message("Loading domain cache file", CTX);
|
||||
try (var is = new FileInputStream(domainCacheFile)) {
|
||||
var json = new JSONObject(new JSONTokener(is));
|
||||
json.keySet().forEach(host -> {
|
||||
domainCache.put(host, JSONUtility.deserializeStructure(json.getJSONObject(host), ResolverCacheEntry.class));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
SystemLogger.error("Failed to load domain cache file " + asCacheFile.getPath(), CTX, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void performAutoresolve() {
|
||||
var current = new HashSet<IPv4Subnet>();
|
||||
domainCache.forEach((host, entry) -> current.addAll(entry.getAddresses().keySet()));
|
||||
|
||||
resolveDomains(List.copyOf(context.getServiceManager().getService(SubscriptionService.class).getAutoResolvingDomains()));
|
||||
|
||||
var resolved = new HashSet<IPv4Subnet>();
|
||||
domainCache.forEach((host, entry) -> resolved.addAll(entry.getAddresses().keySet()));
|
||||
|
||||
if (resolved.size() != current.size()) {
|
||||
rebuildInputs();
|
||||
} else {
|
||||
var updated = false;
|
||||
for (var subnet : resolved) {
|
||||
if (!current.contains(subnet)) {
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
rebuildInputs();
|
||||
}
|
||||
}
|
||||
|
||||
if(!context.getConfig().isCachingDomains()){
|
||||
domainCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildInputs() {
|
||||
inputResources.clear();
|
||||
inputResources.add(context.getConfig().getCustomResources());
|
||||
inputResources.add(context.getServiceManager().getService(SubscriptionService.class).getOutputResources());
|
||||
triggerUpdate(false);
|
||||
}
|
||||
private final AtomicReference<Future<?>> updateProcess = new AtomicReference<>();
|
||||
@Getter
|
||||
private final NetworkResourceBundle inputResources = new NetworkResourceBundle();
|
||||
@Getter
|
||||
private final NetworkResourceBundle outputResources = new NetworkResourceBundle();
|
||||
|
||||
/**
|
||||
* Indicates whether an update job is currently executing.
|
||||
|
|
@ -131,6 +80,8 @@ public class NetworkingService extends AppService {
|
|||
return future != null && !future.isDone() && !future.isCancelled();
|
||||
}
|
||||
|
||||
private final Map<Integer, List<IPv4Subnet>> prefixCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Schedules an update of network resources, optionally ignoring cached prefixes.
|
||||
*/
|
||||
|
|
@ -145,6 +96,50 @@ public class NetworkingService extends AppService {
|
|||
SystemLogger.message("Update is started", CTX);
|
||||
var config = context.getConfig();
|
||||
var filteredResources = config.getFilteredResources();
|
||||
var asn = new ArrayList<>(inputResources.getASN());
|
||||
asn.removeAll(filteredResources.getASN());
|
||||
|
||||
var asnToFetch = new ArrayList<>(asn);
|
||||
if (!ignoreCache) {
|
||||
asnToFetch.removeAll(prefixCache.keySet());
|
||||
}
|
||||
|
||||
fetchPrefixes(asnToFetch);
|
||||
|
||||
if (config.isCachingAS()) {
|
||||
try (var os = new FileOutputStream(cacheFile)) {
|
||||
var json = new JSONObject();
|
||||
prefixCache.forEach((key, asnList) -> {
|
||||
json.put(String.valueOf(key), JSONUtility.serializeCollection(asnList, IPv4Subnet.class, null));
|
||||
});
|
||||
os.write(json.toString().getBytes());
|
||||
} catch (IOException e) {
|
||||
SystemLogger.error("Unable to write file " + cacheFile.getPath(), CTX, e);
|
||||
}
|
||||
}
|
||||
|
||||
var subnets = new HashSet<>(inputResources.getSubnets());
|
||||
asn.forEach(n -> {
|
||||
var cached = prefixCache.get(n);
|
||||
if (cached == null) {
|
||||
return;
|
||||
}
|
||||
subnets.addAll(cached);
|
||||
SystemLogger.message("Using " + cached.size() + " subnets from AS" + n, CTX);
|
||||
});
|
||||
filteredResources.getSubnets().forEach(subnets::remove);
|
||||
|
||||
SystemLogger.message("Trying to summary " + subnets.size() + " subnets...", CTX);
|
||||
|
||||
var merged = IPv4Util.summarySubnets(subnets, config.getMergeSubnetsWithUsage());
|
||||
var unmerged = new AtomicInteger();
|
||||
subnets.forEach(subnet -> {
|
||||
if (!merged.getMergedSubnets().contains(subnet)) {
|
||||
unmerged.getAndIncrement();
|
||||
}
|
||||
});
|
||||
|
||||
SystemLogger.message(subnets.size() + " subnets has been summarized and merged to " + merged.getResult().size() + " new subnets. Unmerged: " + unmerged.get(), CTX);
|
||||
|
||||
var domains = new HashSet<>(inputResources.getDomains());
|
||||
filteredResources.getDomains().forEach(domains::remove);
|
||||
|
|
@ -162,75 +157,6 @@ public class NetworkingService extends AppService {
|
|||
|
||||
domains.removeAll(domainsToRemove);
|
||||
|
||||
var asn = new ArrayList<>(inputResources.getASN());
|
||||
asn.removeAll(filteredResources.getASN());
|
||||
|
||||
var asnToFetch = new ArrayList<>(asn);
|
||||
if (!ignoreCache) {
|
||||
asnToFetch.removeAll(prefixCache.keySet());
|
||||
}
|
||||
|
||||
fetchPrefixes(asnToFetch);
|
||||
|
||||
if (config.isCachingAS()) {
|
||||
try (var os = new FileOutputStream(asCacheFile)) {
|
||||
var json = new JSONObject();
|
||||
prefixCache.forEach((key, asnList) -> {
|
||||
json.put(String.valueOf(key), JSONUtility.serializeCollection(asnList, IPv4Subnet.class, null));
|
||||
});
|
||||
os.write(json.toString().getBytes());
|
||||
} catch (IOException e) {
|
||||
SystemLogger.error("Unable to write file " + asCacheFile.getPath(), CTX, e);
|
||||
}
|
||||
}
|
||||
|
||||
resolveDomains(List.copyOf(context.getServiceManager().getService(SubscriptionService.class).getAutoResolvingDomains()));
|
||||
|
||||
if (config.isCachingDomains()) {
|
||||
try (var os = new FileOutputStream(domainCacheFile)) {
|
||||
var json = new JSONObject();
|
||||
domainCache.forEach((key, entry) -> {
|
||||
var serialized = JSONUtility.serializeStructure(entry);
|
||||
json.put(String.valueOf(key), serialized);
|
||||
});
|
||||
os.write(json.toString().getBytes());
|
||||
} catch (IOException e) {
|
||||
SystemLogger.error("Unable to write file " + domainCacheFile.getPath(), CTX, e);
|
||||
}
|
||||
}
|
||||
|
||||
var subnets = new HashSet<>(inputResources.getSubnets());
|
||||
asn.forEach(n -> {
|
||||
var cached = prefixCache.get(n);
|
||||
if (cached == null) {
|
||||
return;
|
||||
}
|
||||
subnets.addAll(cached);
|
||||
SystemLogger.message("Using " + cached.size() + " subnets from AS" + n, CTX);
|
||||
});
|
||||
|
||||
//добавляем отрезолвенные домены
|
||||
domains.forEach(domain -> {
|
||||
var entry = domainCache.get(domain);
|
||||
if (entry != null) {
|
||||
subnets.addAll(entry.getAddresses().keySet());
|
||||
}
|
||||
});
|
||||
|
||||
filteredResources.getSubnets().forEach(subnets::remove);
|
||||
|
||||
SystemLogger.message("Trying to summary " + subnets.size() + " subnets...", CTX);
|
||||
|
||||
var merged = IPv4Util.summarySubnets(subnets, config.getMergeSubnetsWithUsage());
|
||||
var unmerged = new AtomicInteger();
|
||||
subnets.forEach(subnet -> {
|
||||
if (!merged.getMergedSubnets().contains(subnet)) {
|
||||
unmerged.getAndIncrement();
|
||||
}
|
||||
});
|
||||
|
||||
SystemLogger.message(subnets.size() + " subnets has been summarized and merged to " + merged.getResult().size() + " new subnets. Unmerged: " + unmerged.get(), CTX);
|
||||
|
||||
outputResources.setASN(Collections.unmodifiableList(asn));
|
||||
outputResources.setSubnets(merged.getResult());
|
||||
outputResources.setDomains(domains.stream().toList());
|
||||
|
|
@ -248,48 +174,6 @@ public class NetworkingService extends AppService {
|
|||
}));
|
||||
}
|
||||
|
||||
private void resolveDomains(List<String> domains) {
|
||||
var resolvedSubnets = new ArrayList<IPv4Subnet>();
|
||||
var resolver = context.getServiceManager().getService(ResolverService.class);
|
||||
for (var domain : domains) {
|
||||
var task = resolver.resolve(domain);
|
||||
while (!task.isDone() && !task.isCancelled()) {
|
||||
Thread.yield();
|
||||
}
|
||||
try {
|
||||
var subnets = task.get();
|
||||
var entry = domainCache.get(domain);
|
||||
|
||||
if(entry == null) {
|
||||
entry = new ResolverCacheEntry();
|
||||
domainCache.put(domain, entry);
|
||||
}
|
||||
|
||||
var addresses = entry.getAddresses();
|
||||
entry.setLastUpdate(Instant.now());
|
||||
subnets.forEach(subnet -> addresses.put(subnet, Instant.now()));
|
||||
resolvedSubnets.addAll(domainCache.get(domain).getAddresses().keySet());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
SystemLogger.error("Error happened while resolving domain " + domain, CTX, e);
|
||||
}
|
||||
}
|
||||
|
||||
//remove old entries
|
||||
for (var domain : domainCache.keySet()) {
|
||||
var entry = domainCache.get(domain);
|
||||
var addresses = entry.getAddresses();
|
||||
for (var subnet : addresses.keySet()) {
|
||||
var time = addresses.get(subnet);
|
||||
if (time.isBefore(Instant.now().minus(context.getConfig().getDomainsTimeToLive(), ChronoUnit.HOURS))) {
|
||||
addresses.remove(subnet);
|
||||
}
|
||||
}
|
||||
if (addresses.isEmpty()) {
|
||||
domainCache.remove(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches prefixes for the given autonomous systems and stores them in the cache.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package ru.kirillius.pf.sdn.core.Networking;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import ru.kirillius.json.JSONMapProperty;
|
||||
import ru.kirillius.json.JSONProperty;
|
||||
import ru.kirillius.json.JSONSerializable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@JSONSerializable
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ResolverCacheEntry {
|
||||
@JSONProperty
|
||||
private Instant lastUpdate = Instant.now();
|
||||
@JSONMapProperty(keyType = IPv4Subnet.class, valueType = Instant.class)
|
||||
private Map<IPv4Subnet, Instant> addresses = new HashMap<>();
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package ru.kirillius.pf.sdn.core.Networking;
|
||||
|
||||
import ru.kirillius.pf.sdn.core.AppService;
|
||||
import ru.kirillius.pf.sdn.core.Context;
|
||||
import ru.kirillius.pf.sdn.core.Util.DomainUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class ResolverService extends AppService {
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final static String CTX = ResolverService.class.getSimpleName();
|
||||
|
||||
public Future<List<IPv4Subnet>> resolve(String host) {
|
||||
return executor.submit(() -> {
|
||||
var resolved = new ArrayList<IPv4Subnet>();
|
||||
for (var domainResolver : context.getConfig().getDomainResolvers()) {
|
||||
DomainUtil.lookup(host, domainResolver).stream().map(addr -> new IPv4Subnet(addr, 32)).forEach(resolved::add);
|
||||
}
|
||||
return resolved;
|
||||
});
|
||||
}
|
||||
|
||||
public ResolverService(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event subscriptions and shuts down the executor.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ public class ResourceUpdateService extends AppService {
|
|||
updateThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interrupts the update thread and stops scheduling tasks.
|
||||
*/
|
||||
|
|
@ -75,11 +76,6 @@ public class ResourceUpdateService extends AppService {
|
|||
Wait.when(subscriptionManager::isUpdatingNow);
|
||||
}
|
||||
|
||||
if (uptime % context.getConfig().getDomainLookupInterval() == 0) {
|
||||
SystemLogger.message("Resolving domains...", CTX);
|
||||
context.getServiceManager().getService(NetworkingService.class).performAutoresolve();
|
||||
}
|
||||
|
||||
if (config.getUpdateASInterval() > 0 && uptime % (config.getUpdateASInterval() * 60L) == 0) {
|
||||
SystemLogger.message("Updating cached AS", CTX);
|
||||
var networkManager = context.getServiceManager().getService(NetworkingService.class);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import ru.kirillius.utils.logging.SystemLogger;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
|
|
@ -19,14 +21,14 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
public class SubscriptionService extends AppService {
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
|
||||
private final Map<Class<? extends SubscriptionProvider>, SubscriptionProvider> providerCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
private final AtomicReference<Future<?>> updateProcess = new AtomicReference<>();
|
||||
|
||||
@Getter
|
||||
private final NetworkResourceBundle outputResources = new NetworkResourceBundle();
|
||||
@Getter
|
||||
private final List<String> autoResolvingDomains = new CopyOnWriteArrayList<>();
|
||||
|
||||
public SubscriptionService(Context context) {
|
||||
super(context);
|
||||
|
|
@ -43,6 +45,7 @@ public class SubscriptionService extends AppService {
|
|||
@Getter
|
||||
private final Map<String, NetworkResourceBundle> availableResources = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends SubscriptionProvider> T getProvider(Class<T> providerType) {
|
||||
if (!providerCache.containsKey(providerType)) {
|
||||
|
|
@ -59,9 +62,6 @@ public class SubscriptionService extends AppService {
|
|||
return;
|
||||
}
|
||||
updateProcess.set(executor.submit(() -> {
|
||||
|
||||
|
||||
|
||||
var available = new HashMap<String, NetworkResourceBundle>();
|
||||
|
||||
var bundle = new NetworkResourceBundle();
|
||||
|
|
@ -90,12 +90,6 @@ public class SubscriptionService extends AppService {
|
|||
availableResources.putAll(available);
|
||||
outputResources.clear();
|
||||
outputResources.add(bundle);
|
||||
|
||||
available.values().forEach(b -> {
|
||||
if (b.isResolveDomains()) {
|
||||
autoResolvingDomains.addAll(b.getDomains());
|
||||
}
|
||||
});
|
||||
try {
|
||||
context.getEventsHandler().getSubscriptionsUpdateEvent().invoke(outputResources);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -106,6 +100,7 @@ public class SubscriptionService extends AppService {
|
|||
|
||||
private final static String CTX = SubscriptionService.class.getSimpleName();
|
||||
|
||||
|
||||
/**
|
||||
* Shuts down the executor used for update tasks.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public class IPv4Util {
|
|||
var overlapped = new ArrayList<IPv4Subnet>();
|
||||
var orderedByPrefix = result.stream().sorted(Comparator.comparing(IPv4Subnet::getPrefixLength)).toList();
|
||||
orderedByPrefix.stream()
|
||||
.filter(subnet -> subnet.getPrefixLength() > 32)
|
||||
.filter(subnet -> subnet.getPrefixLength() < 32)
|
||||
.forEach(parent -> orderedByPrefix.forEach(subnet -> {
|
||||
if (subnet.equals(parent)) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package ru.kirillius.pf.sdn.core.Networking;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
class ResBundleTest {
|
||||
|
||||
@Test
|
||||
void testInit() {
|
||||
var b = new ResolverCacheEntry();
|
||||
b.getAddresses().put(new IPv4Subnet("127.0.0.1/32"), Instant.now());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -31,27 +31,6 @@ class IPv4UtilTest {
|
|||
|
||||
|
||||
|
||||
//subnets.forEach(System.out::println);
|
||||
|
||||
var merged = IPv4Util.summarySubnets(subnets, 51).getResult();
|
||||
|
||||
merged.forEach(System.out::println);
|
||||
|
||||
assertThat(merged).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void summarySubnetsShishanyaCase() {
|
||||
var subnets = new ArrayList<IPv4Subnet>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
subnets.add(new IPv4Subnet("8.6.112.0/32"));
|
||||
subnets.add(new IPv4Subnet("8.6.112.0/24"));
|
||||
|
||||
|
||||
//subnets.forEach(System.out::println);
|
||||
|
||||
var merged = IPv4Util.summarySubnets(subnets, 51).getResult();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ const FIELD_IDS = {
|
|||
domainsInput: 'local-storages-domains',
|
||||
asnInput: 'local-storages-asn',
|
||||
subnetsInput: 'local-storages-subnets',
|
||||
resolveDomainsCheckbox: 'local-storages-resolve-domains',
|
||||
modalStatus: 'local-storages-modal-status',
|
||||
modalSaveButton: 'local-storages-save-btn',
|
||||
modalCancelButton: 'local-storages-cancel-btn'
|
||||
|
|
@ -254,7 +253,6 @@ function openModal(mode, resourceName = '') {
|
|||
const $domainsInput = $(SELECTORS.domainsInput);
|
||||
const $asnInput = $(SELECTORS.asnInput);
|
||||
const $subnetsInput = $(SELECTORS.subnetsInput);
|
||||
const $resolveDomainsCheckbox = $(SELECTORS.resolveDomainsCheckbox);
|
||||
const $saveButton = $(SELECTORS.modalSaveButton);
|
||||
|
||||
setModalStatus('', '');
|
||||
|
|
@ -268,7 +266,6 @@ function openModal(mode, resourceName = '') {
|
|||
$domainsInput.val(joinLines(resource.domains));
|
||||
$asnInput.val(joinLines(resource.ASN));
|
||||
$subnetsInput.val(joinLines(resource.subnets));
|
||||
$resolveDomainsCheckbox.prop('checked', resource.resolveDomains === true);
|
||||
} else {
|
||||
$title.text('Создание ресурса');
|
||||
$nameInput.val('').prop('disabled', false);
|
||||
|
|
@ -276,7 +273,6 @@ function openModal(mode, resourceName = '') {
|
|||
$domainsInput.val('');
|
||||
$asnInput.val('');
|
||||
$subnetsInput.val('');
|
||||
$resolveDomainsCheckbox.prop('checked', false);
|
||||
}
|
||||
|
||||
$saveButton.prop('disabled', false).text('Сохранить');
|
||||
|
|
@ -318,7 +314,6 @@ async function handleModalSubmit(event) {
|
|||
const $domainsInput = $(SELECTORS.domainsInput);
|
||||
const $asnInput = $(SELECTORS.asnInput);
|
||||
const $subnetsInput = $(SELECTORS.subnetsInput);
|
||||
const $resolveDomainsCheckbox = $(SELECTORS.resolveDomainsCheckbox);
|
||||
|
||||
const nameValue = ($nameInput.val() || '').trim();
|
||||
const validationError = validateName(nameValue);
|
||||
|
|
@ -355,7 +350,6 @@ async function handleModalSubmit(event) {
|
|||
parsedAsn.value,
|
||||
subnetsValue,
|
||||
[],
|
||||
$resolveDomainsCheckbox.is(':checked'),
|
||||
selectedRepository,
|
||||
false
|
||||
);
|
||||
|
|
@ -515,12 +509,6 @@ export const LocalStoragesPage = {
|
|||
<label for="${FIELD_IDS.subnetsInput}">Подсети (каждая с новой строки)</label>
|
||||
<textarea id="${FIELD_IDS.subnetsInput}" class="form-control" rows="4" placeholder="192.0.2.0/24"></textarea>
|
||||
</div>
|
||||
<div class="form-group-checkbox" style="margin-bottom: 0;">
|
||||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" id="${FIELD_IDS.resolveDomainsCheckbox}">
|
||||
Автоматически получать IP адреса доменов
|
||||
</label>
|
||||
</div>
|
||||
<div id="${FIELD_IDS.modalStatus}" class="error-message" style="display: none;"></div>
|
||||
<div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 8px;">
|
||||
<button type="button" id="${FIELD_IDS.modalCancelButton}" class="btn-secondary" style="padding: 10px 18px;">Отмена</button>
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ const FIELD_IDS = {
|
|||
customDomains: 'settings-custom-domains',
|
||||
filteredASN: 'settings-filtered-asn',
|
||||
filteredSubnets: 'settings-filtered-subnets',
|
||||
filteredDomains: 'settings-filtered-domains',
|
||||
cachingDomains: 'settings-caching-domains',
|
||||
domainResolvers: 'settings-domain-resolvers',
|
||||
domainLookupInterval: 'settings-domain-lookup-interval',
|
||||
domainsTimeToLive: 'settings-domains-time-to-live'
|
||||
filteredDomains: 'settings-filtered-domains'
|
||||
};
|
||||
|
||||
const CLASS_NAMES = {
|
||||
|
|
@ -214,10 +210,6 @@ function renderSettingsForm() {
|
|||
const mergeSubnets = !!getConfigValue('mergeSubnets', false);
|
||||
const displayDebuggingInfo = !!getConfigValue('displayDebuggingInfo', false);
|
||||
const mergeSubnetsWithUsage = getConfigValue('mergeSubnetsWithUsage', 80);
|
||||
const cachingDomains = !!getConfigValue('cachingDomains', false);
|
||||
const domainResolvers = getConfigValue('domainResolvers', []);
|
||||
const domainLookupInterval = getConfigValue('domainLookupInterval', 60);
|
||||
const domainsTimeToLive = getConfigValue('domainsTimeToLive', 24);
|
||||
|
||||
const customResources = getConfigValue('customResources', {});
|
||||
const filteredResources = getConfigValue('filteredResources', {});
|
||||
|
|
@ -269,24 +261,6 @@ function renderSettingsForm() {
|
|||
<label for="${FIELD_IDS.mergeSubnetsWithUsage}">Объединять подсети с заполнением >= %</label>
|
||||
<input type="number" min="51" max="99" id="${FIELD_IDS.mergeSubnetsWithUsage}" class="form-control" value="${mergeSubnetsWithUsage}">
|
||||
</div>
|
||||
|
||||
<h3 class="config-section-title" style="margin-top: 40px;">DNS Resolver</h3>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="${FIELD_IDS.cachingDomains}" ${cachingDomains ? 'checked' : ''}>
|
||||
<label for="${FIELD_IDS.cachingDomains}" style="margin-bottom: 0;">Кешировать домены</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="${FIELD_IDS.domainResolvers}">DNS серверы</label>
|
||||
<textarea id="${FIELD_IDS.domainResolvers}" class="form-control" rows="3" placeholder="8.8.8.8">${textareaFromArray(domainResolvers)}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="${FIELD_IDS.domainLookupInterval}">Интервал проверки доменов (минуты): <span id="domain-lookup-value">${domainLookupInterval}</span></label>
|
||||
<input type="range" min="1" max="${60 * 24}" id="${FIELD_IDS.domainLookupInterval}" class="form-control" value="${domainLookupInterval}" style="padding: 0;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="${FIELD_IDS.domainsTimeToLive}">Время жизни кешированных доменов (часы): <span id="domains-ttl-value">${domainsTimeToLive}</span></label>
|
||||
<input type="range" min="1" max="${24 * 7}" id="${FIELD_IDS.domainsTimeToLive}" class="form-control" value="${domainsTimeToLive}" style="padding: 0;">
|
||||
</div>
|
||||
<div style="margin-top: 30px;">
|
||||
<h4 style="margin-bottom: 15px;">Дополнительные ресурсы</h4>
|
||||
<div class="form-group">
|
||||
|
|
@ -410,8 +384,6 @@ function collectSettingsFromForm() {
|
|||
const updateSubscriptionsInterval = parseNumberInRange($(`#${FIELD_IDS.updateSubscriptionsInterval}`).val(), 1, 24, 'Интервал обновления подписок');
|
||||
const updateASInterval = parseNumberInRange($(`#${FIELD_IDS.updateASInterval}`).val(), 1, 24, 'Интервал обновления ASN');
|
||||
const mergeSubnetsWithUsage = parseNumberInRange($(`#${FIELD_IDS.mergeSubnetsWithUsage}`).val(), 51, 99, 'Процент объединения подсетей');
|
||||
const domainLookupInterval = parseNumberInRange($(`#${FIELD_IDS.domainLookupInterval}`).val(), 1, 60 * 24, 'Интервал проверки доменов');
|
||||
const domainsTimeToLive = parseNumberInRange($(`#${FIELD_IDS.domainsTimeToLive}`).val(), 1, 24 * 7, 'Время жизни кешированных доменов');
|
||||
|
||||
const httpPortValue = parseInt($(`#${FIELD_IDS.httpPort}`).val(), 10);
|
||||
if (Number.isNaN(httpPortValue) || httpPortValue < 1 || httpPortValue > 65535) {
|
||||
|
|
@ -441,10 +413,6 @@ function collectSettingsFromForm() {
|
|||
mergeSubnets: $(`#${FIELD_IDS.mergeSubnets}`).prop('checked'),
|
||||
displayDebuggingInfo: $(`#${FIELD_IDS.displayDebuggingInfo}`).prop('checked'),
|
||||
mergeSubnetsWithUsage,
|
||||
cachingDomains: $(`#${FIELD_IDS.cachingDomains}`).prop('checked'),
|
||||
domainResolvers: parseTextAreaLines($(`#${FIELD_IDS.domainResolvers}`)),
|
||||
domainLookupInterval,
|
||||
domainsTimeToLive,
|
||||
subscriptions,
|
||||
customResources,
|
||||
filteredResources
|
||||
|
|
@ -500,12 +468,6 @@ function attachEventHandlers() {
|
|||
$(this).closest(`.${CLASS_NAMES.subscriptionEntry}`).remove();
|
||||
});
|
||||
$(`#${FIELD_IDS.changePasswordButton}`).off('click').on('click', handleChangePassword);
|
||||
$(`#${FIELD_IDS.domainLookupInterval}`).off('input').on('input', function () {
|
||||
$('#domain-lookup-value').text($(this).val());
|
||||
});
|
||||
$(`#${FIELD_IDS.domainsTimeToLive}`).off('input').on('input', function () {
|
||||
$('#domains-ttl-value').text($(this).val());
|
||||
});
|
||||
}
|
||||
|
||||
function detachEventHandlers() {
|
||||
|
|
@ -513,8 +475,6 @@ function detachEventHandlers() {
|
|||
$(`#${FIELD_IDS.addSubscriptionButton}`).off('click');
|
||||
$(`#${FIELD_IDS.subscriptionsList}`).off('click', `.${CLASS_NAMES.subscriptionRemove}`);
|
||||
$(`#${FIELD_IDS.changePasswordButton}`).off('click');
|
||||
$(`#${FIELD_IDS.domainLookupInterval}`).off('input');
|
||||
$(`#${FIELD_IDS.domainsTimeToLive}`).off('input');
|
||||
}
|
||||
|
||||
export const SettingsPage = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue