From 63ac0c2d8949de27422221949e0db942a6c84725 Mon Sep 17 00:00:00 2001 From: Finn Date: Sun, 18 Jan 2026 18:34:29 +0100 Subject: [PATCH] Changed to new ProtocolVersion --- pom.xml | 4 +- .../insserver/ClassicHandler.java | 78 ------- .../insserver/DatabaseINSServer.java | 195 ++++++++++++------ .../insserver/Main.java | 7 +- .../insserver/utils/ClassicHelper.java | 38 ---- .../insserver/utils/ParsedDomain.java | 15 -- .../insserver/utils/TargetName.java | 16 -- 7 files changed, 145 insertions(+), 208 deletions(-) delete mode 100644 src/main/java/org/openautonomousconnection/insserver/ClassicHandler.java delete mode 100644 src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java delete mode 100644 src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java delete mode 100644 src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java diff --git a/pom.xml b/pom.xml index a4c3f56..858390b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openautonomousconnection INSServer - 1.0.0-BETA.1.0 + 1.0.0-BETA.1.1 Open Autonomous Connection https://open-autonomous-connection.org/ @@ -104,7 +104,7 @@ org.openautonomousconnection Protocol - 1.0.0-BETA.5.2 + 1.0.0-BETA.5.4 org.projectlombok diff --git a/src/main/java/org/openautonomousconnection/insserver/ClassicHandler.java b/src/main/java/org/openautonomousconnection/insserver/ClassicHandler.java deleted file mode 100644 index 1932678..0000000 --- a/src/main/java/org/openautonomousconnection/insserver/ClassicHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.openautonomousconnection.insserver; - -import org.openautonomousconnection.insserver.utils.ClassicHelper; -import org.openautonomousconnection.insserver.utils.ParsedDomain; -import org.openautonomousconnection.protocol.ProtocolBridge; -import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient; -import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord; -import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion; - -import java.sql.*; -import java.util.List; - -public class ClassicHandler extends ClassicHandlerINSServer { - - private ProtocolBridge bridge() { - return Main.getProtocolBridge(); - } - - @Override - public void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion) { - bridge().getLogger().info("[ClassicHandler] Message received from ClientID " + client.getConnectionHandler().getClientID() + - " (Classic Version " + protocolVersion.version + ", Client Version: " + - client.getClientVersion().toString() + "): " + message); - } - - @Override - public Classic_Domain getDomain(Classic_RequestDomain requestDomain) throws SQLException { - - ParsedDomain pd = ClassicHelper.parseDomain(requestDomain); - - // FIRST try A-record - List a = bridge().getProtocolINSServer().resolve(pd.tln, pd.name, pd.sub, INSRecordType.A); - - if (!a.isEmpty()) { - return ClassicHelper.buildClassicDomain(pd, a.get(0)); - } - - // If no A, try CNAME - List cnameList = bridge().getProtocolINSServer().resolve(pd.tln, pd.name, pd.sub, INSRecordType.CNAME); - - if (cnameList.isEmpty()) return null; // completely not found - - INSRecord cname = cnameList.get(0); - - // CNAME target = "www.example.net" or "app.dev" - String[] cnameParts = cname.value.split("\\."); - if (cnameParts.length < 2) return null; - - String cnameTln = cnameParts[cnameParts.length - 1]; - String cnameName = cnameParts[cnameParts.length - 2]; - String cnameSub = cnameParts.length > 2 - ? String.join(".", java.util.Arrays.copyOfRange(cnameParts, 0, cnameParts.length - 2)) - : null; - - List aAfterCname = bridge().getProtocolINSServer().resolve(cnameTln, cnameName, cnameSub, INSRecordType.A); - - if (aAfterCname.isEmpty()) return null; - - return ClassicHelper.buildClassicDomain(pd, aAfterCname.get(0)); - } - - @Override - public Classic_Domain ping(Classic_RequestDomain req) { - return new Classic_Domain(req.name, req.topLevelDomain, null, req.path, bridge()); - } - - @Override - public void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client) { - bridge().getLogger().warn( - "[Classic UnsupportedPacket] From client " + client.getConnectionHandler().getClientID() + - ": packet=" + className + " content=" + java.util.Arrays.toString(content) - ); - } -} diff --git a/src/main/java/org/openautonomousconnection/insserver/DatabaseINSServer.java b/src/main/java/org/openautonomousconnection/insserver/DatabaseINSServer.java index f607f9b..6042265 100644 --- a/src/main/java/org/openautonomousconnection/insserver/DatabaseINSServer.java +++ b/src/main/java/org/openautonomousconnection/insserver/DatabaseINSServer.java @@ -1,25 +1,30 @@ package org.openautonomousconnection.insserver; import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager; -import org.openautonomousconnection.insserver.utils.TargetName; import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer; import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord; import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType; +import org.openautonomousconnection.protocol.versions.v1_0_0.classic.helper.TargetName; import java.io.File; import java.io.IOException; import java.security.cert.CertificateException; import java.sql.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +/** + * Database-backed INS server. + *

+ * This implementation resolves records from the SQL schema and performs CNAME recursion (with loop detection + depth limit). + * Returned records are deterministically sorted so that callers can safely select index 0 as the "best" record. + */ public final class DatabaseINSServer extends ProtocolINSServer { private final String jdbcUrl; private final String jdbcUser; private final String jdbcPassword; - private final ConfigurationManager configurationManager; + private final ConfigurationManager config; /** * Creates a new database-backed INS server. @@ -27,38 +32,40 @@ public final class DatabaseINSServer extends ProtocolINSServer { * @throws IOException If the base server initialization fails. * @throws CertificateException If required certificate files are missing or invalid. */ - public DatabaseINSServer() throws IOException, CertificateException { + public DatabaseINSServer() throws Exception { super(new File("config.properties")); - configurationManager = new ConfigurationManager(new File("config.properties")); - configurationManager.loadProperties(); + config = new ConfigurationManager(new File("config.properties")); + config.loadProperties(); - if (!configurationManager.isSet("db.url")) { - configurationManager.set( + if (!config.isSet("db.url")) { + config.set( "db.url", "jdbc:mysql://localhost:3306/ins?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC" ); - configurationManager.saveProperties(); + config.saveProperties(); } - if (!configurationManager.isSet("db.user")) { - configurationManager.set("db.user", "username"); - configurationManager.saveProperties(); + if (!config.isSet("db.user")) { + config.set("db.user", "username"); + config.saveProperties(); } - if (!configurationManager.isSet("db.password")) { - configurationManager.set("db.password", "password"); - configurationManager.saveProperties(); + if (!config.isSet("db.password")) { + config.set("db.password", "password"); + config.saveProperties(); } - if (!configurationManager.isSet("port")) { - configurationManager.set("port", 1023); - configurationManager.saveProperties(); + if (!config.isSet("port")) { + config.set("port", 1023); + config.saveProperties(); } - jdbcUrl = configurationManager.getString("db.url"); - jdbcUser = configurationManager.getString("db.user"); - jdbcPassword = configurationManager.getString("db.password"); + jdbcUrl = config.getString("db.url"); + jdbcUser = config.getString("db.user"); + jdbcPassword = config.getString("db.password"); + + Main.getSettings().port = config.getInt("port"); } private Connection openConnection() throws SQLException { @@ -68,30 +75,42 @@ public final class DatabaseINSServer extends ProtocolINSServer { /** * Resolves a request for an INS record based on TLN, name, subname and record type. *

- * This implementation: + * The implementation: *

    *
  • Locates the corresponding InfoName in the SQL schema
  • *
  • Returns all matching {@link INSRecord} entries
  • - *
  • Performs CNAME recursion (with a depth limit) when no direct - * records for the requested type exist
  • + *
  • Performs CNAME recursion (with loop detection + depth limit) when no direct records for the requested type exist
  • + *
  • Returns deterministically sorted results (so index 0 can be used as "best record")
  • *
+ * + * @param tln The top-level name. + * @param name The InfoName. + * @param sub Optional subname, may be {@code null}. + * @param type The requested record type. + * @return Resolved records (possibly empty). */ @Override public List resolve(String tln, String name, String sub, INSRecordType type) { try { - return resolveInternal(tln, name, sub, type, 0); + List out = resolveInternal(tln, name, sub, type, 0, new HashSet<>()); + out.sort(recordComparator(type)); + return out; } catch (SQLException ex) { - getProtocolBridge().getLogger().exception("INS resolve failed for " + formatName(tln, name, sub) + " type=" + type, ex); + getProtocolBridge().getLogger().exception( + "INS resolve failed for " + formatName(tln, name, sub) + " type=" + type, + ex + ); return new ArrayList<>(); } } /** - * Resolves the TLN info site, - * which is used when a client queries {@code info.} without any sub-name. + * Resolves the TLN info site which is used when a client queries {@code info.} without any sub-name. *

- * The value is read from {@code tln.info} and must be of the form - * {@code "host:port"}. + * The value is read from {@code tln.info} and must be of the form {@code "host:port"}. + * + * @param tln The TLN name. + * @return The configured info target ("host:port") or {@code null} if not present. */ @Override public String resolveTLNInfoSite(String tln) { @@ -103,7 +122,7 @@ public final class DatabaseINSServer extends ProtocolINSServer { ps.setString(1, tln); try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) return rs.getString("info"); // expected "host:port" + if (rs.next()) return rs.getString("info"); } } catch (SQLException ex) { getProtocolBridge().getLogger().exception("Failed to resolve TLN info site for tln=" + tln, ex); @@ -118,18 +137,35 @@ public final class DatabaseINSServer extends ProtocolINSServer { } /** - * Recursive resolver with CNAME handling and depth limit. + * Internal recursive resolver with CNAME handling. + * + * @param depth Current recursion depth. + * @param visited Loop detection set of canonical names (sub.name.tln). */ - private List resolveInternal(String tln, String name, String sub, INSRecordType requestedType, int depth) throws SQLException { - final int MAX_CNAME_DEPTH = 8; + private List resolveInternal( + String tln, + String name, + String sub, + INSRecordType requestedType, + int depth, + Set visited + ) throws SQLException { + + final int MAX_CNAME_DEPTH = 16; + + String canonical = formatName(tln, name, sub).toLowerCase(Locale.ROOT); + if (!visited.add(canonical)) { + // loop detected + getProtocolBridge().getLogger().warn("CNAME loop detected for " + canonical + " type=" + requestedType); + return new ArrayList<>(); + } if (depth > MAX_CNAME_DEPTH) { - getProtocolBridge().getLogger().warn("CNAME recursion limit exceeded for " + formatName(tln, name, sub) + " type=" + requestedType); + getProtocolBridge().getLogger().warn("CNAME recursion limit exceeded for " + canonical + " type=" + requestedType); return new ArrayList<>(); } try (Connection conn = openConnection()) { - // Get IDs Integer tlnId = findTLNId(conn, tln); if (tlnId == null) return new ArrayList<>(); @@ -138,43 +174,93 @@ public final class DatabaseINSServer extends ProtocolINSServer { Integer subNameId = findSubNameId(conn, infoNameId, sub); - // Get records + // 1) direct records List direct = loadRecords(conn, infoNameId, subNameId, requestedType); + direct.sort(recordComparator(requestedType)); - // No more recursion when CNAME received + // If the requested type is CNAME, do not recurse. if (requestedType == INSRecordType.CNAME) return direct; if (!direct.isEmpty()) return direct; - // CNAME lookup + // 2) fallback to CNAME if no direct records exist List cnames = loadRecords(conn, infoNameId, subNameId, INSRecordType.CNAME); - if (cnames.isEmpty()) return direct; // empty + cnames.sort(recordComparator(INSRecordType.CNAME)); + + if (cnames.isEmpty()) return new ArrayList<>(); List aggregated = new ArrayList<>(); + for (INSRecord cname : cnames) { TargetName target = parseCnameTarget(cname.value); - if (target == null) { - getProtocolBridge().getLogger().warn("Invalid CNAME target '" + cname.value + "' on " + formatName(tln, name, sub)); + getProtocolBridge().getLogger().warn("Invalid CNAME target '" + cname.value + "' on " + canonical); continue; } - // Rekursiv den Zielnamen auflösen - aggregated.addAll(resolveInternal(target.tln, target.name, target.sub, requestedType, depth + 1)); + // recurse on the target name to fetch the original requested type + List resolvedTarget = resolveInternal( + target.tln(), + target.name(), + target.sub(), + requestedType, + depth + 1, + visited + ); + aggregated.addAll(resolvedTarget); } + aggregated.sort(recordComparator(requestedType)); return aggregated; + } finally { + // important: visited is shared across the recursion chain on purpose + // (do not remove canonical here; loop detection should stay for the whole chain) } } + /** + * Deterministic ordering for returned records. The goal is stable ranking so callers can pick index 0. + * + *

Rules: + *

    + *
  • priority ASC (smaller is better)
  • + *
  • weight DESC (larger is better)
  • + *
  • port ASC (smaller is better)
  • + *
  • ttl DESC (larger is better)
  • + *
  • value ASC (case-insensitive) as final tie-breaker
  • + *
+ * + * @param requestedType The type being requested (kept for future type-specific tuning). + * @return Comparator for {@link INSRecord}. + */ + private Comparator recordComparator(INSRecordType requestedType) { + return Comparator + .comparingInt((INSRecord r) -> safeInt(r.priority)) + .thenComparingInt(r -> -safeInt(r.weight)) + .thenComparingInt(r -> safeInt(r.port)) + .thenComparingInt(r -> -safeInt(r.ttl)) + .thenComparing(r -> safeString(r.value), String.CASE_INSENSITIVE_ORDER); + } + + private static int safeInt(int v) { + return v; + } + + private static String safeString(String s) { + return s == null ? "" : s; + } + /** * Loads all records of a given type for (infoname_id, subname_id). * * @param type May be {@code null} to load all types. */ private List loadRecords(Connection conn, int infonameId, Integer subnameId, INSRecordType type) throws SQLException { - - StringBuilder sql = new StringBuilder("SELECT type, value, ttl, priority, port, weight FROM records WHERE infoname_id = ? "); + StringBuilder sql = new StringBuilder( + "SELECT type, value, ttl, priority, port, weight " + + "FROM records " + + "WHERE infoname_id = ? " + ); if (subnameId == null) sql.append("AND subname_id IS NULL "); else sql.append("AND subname_id = ? "); @@ -213,7 +299,6 @@ public final class DatabaseINSServer extends ProtocolINSServer { try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, tln); - try (ResultSet rs = ps.executeQuery()) { if (rs.next()) return rs.getInt("id"); } @@ -227,12 +312,10 @@ public final class DatabaseINSServer extends ProtocolINSServer { try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, tlnId); ps.setString(2, infoName); - try (ResultSet rs = ps.executeQuery()) { if (rs.next()) return rs.getInt("id"); } } - return null; } @@ -248,16 +331,14 @@ public final class DatabaseINSServer extends ProtocolINSServer { if (rs.next()) return rs.getInt("id"); } } - return null; } /** * Parses a CNAME target string into TLN, InfoName and optional subname. - *

* - * @param value Raw CNAME value from the {@code records} table. - * @return Parsed {@link TargetName} or {@code null} if the value is invalid. + * @param value Raw CNAME value (e.g. "sub.app.example" or "example.net"). + * @return Parsed {@link TargetName} or {@code null} if invalid. */ private TargetName parseCnameTarget(String value) { if (value == null) return null; @@ -265,7 +346,7 @@ public final class DatabaseINSServer extends ProtocolINSServer { if (trimmed.isEmpty()) return null; String[] parts = trimmed.split("\\."); - if (parts.length < 2) return null; // at least "." + if (parts.length < 2) return null; String tln = parts[parts.length - 1]; String name = parts[parts.length - 2]; @@ -273,15 +354,13 @@ public final class DatabaseINSServer extends ProtocolINSServer { if (parts.length > 2) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < parts.length - 2; i++) { if (i > 0) sb.append('.'); sb.append(parts[i]); } - sub = sb.toString(); } return new TargetName(tln, name, sub); } -} \ No newline at end of file +} diff --git a/src/main/java/org/openautonomousconnection/insserver/Main.java b/src/main/java/org/openautonomousconnection/insserver/Main.java index 037eca7..b9ed53f 100644 --- a/src/main/java/org/openautonomousconnection/insserver/Main.java +++ b/src/main/java/org/openautonomousconnection/insserver/Main.java @@ -9,6 +9,7 @@ import lombok.Getter; import org.openautonomousconnection.protocol.ProtocolBridge; import org.openautonomousconnection.protocol.ProtocolSettings; import org.openautonomousconnection.protocol.versions.ProtocolVersion; +import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.builtin.INSClassic; import java.io.File; import java.util.Scanner; @@ -31,7 +32,11 @@ public class Main { settings.eventManager.registerListener(Listener.class); settings.port = 1023; - protocolBridge = new ProtocolBridge(new DatabaseINSServer(), settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs")); + DatabaseINSServer server = new DatabaseINSServer(); + protocolBridge = new ProtocolBridge(server, settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs")); + protocolBridge.setClassicHandlerINSServer(new INSClassic(protocolBridge)); + server.getPipelineServer().start(); + commandManager = new CommandManager(protocolBridge.getProtocolSettings().eventManager); Scanner scanner = new Scanner(System.in); diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java b/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java deleted file mode 100644 index 42027ca..0000000 --- a/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.openautonomousconnection.insserver.utils; - -import org.openautonomousconnection.insserver.Main; -import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain; -import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain; - -public class ClassicHelper { - public static Classic_Domain buildClassicDomain(ParsedDomain req, INSRecord rec) { - Classic_Domain info = new Classic_Domain(req.name, req.tln, req.sub, req.path, Main.getProtocolBridge()); - - String host = rec.value; - int port = rec.port > 0 ? rec.port : 80; - - return new Classic_Domain( - req.name, req.tln, - host.contains(":") ? host : host + ":" + port, - req.path, Main.getProtocolBridge()); - } - - public static ParsedDomain parseDomain(Classic_RequestDomain req) { - String tln = req.topLevelDomain; // example: "net" - String full = req.name; // example: "api.v1.example" - - String[] parts = full.split("\\."); - - if (parts.length == 1) { - return new ParsedDomain(tln, full, null, req.path); - } - - String name = parts[parts.length - 1]; - String sub = parts.length > 1 - ? String.join(".", java.util.Arrays.copyOfRange(parts, 0, parts.length - 1)) - : null; - - return new ParsedDomain(tln, name, sub, req.path); - } -} diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java b/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java deleted file mode 100644 index 5a51f38..0000000 --- a/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.openautonomousconnection.insserver.utils; - -public final class ParsedDomain { - public final String tln; - public final String name; - public final String sub; - public final String path; - - public ParsedDomain(String tln, String name, String sub, String path) { - this.tln = tln; - this.name = name; - this.sub = sub; - this.path = path; - } -} \ No newline at end of file diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java b/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java deleted file mode 100644 index e6f0d47..0000000 --- a/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.openautonomousconnection.insserver.utils; - -/** - * Represents a parsed CNAME target in (tln, name, sub) form. - */ -public final class TargetName { - public final String tln; - public final String name; - public final String sub; - - public TargetName(String tln, String name, String sub) { - this.tln = tln; - this.name = name; - this.sub = sub; - } -}