EEE
This commit is contained in:
83
pom.xml
Normal file
83
pom.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>dev.astronand</groupId>
|
||||
<artifactId>ast-ip</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jetty.version>11.0.26</jetty.version>
|
||||
<jakarta.websocket.version>2.1.1</jakarta.websocket.version>
|
||||
<jakarta.servlet.version>6.0.0</jakarta.servlet.version>
|
||||
<slf4j.version>2.0.13</slf4j.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>1.6.0</version> <!-- Use the latest stable version -->
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty server core -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty WebSocket support (Jakarta WebSocket) -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-jakarta-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta WebSocket API -->
|
||||
<dependency>
|
||||
<groupId>jakarta.websocket</groupId>
|
||||
<artifactId>jakarta.websocket-api</artifactId>
|
||||
<version>${jakarta.websocket.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta Servlet API -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>${jakarta.servlet.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SLF4J logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Compiler plugin -->
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
67
src/main/java/dev/astronand/Dns.java
Normal file
67
src/main/java/dev/astronand/Dns.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package dev.astronand;
|
||||
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Dns extends WebSocketClient {
|
||||
|
||||
public Dns(URI serverUri) {
|
||||
super(serverUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
System.out.println("DNS: Opened connection to " + getURI());
|
||||
}
|
||||
|
||||
private static String toIP(byte[] bytes) {
|
||||
if (bytes.length != 4) return "?.?.?.?";
|
||||
return (bytes[0] & 0xFF) + "." +
|
||||
(bytes[1] & 0xFF) + "." +
|
||||
(bytes[2] & 0xFF) + "." +
|
||||
(bytes[3] & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
var bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
var ver = bytes[0];
|
||||
var sender = Arrays.copyOfRange(bytes, 1, 5);
|
||||
var receiver = Arrays.copyOfRange(bytes, 5, 9);
|
||||
var contentb = Arrays.copyOfRange(bytes, 9, bytes.length);
|
||||
var content = new String(contentb, StandardCharsets.UTF_8);
|
||||
|
||||
if (ver!=0) {
|
||||
send(new Packet((byte)0, toIP(receiver), toIP(sender), ("Invalid ver "+((char)ver)+"|\0").getBytes()).toPacket());
|
||||
}
|
||||
if (!toIP(receiver).equals("1.1.1.1")) {
|
||||
System.out.println("WTF");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
System.out.println("Connection closed with exit code " + code + " reason: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
System.err.println("An error occurred:" + ex);
|
||||
}
|
||||
|
||||
public static void main(String url) throws URISyntaxException {
|
||||
// Replace with your WebSocket server URI
|
||||
Dns client = new Dns(new URI(url+"/connect"));
|
||||
client.connect();
|
||||
// You can send more messages here if needed
|
||||
// client.send("Another message");
|
||||
}
|
||||
}
|
||||
7
src/main/java/dev/astronand/Docs.txt
Normal file
7
src/main/java/dev/astronand/Docs.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
ip map:
|
||||
0.0.0.0 - 0.255.255.255 | Local network
|
||||
1.0.0.0 - 1.255.255.255 | Reserved
|
||||
2.0.0.0 - 126.255.255.255 | Assignable address space
|
||||
127.0.0.0 - 127.255.255.255 | Loopback addresses
|
||||
128.0.0.0 - 255.255.255.254 | Mass random address space
|
||||
255.255.255.255 - 255.255.255.255 | Broadcast address
|
||||
38
src/main/java/dev/astronand/ExternalHTTP.java
Normal file
38
src/main/java/dev/astronand/ExternalHTTP.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package dev.astronand;
|
||||
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Simple HTTP servlet for the AST-IP server.
|
||||
* Handles GET requests under /http/*
|
||||
*/
|
||||
public class ExternalHTTP extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
// Set content type
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
|
||||
// Get request path
|
||||
String path = req.getPathInfo(); // e.g., /test
|
||||
if (path == null) path = "/";
|
||||
|
||||
// Write response
|
||||
resp.getWriter().println("✅ ExternalHTTP servlet received a GET request!");
|
||||
resp.getWriter().println("Path: " + path);
|
||||
resp.getWriter().println("Query: " + req.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
// Optional: handle POST requests
|
||||
resp.setContentType("text/plain");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.getWriter().println("✅ ExternalHTTP servlet received a POST request!");
|
||||
}
|
||||
}
|
||||
183
src/main/java/dev/astronand/IpHandler.java
Normal file
183
src/main/java/dev/astronand/IpHandler.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package dev.astronand;
|
||||
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@ServerEndpoint("/connect")
|
||||
public class IpHandler {
|
||||
|
||||
private static final Set<Session> sessions = ConcurrentHashMap.newKeySet();
|
||||
private static final Map<Session, String> ipMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Session> reverseIpMap = new ConcurrentHashMap<>();
|
||||
byte version = 0;
|
||||
private static final ScheduledExecutorService heartbeatExecutor =
|
||||
Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private static final DateTimeFormatter TS_FORMAT =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// IP allocation counter (start at 128.0.0.0)
|
||||
private static final AtomicLong ipCounter = new AtomicLong(ipToLong(128, 0, 0, 0));
|
||||
|
||||
static {
|
||||
// Schedule heartbeat every 20 seconds
|
||||
heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
for (Session session : sessions) {
|
||||
if (session.isOpen()) {
|
||||
try {
|
||||
session.getAsyncRemote().sendPing(ByteBuffer.wrap(new byte[]{1}));
|
||||
} catch (Exception e) {
|
||||
log("❌ Failed to send heartbeat: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 20, 20, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
String ts = LocalDateTime.now().format(TS_FORMAT);
|
||||
System.out.println("[" + ts + "] " + msg);
|
||||
}
|
||||
|
||||
private static String toIP(byte[] bytes) {
|
||||
if (bytes.length != 4) return "?.?.?.?";
|
||||
return (bytes[0] & 0xFF) + "." +
|
||||
(bytes[1] & 0xFF) + "." +
|
||||
(bytes[2] & 0xFF) + "." +
|
||||
(bytes[3] & 0xFF);
|
||||
}
|
||||
|
||||
private static byte[] ipToBytes(String ip) {
|
||||
String[] parts = ip.split("\\.");
|
||||
return new byte[]{
|
||||
(byte) Integer.parseInt(parts[0]),
|
||||
(byte) Integer.parseInt(parts[1]),
|
||||
(byte) Integer.parseInt(parts[2]),
|
||||
(byte) Integer.parseInt(parts[3])
|
||||
};
|
||||
}
|
||||
|
||||
private static long ipToLong(int a, int b, int c, int d) {
|
||||
return ((long) a << 24) | ((long) b << 16) | ((long) c << 8) | d;
|
||||
}
|
||||
|
||||
private static String longToIp(long value) {
|
||||
return String.format("%d.%d.%d.%d",
|
||||
(value >> 24) & 0xFF,
|
||||
(value >> 16) & 0xFF,
|
||||
(value >> 8) & 0xFF,
|
||||
value & 0xFF);
|
||||
}
|
||||
|
||||
private static synchronized String allocateIp() {
|
||||
long ipLong = ipCounter.getAndIncrement();
|
||||
if (ipLong > ipToLong(255, 255, 255, 254)) {
|
||||
throw new RuntimeException("IP pool exhausted");
|
||||
}
|
||||
return longToIp(ipLong);
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
sessions.add(session);
|
||||
|
||||
String assignedIp = allocateIp();
|
||||
ipMap.put(session, assignedIp);
|
||||
reverseIpMap.put(assignedIp, session);
|
||||
|
||||
log("✅ Client connected: " + session.getId() + " assigned IP " + assignedIp);
|
||||
|
||||
try {
|
||||
var packet = new Packet(version, "1.0.0.0", assignedIp, ipToBytes(assignedIp));
|
||||
session.getBasicRemote().sendBinary(packet.toPacket());
|
||||
} catch (IOException e) {
|
||||
log("⚠ Failed to send IP assignment: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session) {
|
||||
sessions.remove(session);
|
||||
String ip = ipMap.remove(session);
|
||||
if (ip != null) reverseIpMap.remove(ip);
|
||||
log("❎ Client disconnected: " + session.getId() + " (IP " + ip + ")");
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onTextMessage(Session session, String message) {
|
||||
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
parseAndRoute(session, bytes);
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onBinaryMessage(Session session, byte[] bytes) {
|
||||
parseAndRoute(session, bytes);
|
||||
}
|
||||
|
||||
private void parseAndRoute(Session session, byte[] bytes) {
|
||||
try {
|
||||
var ver = bytes[0];
|
||||
var senderb = Arrays.copyOfRange(bytes, 1, 5);
|
||||
var receiverb = Arrays.copyOfRange(bytes, 5, 9);
|
||||
var contentb = Arrays.copyOfRange(bytes, 9, bytes.length);
|
||||
|
||||
var sender = toIP(senderb);
|
||||
var receiver = toIP(receiverb);
|
||||
var content = new String(contentb, StandardCharsets.UTF_8);
|
||||
//if (bytes[0]!=version) {
|
||||
// //session.getBasicRemote().sendBinary(new Packet((byte) 0, "1.0.0.0", sender, ("Invalid version "+((char)ver)+"|"+((char)version)).getBytes(StandardCharsets.UTF_8)).toPacket().flip());
|
||||
// return;
|
||||
//}
|
||||
|
||||
log("📦 Packet: " + sender + " > " + receiver + "\n" + content);
|
||||
|
||||
// Find target client
|
||||
Session target = reverseIpMap.get(receiver);
|
||||
if (target != null && target.isOpen()) {
|
||||
if (receiver.equals("1.0.0.0")) {
|
||||
server(session, bytes);
|
||||
} else {
|
||||
// Forward the original binary data (preserving header + content)
|
||||
target.getBasicRemote().sendBinary(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
} else {
|
||||
log("📦 Packet: 1.0.0.0 > " + sender + "\nReceiver "+receiver+" not found");
|
||||
session.getBasicRemote().sendBinary(new Packet(version, "1.0.0.0", sender, ("Receiver " + receiver + " not found").getBytes(StandardCharsets.UTF_8)).toPacket());
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try { session.close(); } catch (IOException ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void server(Session session, byte[] bytes) throws IOException {
|
||||
var ver = bytes[0];
|
||||
var senderb = Arrays.copyOfRange(bytes, 1, 5);
|
||||
var receiverb = Arrays.copyOfRange(bytes, 5, 9);
|
||||
var contentb = Arrays.copyOfRange(bytes, 9, bytes.length);
|
||||
|
||||
var sender = toIP(senderb);
|
||||
var receiver = toIP(receiverb);
|
||||
var content = new String(contentb, StandardCharsets.UTF_8);
|
||||
if (ver!=version) {
|
||||
session.getBasicRemote().sendBinary(new Packet(version,"1.0.0.0", sender, "Invalid version".getBytes(StandardCharsets.UTF_8)).toPacket().flip());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable t) {
|
||||
log("⚠ WebSocket error on " + session.getId() + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
69
src/main/java/dev/astronand/Main.java
Normal file
69
src/main/java/dev/astronand/Main.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package dev.astronand;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class Main {
|
||||
static boolean stop = false;
|
||||
public static void main(String[] args) throws Exception {
|
||||
int port = 25616;
|
||||
String serveraddress = "ws://localhost:25616";
|
||||
|
||||
// Create Jetty server with no default connector
|
||||
Server server = new Server();
|
||||
var DNS = new Thread(() -> {
|
||||
try {
|
||||
Dns.main(serveraddress);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Add shutdown hook to cleanly stop the server
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
System.out.println("Shutting down server...");
|
||||
server.setStopTimeout(10000);
|
||||
try {
|
||||
server.stop();
|
||||
Main.stop = true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
|
||||
// Bind to all interfaces so domain traffic is accepted
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setHost("0.0.0.0"); // listens on all interfaces (public + localhost)
|
||||
connector.setPort(port);
|
||||
server.addConnector(connector);
|
||||
|
||||
// Servlet context
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
// Enable WebSocket + register endpoint
|
||||
JakartaWebSocketServletContainerInitializer.configure(context,
|
||||
(servletContext, wsContainer) -> wsContainer.addEndpoint(IpHandler.class));
|
||||
|
||||
// Register ExternalHTTP servlet
|
||||
// Map it to any path you like, e.g., /http/*
|
||||
ServletHolder httpHolder = new ServletHolder(new ExternalHTTP());
|
||||
context.addServlet(httpHolder, "/net/*");
|
||||
|
||||
// Start Jetty
|
||||
server.start();
|
||||
System.out.println("✅ AST-IP running on port "+port);
|
||||
System.out.println("Accepting incoming connections");
|
||||
DNS.start();
|
||||
System.out.println("✅ DNS running on ip 1.1.1.1");
|
||||
server.join();
|
||||
DNS.join();
|
||||
}
|
||||
}
|
||||
28
src/main/java/dev/astronand/Packet.java
Normal file
28
src/main/java/dev/astronand/Packet.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package dev.astronand;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public record Packet(Byte ver, String sender, String receiver, byte[] content) {
|
||||
private static byte[] ipToBytes(String ip) {
|
||||
String[] parts = ip.split("\\.");
|
||||
return new byte[]{
|
||||
(byte) Integer.parseInt(parts[0]),
|
||||
(byte) Integer.parseInt(parts[1]),
|
||||
(byte) Integer.parseInt(parts[2]),
|
||||
(byte) Integer.parseInt(parts[3])
|
||||
};
|
||||
}
|
||||
|
||||
public ByteBuffer toPacket() {
|
||||
byte[] senderb = ipToBytes(sender);
|
||||
byte[] receiverb = ipToBytes(receiver);
|
||||
byte[] contentb = content;
|
||||
|
||||
ByteBuffer packet = ByteBuffer.allocate(1+senderb.length + receiverb.length + contentb.length);
|
||||
packet.put(ver);
|
||||
packet.put(senderb);
|
||||
packet.put(receiverb);
|
||||
packet.put(contentb);
|
||||
return packet.flip();
|
||||
}
|
||||
}
|
||||
9
src/main/java/dev/astronand/Registry.java
Normal file
9
src/main/java/dev/astronand/Registry.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package dev.astronand;
|
||||
|
||||
public record Registry() {
|
||||
public boolean hasClient(String ip) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user