From e4f4c77c19fb68759ca453df8eabf5d8de205432 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 30 Jan 2024 16:54:57 +0800 Subject: [PATCH] UPDATE 2.2 --- KLALB协议规范V2.1.docx | Bin 0 -> 20190 bytes klalbclient.ini | 3 - klalbconfig.json | 29 + kserver - 副本.ini | 3 - lib/ini4j.jar | Bin 0 -> 102290 bytes src/org/kne/acclerate/FastLib.java | 46 ++ .../cloud/network/DatagramServerSocket.java | 176 ++++++ .../network/DatagramServerSocketFactory.java | 18 + .../cloud/network/DatagramSocketFactory.java | 27 + .../cloud/network/DatagramSocketListener.java | 78 +++ .../DefaultDatagramServerSocketFactory.java | 28 + .../network/DefaultDatagramSocketFactory.java | 41 ++ .../network/KLALBProxyConfigJsonExecuter.java | 172 ++++++ .../cloud/network/ProxyProfileAnalyser.java | 63 -- .../cloud/network/ProxyProfileExecutor.java | 57 -- src/org/kne/cloud/network/SocketType.java | 52 ++ src/org/kne/cloud/network/frpc/FrpcIni.java | 233 +++++++ .../kne/cloud/network/frpc/FrpcProcess.java | 245 ++++++++ .../cloud/network/frpc/FrpcServerSocket.java | 158 +++++ .../klalb/BindableKLALBPacketConsumer.java | 12 + src/org/kne/cloud/network/klalb/CONST.java | 6 + .../klalb/DatagramKLALBPacketLink.java | 68 +++ .../network/klalb/KLALBPacketConsumer.java | 7 + .../cloud/network/klalb/KLALBPacketLink.java | 13 + .../KLALBProtocolDetectSocketListener.java | 97 +++ .../cloud/network/klalb/KLALBRemoteLine.java | 502 +++++++++++++++ .../network/klalb/KLALBRemoteSocket.java | 267 -------- .../cloud/network/klalb/KLALBStateGUI.java | 201 ++++++ .../kne/cloud/network/klalb/LineManager.java | 162 ----- .../kne/cloud/network/klalb/NACKTPacket.java | 63 ++ .../kne/cloud/network/klalb/PortBinder.java | 243 ++++++++ .../kne/cloud/network/klalb/PortPacket.java | 8 + .../network/klalb/StreamKLALBPacketLink.java | 59 ++ src/org/kne/cloud/network/klalb/SumQueue.java | 216 +++++++ src/org/kne/cloud/network/klalb/Sumable.java | 5 + .../kne/cloud/network/klalb/TESTPacket.java | 37 ++ src/org/kne/cloud/network/klalb/TPanel.java | 132 ++++ .../cloud/network/klalb/VADDRACKPacket.java | 14 + .../cloud/network/klalb/VADDRREQPacket.java | 14 + .../minecraft/KLALBJsonMotdInserter.java | 37 ++ .../minecraft/MinecraftDefaultPacket.java | 35 ++ .../minecraft/MinecraftHandShakePacket.java | 57 ++ .../minecraft/MinecraftInputStream.java | 116 ++++ .../minecraft/MinecraftMotdRequest.java | 14 + .../minecraft/MinecraftMotdResponse.java | 38 ++ .../minecraft/MinecraftOutputStream.java | 38 ++ .../network/minecraft/MinecraftPacket.java | 45 ++ .../minecraft/MinecraftPingPacket.java | 35 ++ .../minecraft/MinecraftPongPacket.java | 34 ++ .../minecraft/MinecraftProtocolContext.java | 22 + .../minecraft/MinecraftServerAddress.java | 119 ++++ .../minecraft/MinecraftServerPinger.java | 299 +++++++++ .../minecraft/MinecraftSocketBridge.java | 47 ++ .../MinecraftSocketToSocketProxy.java | 55 ++ .../minecraft/MinecraftStreamBridge.java | 59 ++ .../minecraft/MinecraftUnsolvedPacket.java | 39 ++ .../minecraft/SimpleKLALBMinecraftClient.java | 67 ++ .../minecraft/SimpleKLALBMinecraftServer.java | 194 ++++++ .../network/nathole/ICMPNatholeTest.java | 27 + .../cloud/network/nathole/UDPNatholeTest.java | 42 ++ src/org/kne/debug/Debuger.java | 129 ++++ src/org/kne/io/VarDataInputStream.java | 80 +++ src/org/kne/io/VarDataOutputStream.java | 43 ++ src/org/kne/ui/KTabUI.java | 34 -- src/org/kne/ui/XFrame.java | 573 ------------------ src/org/kne/ui/YScrollPane.java | 133 ---- src/org/knemcl/ui/ProcessingFrame.java | 64 -- src/org/knemcl/ui/UIEnv.java | 83 --- src/org/knemcl/uitool/KNEProgressBarUI.java | 78 --- ~$ALB协议规范V2.1.docx | Bin 0 -> 162 bytes 70 files changed, 4671 insertions(+), 1520 deletions(-) create mode 100644 KLALB协议规范V2.1.docx delete mode 100644 klalbclient.ini create mode 100644 klalbconfig.json delete mode 100644 kserver - 副本.ini create mode 100644 lib/ini4j.jar create mode 100644 src/org/kne/acclerate/FastLib.java create mode 100644 src/org/kne/cloud/network/DatagramServerSocket.java create mode 100644 src/org/kne/cloud/network/DatagramServerSocketFactory.java create mode 100644 src/org/kne/cloud/network/DatagramSocketFactory.java create mode 100644 src/org/kne/cloud/network/DatagramSocketListener.java create mode 100644 src/org/kne/cloud/network/DefaultDatagramServerSocketFactory.java create mode 100644 src/org/kne/cloud/network/DefaultDatagramSocketFactory.java create mode 100644 src/org/kne/cloud/network/KLALBProxyConfigJsonExecuter.java delete mode 100644 src/org/kne/cloud/network/ProxyProfileAnalyser.java delete mode 100644 src/org/kne/cloud/network/ProxyProfileExecutor.java create mode 100644 src/org/kne/cloud/network/SocketType.java create mode 100644 src/org/kne/cloud/network/frpc/FrpcIni.java create mode 100644 src/org/kne/cloud/network/frpc/FrpcProcess.java create mode 100644 src/org/kne/cloud/network/frpc/FrpcServerSocket.java create mode 100644 src/org/kne/cloud/network/klalb/BindableKLALBPacketConsumer.java create mode 100644 src/org/kne/cloud/network/klalb/CONST.java create mode 100644 src/org/kne/cloud/network/klalb/DatagramKLALBPacketLink.java create mode 100644 src/org/kne/cloud/network/klalb/KLALBPacketConsumer.java create mode 100644 src/org/kne/cloud/network/klalb/KLALBPacketLink.java create mode 100644 src/org/kne/cloud/network/klalb/KLALBProtocolDetectSocketListener.java create mode 100644 src/org/kne/cloud/network/klalb/KLALBRemoteLine.java delete mode 100644 src/org/kne/cloud/network/klalb/KLALBRemoteSocket.java create mode 100644 src/org/kne/cloud/network/klalb/KLALBStateGUI.java delete mode 100644 src/org/kne/cloud/network/klalb/LineManager.java create mode 100644 src/org/kne/cloud/network/klalb/NACKTPacket.java create mode 100644 src/org/kne/cloud/network/klalb/PortBinder.java create mode 100644 src/org/kne/cloud/network/klalb/PortPacket.java create mode 100644 src/org/kne/cloud/network/klalb/StreamKLALBPacketLink.java create mode 100644 src/org/kne/cloud/network/klalb/SumQueue.java create mode 100644 src/org/kne/cloud/network/klalb/Sumable.java create mode 100644 src/org/kne/cloud/network/klalb/TESTPacket.java create mode 100644 src/org/kne/cloud/network/klalb/TPanel.java create mode 100644 src/org/kne/cloud/network/klalb/VADDRACKPacket.java create mode 100644 src/org/kne/cloud/network/klalb/VADDRREQPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/KLALBJsonMotdInserter.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftDefaultPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftHandShakePacket.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftInputStream.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftMotdRequest.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftMotdResponse.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftOutputStream.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftPingPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftPongPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftProtocolContext.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftServerAddress.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftServerPinger.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftSocketBridge.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftSocketToSocketProxy.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftStreamBridge.java create mode 100644 src/org/kne/cloud/network/minecraft/MinecraftUnsolvedPacket.java create mode 100644 src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftClient.java create mode 100644 src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftServer.java create mode 100644 src/org/kne/cloud/network/nathole/ICMPNatholeTest.java create mode 100644 src/org/kne/cloud/network/nathole/UDPNatholeTest.java create mode 100644 src/org/kne/debug/Debuger.java create mode 100644 src/org/kne/io/VarDataInputStream.java create mode 100644 src/org/kne/io/VarDataOutputStream.java delete mode 100644 src/org/kne/ui/KTabUI.java delete mode 100644 src/org/kne/ui/XFrame.java delete mode 100644 src/org/kne/ui/YScrollPane.java delete mode 100644 src/org/knemcl/ui/ProcessingFrame.java delete mode 100644 src/org/knemcl/ui/UIEnv.java delete mode 100644 src/org/knemcl/uitool/KNEProgressBarUI.java create mode 100644 ~$ALB协议规范V2.1.docx diff --git a/KLALB协议规范V2.1.docx b/KLALB协议规范V2.1.docx new file mode 100644 index 0000000000000000000000000000000000000000..f252cdcca08063373b7939ee1185204eae33b363 GIT binary patch literal 20190 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

|5$3A5R!K(` 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 IV1fqwAF!G(ssI20 literal 0 HcmV?d00001 diff --git a/src/org/kne/acclerate/FastLib.java b/src/org/kne/acclerate/FastLib.java new file mode 100644 index 0000000..eb09572 --- /dev/null +++ b/src/org/kne/acclerate/FastLib.java @@ -0,0 +1,46 @@ +package org.kne.acclerate; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.kne.io.Task; + +public class FastLib { + private static int cores=Runtime.getRuntime().availableProcessors(); + private static ExecutorService exc=Executors.newFixedThreadPool(cores,new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t=new Thread(r); + t.setDaemon(true); + t.setPriority(Thread.MAX_PRIORITY); + return t; + } + }); + public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) { + if(length<2000) { + System.arraycopy(src, srcPos, dest, destPos, length); + }else { + int count=length/cores; + int mod=length%cores; + Task[]tsk=new Task[cores]; + for (int i = 0; i < cores; i++) { + int j=i; + Task t=new Task() { + + @Override + protected void runTask() { + System.arraycopy(src, srcPos+count*j, dest, destPos+count*j, count); + } + }; + exc.execute(t); + tsk[i]=t; + } + System.arraycopy(src, srcPos+count*cores, dest, destPos+count*cores, mod); + for (int i = 0; i < tsk.length; i++) { + tsk[i].waitfortask(); + } + } + } +} diff --git a/src/org/kne/cloud/network/DatagramServerSocket.java b/src/org/kne/cloud/network/DatagramServerSocket.java new file mode 100644 index 0000000..54a5ba7 --- /dev/null +++ b/src/org/kne/cloud/network/DatagramServerSocket.java @@ -0,0 +1,176 @@ +package org.kne.cloud.network; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Flow.Subscriber; + +public class DatagramServerSocket extends DatagramSocket { + private Map clients=new ConcurrentHashMap<>(); + public DatagramServerSocket() throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + } + + protected DatagramServerSocket(DatagramSocketImpl impl) throws SocketException { + super(impl); + setReuseAddress(true); + } + + public DatagramServerSocket(int port, InetAddress laddr) throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + bind(new InetSocketAddress(laddr, port)); + } + + public DatagramServerSocket(int port) throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + bind(new InetSocketAddress(port)); + } + + public DatagramServerSocket(SocketAddress bindaddr) throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + bind(bindaddr); + } + + public DatagramServerSocket(int port, int backlog) throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + bind(new InetSocketAddress(port)); + } + + @Override + public synchronized void send(DatagramPacket p) throws IOException { + // TODO 自动生成的方法存根 + super.send(p); + } + + @Override + public synchronized void receive(DatagramPacket p) throws IOException { + // TODO 自动生成的方法存根 + super.receive(p); + } + + public DatagramServerSocket(int port, int backlog, InetAddress laddr) throws SocketException { + super((SocketAddress)null); + setReuseAddress(true); + bind(new InetSocketAddress(laddr, port)); + } + public DatagramSocket accept()throws IOException{ + if (isClosed()) + throw new SocketException("Socket is closed"); + if (!isBound()) + throw new SocketException("Socket is not bound yet"); + DatagramPacket dp=new DatagramPacket(new byte[65535], 65535); + do { + receive(dp); + System.out.println(dp); + }while(clients.containsKey(dp.getSocketAddress())); + System.out.println("accept"); + SubDatagramSocket re=new SubDatagramSocket((SocketAddress)null); + re.dp=dp; + re.setReuseAddress(true); + re.bind(new InetSocketAddress(InetAddress.getByName("192.168.0.233"), getLocalPort())); + re.connect(dp.getSocketAddress()); + return re; + /* loop:while(true) { + receive(dp); + SocketAddress isa= dp.getSocketAddress() ; + synchronized (clients) { + for (Iterator iterator = clients.iterator(); iterator.hasNext();) { + SubDatagramSocket subDatagramSocket = (SubDatagramSocket) iterator.next(); + if(subDatagramSocket.getRemoteSocketAddress().equals(isa)) { + DatagramPacket dpx=dp; + dp=new DatagramPacket(new byte[65535], 65535); + subDatagramSocket.getFstq().offer(dpx); + continue loop; + } + } + DatagramPacket dpx=dp; + dp=new DatagramPacket(new byte[65535], 65535); + SubDatagramSocket sds=new SubDatagramSocket(isa); + sds.getFstq().add(dpx); + return sds; + + } + }*/ + } + private class SubDatagramSocket extends DatagramSocket{ + + DatagramPacket dp; + + public SubDatagramSocket() throws SocketException { + super(); + } + + protected SubDatagramSocket(DatagramSocketImpl impl) { + super(impl); + } + + public SubDatagramSocket(int port, InetAddress laddr) throws SocketException { + super(port, laddr); + } + + public SubDatagramSocket(int port) throws SocketException { + super(port); + } + + public SubDatagramSocket(SocketAddress bindaddr) throws SocketException { + super(bindaddr); + } + + @Override + public void connect(InetAddress address, int port) { + super.connect(address, port); + clients.put(new InetSocketAddress(address, port),this); + } + + @Override + public void connect(SocketAddress addr) throws SocketException { + super.connect(addr); + clients.put(addr,this); + } + + @Override + public void receive(DatagramPacket p) throws IOException { + if(dp!=null) { + p.setLength(dp.getLength()); + System.arraycopy(dp.getData(), dp.getOffset(), p.getData(), p.getOffset(), dp.getLength()); + dp=null; + }else { + super.receive(p); + } + } + + @Override + public void disconnect() { + super.disconnect(); + SocketAddress sa=getRemoteSocketAddress(); + if(sa!=null) + clients.remove(sa); + } + + public void close() { + super.close(); + SocketAddress sa=getRemoteSocketAddress(); + if(sa!=null) + clients.remove(sa); + } + } +} diff --git a/src/org/kne/cloud/network/DatagramServerSocketFactory.java b/src/org/kne/cloud/network/DatagramServerSocketFactory.java new file mode 100644 index 0000000..897173c --- /dev/null +++ b/src/org/kne/cloud/network/DatagramServerSocketFactory.java @@ -0,0 +1,18 @@ +package org.kne.cloud.network; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; + +public abstract class DatagramServerSocketFactory { + public DatagramServerSocket createDatagramServerSocket() throws IOException { + return new DatagramServerSocket((SocketAddress)null); + } + + public abstract DatagramServerSocket createDatagramServerSocket(int port) throws IOException; + + public abstract DatagramServerSocket createDatagramServerSocket(int port, int backlog) throws IOException; + + public abstract DatagramServerSocket createDatagramServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException; +} diff --git a/src/org/kne/cloud/network/DatagramSocketFactory.java b/src/org/kne/cloud/network/DatagramSocketFactory.java new file mode 100644 index 0000000..37afb3b --- /dev/null +++ b/src/org/kne/cloud/network/DatagramSocketFactory.java @@ -0,0 +1,27 @@ +package org.kne.cloud.network; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; + +public abstract class DatagramSocketFactory { + public DatagramSocket createSocket() throws IOException { + return new DatagramSocket((SocketAddress)null); + } + + public abstract DatagramSocket createSocket(String paramString, int paramInt) throws IOException, UnknownHostException; + + public abstract DatagramSocket createSocket(InetAddress paramInetAddress, int paramInt) throws IOException; + + public abstract DatagramSocket createSocket(String paramString, int paramInt1, InetAddress paramInetAddress, int paramInt2) + throws IOException, UnknownHostException; + + public abstract DatagramSocket createSocket(InetAddress paramInetAddress1, int paramInt1, InetAddress paramInetAddress2, + int paramInt2) throws IOException; +} diff --git a/src/org/kne/cloud/network/DatagramSocketListener.java b/src/org/kne/cloud/network/DatagramSocketListener.java new file mode 100644 index 0000000..de9beda --- /dev/null +++ b/src/org/kne/cloud/network/DatagramSocketListener.java @@ -0,0 +1,78 @@ +package org.kne.cloud.network; + +import java.io.Closeable; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.function.Consumer; + +import javax.net.ServerSocketFactory; + +public class DatagramSocketListener implements Closeable,AutoCloseable{ + private DatagramServerSocket serverSocket; + public DatagramServerSocket getDatagramServerSocket() { + return serverSocket; + } + + private volatile boolean flag=true; + + private Consumercon; + private Runnable r=new Runnable() { + @Override + public void run() { + while(flag){ + try { + DatagramSocket soce=serverSocket.accept(); + ThreadTool.makeVThreadIfSupport("端口监听处理线程",()->{ + con.accept(soce); + }).start(); + }catch(SocketException e) {e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }; + private MultipurposeSocketAddress multipurposeSocketAddress; + + public DatagramSocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException { + this.multipurposeSocketAddress=multipurposeSocketAddress; + open(); + } + public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException { + this.serverSocket=tserverSocket; + open(); + } + + private void open() throws IOException { + if(serverSocket==null) + serverSocket=multipurposeSocketAddress.listenDatagramServerSocket(); + //new Thread(r).start(); + ThreadTool.makeVThreadIfSupport("端口监听线程",r).start(); + } + public void close() { + flag=false; + if(serverSocket!=null) { + serverSocket.close(); + serverSocket=null; + } + } + + + + public MultipurposeSocketAddress getMultipurposeSocketAddress() { + return multipurposeSocketAddress; + } + + public Consumer getCon() { + return con; + } + + public void setCon(Consumer con) { + this.con = con; + } + +} diff --git a/src/org/kne/cloud/network/DefaultDatagramServerSocketFactory.java b/src/org/kne/cloud/network/DefaultDatagramServerSocketFactory.java new file mode 100644 index 0000000..96327d4 --- /dev/null +++ b/src/org/kne/cloud/network/DefaultDatagramServerSocketFactory.java @@ -0,0 +1,28 @@ +package org.kne.cloud.network; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class DefaultDatagramServerSocketFactory extends DatagramServerSocketFactory { + + @Override + public DatagramServerSocket createDatagramServerSocket(int port) throws IOException { + return new DatagramServerSocket(port); + } + + @Override + public DatagramServerSocket createDatagramServerSocket(int port, int backlog) throws IOException { + return new DatagramServerSocket(port,backlog); + } + + @Override + public DatagramServerSocket createDatagramServerSocket(int port, int backlog, InetAddress ifAddress) + throws IOException { + return new DatagramServerSocket(port,backlog, ifAddress); + } + + + +} diff --git a/src/org/kne/cloud/network/DefaultDatagramSocketFactory.java b/src/org/kne/cloud/network/DefaultDatagramSocketFactory.java new file mode 100644 index 0000000..9cf568b --- /dev/null +++ b/src/org/kne/cloud/network/DefaultDatagramSocketFactory.java @@ -0,0 +1,41 @@ +package org.kne.cloud.network; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +public class DefaultDatagramSocketFactory extends DatagramSocketFactory { + + @Override + public DatagramSocket createSocket(String paramString, int paramInt) throws IOException, UnknownHostException { + DatagramSocket dg=new DatagramSocket(); + dg.connect(new InetSocketAddress(paramString, paramInt)); + return dg; + } + + @Override + public DatagramSocket createSocket(InetAddress paramInetAddress, int paramInt) throws IOException { + DatagramSocket dg=new DatagramSocket(); + dg.connect(paramInetAddress, paramInt); + return dg; + } + + @Override + public DatagramSocket createSocket(String paramString, int paramInt1, InetAddress paramInetAddress, int paramInt2) + throws IOException, UnknownHostException { + DatagramSocket dg=new DatagramSocket(new InetSocketAddress(paramInetAddress, paramInt2)); + dg.connect(new InetSocketAddress(paramString, paramInt1)); + return dg; + } + + @Override + public DatagramSocket createSocket(InetAddress paramInetAddress1, int paramInt1, InetAddress paramInetAddress2, + int paramInt2) throws IOException { + DatagramSocket dg=new DatagramSocket(new InetSocketAddress(paramInetAddress2, paramInt2)); + dg.connect(paramInetAddress1, paramInt1); + return dg; + } + +} diff --git a/src/org/kne/cloud/network/KLALBProxyConfigJsonExecuter.java b/src/org/kne/cloud/network/KLALBProxyConfigJsonExecuter.java new file mode 100644 index 0000000..ad1a2e0 --- /dev/null +++ b/src/org/kne/cloud/network/KLALBProxyConfigJsonExecuter.java @@ -0,0 +1,172 @@ +package org.kne.cloud.network; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map.Entry; + +import org.kne.cloud.network.klalb.KLALBController; +import org.kne.cloud.network.klalb.KLALBController.KLALBSocketType; +import org.kne.cloud.network.klalb.KLALBRemoteLine; +import org.kne.cloud.network.klalb.StreamKLALBPacketLink; +import org.kne.cloud.network.minecraft.KLALBJsonMotdInserter; +import org.kne.cloud.network.minecraft.MinecraftSocketToSocketProxy; + +import java.util.Set; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class KLALBProxyConfigJsonExecuter { + private Set proxys=new HashSet<>(); + private KLALBController klalbController; + public Set getProxys() { + return proxys; + } + public KLALBController getKlalbController() { + return klalbController; + } + public KLALBProxyConfigJsonExecuter(Reader json) { + loadConfigJson(json); + } + public KLALBProxyConfigJsonExecuter(String json) { + loadConfigJson(json); + } + + public KLALBProxyConfigJsonExecuter(JsonElement json) { + loadConfigJson(json); + } + public KLALBProxyConfigJsonExecuter(File jsonFile) throws IOException { + loadConfigJson(jsonFile); + } + public KLALBProxyConfigJsonExecuter() { + } + + + + public void loadConfigJson(File jsonFile) throws IOException { + FileReader fr = null; + try { + fr=new FileReader(jsonFile); + loadConfigJson(fr); + }finally { + if(fr!=null) + fr.close(); + } + + } + public void loadConfigJson(String json) { + loadConfigJson(new JsonParser().parse(json)); + } + public void loadConfigJson(Reader json) { + loadConfigJson(new JsonParser().parse(json)); + } + public void loadConfigJson(JsonElement json) { + JsonArray jobj=(JsonArray) json; + jobj.forEach((val)->{ + solveEntry((JsonObject) val); + }); + } + protected void solveEntry(JsonObject entry){ + switch (entry.get("Type").getAsString()) { + case "KLALBController": + JsonElement vase= entry.get("VirtualAddress"); + if(vase!=null) { + try { + klalbController=new KLALBController((Inet6Address) InetAddress.getByName(vase.getAsString())); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + }else { + klalbController=new KLALBController(); + } + JsonElement vsne=entry.get("VirtualSocketName"); + //if(vsne!=null) { + MultipurposeSocketAddress.getSocketTypeRegister().put(vsne.getAsString(), klalbController.getSocketType()); + //} + JsonElement tcple=entry.get("TCPListen"); + if(tcple!=null) { + MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(tcple.getAsString()); + SocketListener tcpl = null; + try { + tcpl = new SocketListener(mpsa); + + tcpl.setCon((soc)->{ + + KLALBRemoteLine krs=null; + try { + krs = new KLALBRemoteLine(new StreamKLALBPacketLink(soc)); + klalbController.addRemoteLine(krs); + } catch (IOException e) { + e.printStackTrace(); + } + + }); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + JsonElement linele=entry.get("LineTable"); + if(linele!=null) { + JsonArray jary=(JsonArray)linele; + StringBuilder sb=new StringBuilder(); + jary.forEach((aline)->{ + sb.append(aline.getAsString()); + sb.append('\n'); + }); + klalbController.setSelflineTableSupplier(()->{return sb.toString();}); + } + break; + case "SocketBridge": + HostPortMap mapp=new HostPortMap(); + + MultipurposeSocketAddress l=new MultipurposeSocketAddress(entry.get("Listen").getAsString()); + MultipurposeSocketAddress r=getDefault(entry.get("Connect"),mapp); + try { + proxys.add(new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp)); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case "MinecraftSocketBridge": + + HostPortMap mapp2=new HostPortMap(); + MultipurposeSocketAddress l2=new MultipurposeSocketAddress(entry.get("Listen").getAsString()); + MultipurposeSocketAddress r2=getDefault(entry.get("Connect"),mapp2); + try { + MinecraftSocketToSocketProxy mcp=new MinecraftSocketToSocketProxy(l2 ,new MultipurposeSocketAddress("0.0.0.0:0"), r2,mapp2); + //if(MultipurposeSocketAddress.getSocketTypeRegister().get( l2.getType()) instanceof KLALBSocketType ) + mcp.getTransformersBA().add(new KLALBJsonMotdInserter(()->{return klalbController.getSelf();},()->{return 23332;})); + proxys.add(mcp); + } catch (IOException e) { + e.printStackTrace(); + } + break; + + default: + break; + } + } + private MultipurposeSocketAddress getDefault(JsonElement jsonElement, HostPortMap mapp) { + if(jsonElement instanceof JsonObject) { + JsonObject jobj=(JsonObject) jsonElement; + jobj.entrySet().forEach((en)->{ + mapp.put(en.getKey(), new MultipurposeSocketAddress( en.getValue().getAsString())); + }); + return mapp.get("DEFAULT"); + }else { + return new MultipurposeSocketAddress(jsonElement.getAsString()); + } + } +} diff --git a/src/org/kne/cloud/network/ProxyProfileAnalyser.java b/src/org/kne/cloud/network/ProxyProfileAnalyser.java deleted file mode 100644 index ff55fb3..0000000 --- a/src/org/kne/cloud/network/ProxyProfileAnalyser.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.kne.cloud.network.mport; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.*; - -public class ProxyProfileAnalyser { - private Listentrys=new ArrayList(); - public List getEntrys() { - return entrys; - } - public ProxyProfileAnalyser(InputStream f) { - Scanner sr=new Scanner(f); - analyse(sr); - } - public ProxyProfileAnalyser(File f) throws FileNotFoundException { - Scanner sr=new Scanner(f); - analyse(sr); - } - public ProxyProfileAnalyser(String s) { - Scanner sr=new Scanner(s); - analyse(sr); - } - private void analyse(Scanner sr) { - int n=0; - while(sr.hasNext()) { - String sl=sr.nextLine().trim(); - int x=sl.indexOf("//"); - if(x!=-1) - sl=sl.substring(0, x); - n++; - if(sl.isEmpty()) { - continue; - } - String[] sp=sl.split("->"); - if(sp.length!=2) { - throw new IllegalArgumentException("Format error at line "+n); - } - String l=sp[0].trim(); - String r=sp[1].trim(); - //System.out.println(l+" "+r); - if(l.contains(":")) { - if(r.contains(":")){ - SocketToSocketProxyProfileEntry sts=new SocketToSocketProxyProfileEntry(l,r); - entrys.add(sts); - - }else { - SocketToServiceProxyProfileEntry sts=new SocketToServiceProxyProfileEntry(l,r); - entrys.add(sts); - } - }else { - if(r.contains(":")) { - ServiceToSocketProxyProfileEntry sts=new ServiceToSocketProxyProfileEntry(l,r); - entrys.add(sts); - }else { - - } - } - } - } - -} diff --git a/src/org/kne/cloud/network/ProxyProfileExecutor.java b/src/org/kne/cloud/network/ProxyProfileExecutor.java deleted file mode 100644 index 66d66c0..0000000 --- a/src/org/kne/cloud/network/ProxyProfileExecutor.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.kne.cloud.network.mport; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public class ProxyProfileExecutor { - private Listcurrent=new ArrayList<>(); - - public List getCurrent() { - return current; - } - public void applyNewProxyList(Listl) { - for (Iterator iterator = l.iterator(); iterator.hasNext();) { - ProxyProfileEntry proxyProfileEntry = (ProxyProfileEntry) iterator.next(); - if(!current.contains(proxyProfileEntry)) { - if(proxyProfileEntry instanceof SocketToSocketProxyProfileEntry) { - SocketToSocketProxyProfileEntry stsppe=(SocketToSocketProxyProfileEntry) proxyProfileEntry; - }else if(proxyProfileEntry instanceof SocketToServiceProxyProfileEntry){ - SocketToServiceProxyProfileEntry stsppe=(SocketToServiceProxyProfileEntry) proxyProfileEntry; - stsppe.getDes().listen(stsppe.getSrc()); - }else if(proxyProfileEntry instanceof ServiceToSocketProxyProfileEntry) { - ServiceToSocketProxyProfileEntry stsppe=(ServiceToSocketProxyProfileEntry) proxyProfileEntry; - stsppe.getSrc().connect(stsppe.getDes()); - } - } - } - for (Iterator iterator = current.iterator(); iterator.hasNext();) { - ProxyProfileEntry proxyProfileEntry = (ProxyProfileEntry) iterator.next(); - if(!l.contains(proxyProfileEntry)) { - if(proxyProfileEntry instanceof SocketToSocketProxyProfileEntry) { - SocketToSocketProxyProfileEntry stsppe=(SocketToSocketProxyProfileEntry) proxyProfileEntry; - }else if(proxyProfileEntry instanceof SocketToServiceProxyProfileEntry){ - SocketToServiceProxyProfileEntry stsppe=(SocketToServiceProxyProfileEntry) proxyProfileEntry; - stsppe.getDes().unlisten(stsppe.getSrc()); - }else if(proxyProfileEntry instanceof ServiceToSocketProxyProfileEntry) { - ServiceToSocketProxyProfileEntry stsppe=(ServiceToSocketProxyProfileEntry) proxyProfileEntry; - stsppe.getSrc().unconnect(stsppe.getDes()); - } - } - } - current.clear(); - current.addAll(l); - } - public void load(File f) throws FileNotFoundException { - applyNewProxyList(new ProxyProfileAnalyser(f).getEntrys()); - } - public void load(String s) { - applyNewProxyList(new ProxyProfileAnalyser(s).getEntrys()); - } - public void load(InputStream in) { - applyNewProxyList(new ProxyProfileAnalyser(in).getEntrys()); - } -} diff --git a/src/org/kne/cloud/network/SocketType.java b/src/org/kne/cloud/network/SocketType.java new file mode 100644 index 0000000..2cec2ce --- /dev/null +++ b/src/org/kne/cloud/network/SocketType.java @@ -0,0 +1,52 @@ +package org.kne.cloud.network; + +import java.net.DatagramSocket; + +import javax.net.ServerSocketFactory; +import javax.net.SocketFactory; + +public class SocketType { + private SocketFactory socketFactory; + private ServerSocketFactory serverSocketFactory; + private DatagramSocketFactory datagramSocketFactory; + private DatagramServerSocketFactory datagramServerSocketFactory; + private boolean stream; + + public SocketFactory getSocketFactory() { + return socketFactory; + } + + public ServerSocketFactory getServerSocketFactory() { + return serverSocketFactory; + } + + public DatagramSocketFactory getDatagramSocketFactory() { + return datagramSocketFactory; + } + + + public DatagramServerSocketFactory getDatagramServerSocketFactory() { + return datagramServerSocketFactory; + } + + public SocketType(SocketFactory socketFactory, ServerSocketFactory serverSocketFactory) { + super(); + this.socketFactory = socketFactory; + this.serverSocketFactory = serverSocketFactory; + this.stream=true; + } + + public SocketType(DatagramSocketFactory datagramSocketFactory, + DatagramServerSocketFactory datagramServerSocketFactory) { + super(); + this.datagramSocketFactory = datagramSocketFactory; + this.datagramServerSocketFactory = datagramServerSocketFactory; + this.stream=false; + } + + public boolean isStream() { + return stream; + } + + +} diff --git a/src/org/kne/cloud/network/frpc/FrpcIni.java b/src/org/kne/cloud/network/frpc/FrpcIni.java new file mode 100644 index 0000000..d47d0c4 --- /dev/null +++ b/src/org/kne/cloud/network/frpc/FrpcIni.java @@ -0,0 +1,233 @@ +package org.kne.cloud.network.frpc; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URL; + +import org.ini4j.Ini; +import org.ini4j.InvalidFileFormatException; + +public class FrpcIni extends Ini { + + public FrpcIni() { + super(); + } + + public FrpcIni(File input) throws IOException, InvalidFileFormatException { + super(input); + // TODO 自动生成的构造函数存根 + } + + public FrpcIni(InputStream input) throws IOException, InvalidFileFormatException { + super(input); + // TODO 自动生成的构造函数存根 + } + + public FrpcIni(Reader input) throws IOException, InvalidFileFormatException { + super(input); + // TODO 自动生成的构造函数存根 + } + + public FrpcIni(URL input) throws IOException, InvalidFileFormatException { + super(input); + // TODO 自动生成的构造函数存根 + } + + public FrpcIni(String str) throws InvalidFileFormatException { + this(); + loadFromString(str); + } + + public void setServerAddr(String addr) { + add("common", "server_addr", addr); + } + + public String getServerAddr() { + return get("common", "server_addr"); + } + + public void setToken(String addr) { + add("common", "token", addr); + } + + public String getToken() { + return get("common", "token"); + } + + public void setProtocol(String protocol) { + add("common", "protocol", protocol); + } + + public String getProtocol() { + return get("common", "protocol"); + } + + public void setServerPort(int port) { + add("common", "server_port", port); + } + + public int getServerPort() { + return get("common", "server_port", int.class); + } + + public void setDnsServer(String dns) { + add("common", "dns_server", dns); + } + + public String getDnsServer() { + return get("common", "dns_server"); + } + public void setConnectServerLocalIp(String connectserverlocalip) { + add("common", "connect_server_local_ip", connectserverlocalip); + } + + public String getConnectServerLocalIp() { + return get("common", "connect_server_local_ip"); + } + + public void setUser(String user) { + add("common", "user", user); + } + + public String getUser() { + return get("common", "user"); + } + + public void setTcpMux(boolean tcpmux) { + add("common", "tcp_mux", tcpmux); + } + + public boolean getTcpMux() { + return get("common", "tcp_mux",boolean.class); + } + + public void setTlsEnable(boolean tlsenable) { + add("common", "tls_enable", tlsenable); + } + + public boolean getTlsEnable() { + return get("common", "tls_enable",boolean.class); + } + + + + + + + + + + + + + public void setType(String tunnel,String type) { + add(tunnel, "type", type); + } + + public String getType(String tunnel) { + return get(tunnel, "type"); + } + public void setLocalIP (String tunnel,String localip) { + add(tunnel, "local_ip", localip); + } + + public String getLocalIP(String tunnel) { + return get(tunnel, "local_ip"); + } + public void setLocalPort (String tunnel,int localport) { + add(tunnel, "local_port", localport); + } + + public int getLocalPort(String tunnel) { + return get(tunnel, "local_port",int.class); + } + public void setRemotePort (String tunnel,int remoteport) { + add(tunnel, "remote_port", remoteport); + } + + public int getRemotePort(String tunnel) { + return get(tunnel, "remote_port",int.class); + } + public void setUseEncryption (String tunnel,boolean useencryption) { + add(tunnel, "use_encryption", useencryption); + } + + public boolean getUseEncryption(String tunnel) { + return get(tunnel, "use_encryption",boolean.class); + } + public void setUseCompression (String tunnel,boolean usecompression) { + add(tunnel, "use_compression", usecompression); + } + + public boolean getUseCompression(String tunnel) { + return get(tunnel, "use_compression",boolean.class); + } + public void setSk(String tunnel,String sk) { + add(tunnel, "sk", sk); + } + + public String getSk(String tunnel) { + return get(tunnel, "sk"); + } + public void setRole(String tunnel,String sk) { + add(tunnel, "role", sk); + } + + public String getRole(String tunnel) { + return get(tunnel, "role"); + } + public void setServerName(String tunnel,String servername) { + add(tunnel, "server_name", servername); + } + + public String getServerName(String tunnel) { + return get(tunnel, "server_name"); + } + + public void setBindAddr(String tunnel,String bindaddr) { + add(tunnel, "bind_addr", bindaddr); + } + + public String getBindAddr(String tunnel) { + return get(tunnel, "bind_addr"); + } + public void setBindPort (String tunnel,int bindport) { + add(tunnel, "bind_port", bindport); + } + + public int getBindPort(String tunnel) { + return get(tunnel, "bind_port",int.class); + } + public void setKeepTunnelOpen (String tunnel,boolean keeptunnelopen) { + add(tunnel, "keep_tunnel_open", keeptunnelopen); + } + + public boolean getKeepTunnelOpen(String tunnel) { + return get(tunnel, "keep_tunnel_open",boolean.class); + } + + + public String storeToString() { + StringWriter sw = new StringWriter(); + try { + store(sw); + } catch (IOException e) { + e.printStackTrace(); + } + return sw.toString(); + } + + public void loadFromString(String ini) throws InvalidFileFormatException { + try { + load(new StringReader(ini)); + } catch (InvalidFileFormatException e) { + throw e; + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/org/kne/cloud/network/frpc/FrpcProcess.java b/src/org/kne/cloud/network/frpc/FrpcProcess.java new file mode 100644 index 0000000..f109bc5 --- /dev/null +++ b/src/org/kne/cloud/network/frpc/FrpcProcess.java @@ -0,0 +1,245 @@ +package org.kne.cloud.network.frpc; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.ProcessHandle.Info; +import java.net.BindException; +import java.net.ConnectException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +public class FrpcProcess extends Process{ + private static File defaultBase=new File("frpc"); + + private volatile Thread hook; + + public static File getDefaultBase() { + return defaultBase; + } + public static void setDefaultBase(File defaultBase) { + FrpcProcess.defaultBase = defaultBase; + } + private File base; + private File exe; + private Process theProcess; + + public FrpcProcess(String ini) throws IOException { + this(defaultBase,ini); + + } + public FrpcProcess(File base,String ini) throws IOException { + this.base=base; + if(System.getProperty("os.name").toLowerCase().contains("windows")) { + exe=new File(base,"frpc_windows_amd64.exe"); + }else { + exe=new File(base,"frpc_linux_amd64"); + } + File f=new File(base,"frpc_conf.ini"); + f.createNewFile(); + PrintWriter ps=null; + try { + ps=new PrintWriter(f); + ps.println(ini); + }finally { + if(ps!=null) + ps.close(); + } + Listlstc=new ArrayList<>(); + lstc.add(exe.getAbsolutePath()); + lstc.add("-c"); + lstc.add(f.getName()); + ProcessBuilder psb=new ProcessBuilder(lstc); + psb.directory(base); + theProcess=psb.start(); + AtomicReferencearex=new AtomicReference(); + Thread t=Thread.currentThread(); + new Thread(()->{ + BufferedReader br=new BufferedReader(new InputStreamReader(theProcess.getInputStream())); + + try { + while(true) { + String s=br.readLine(); + if(s==null) { + break; + } + System.out.println (s); + if(s.contains("start proxy success")) { + t.interrupt(); + }else if(s.contains("port already used")){ + arex.set(new BindException("port on server already used")); + t.interrupt(); + }else if(s.contains("no such host")) { + arex.set(new UnknownHostException("no such host")); + t.interrupt(); + }else if(s.contains("connection write timeout")) { + arex.set(new ConnectException("connection write timeout")); + t.interrupt(); + }else if(s.contains("already exists")&&s.contains("proxy")) { + arex.set(new BindException("proxy already exists")); + t.interrupt(); + } + } + } catch (IOException e) { + e.printStackTrace(); + }finally { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + }).start(); + try { + if(theProcess.waitFor(20000, TimeUnit.MILLISECONDS)) { + destroy(); + throw new SocketException("Frpc exit code:"+theProcess.exitValue()); + }else { + destroy(); + throw new SocketException("Frpc timed out"); + } + } catch (InterruptedException e) { + if(arex.get()!=null) { + destroy(); + arex.get().fillInStackTrace(); + throw arex.get(); + } + }finally { + Thread.interrupted(); + } + hook=new Thread(()->{ + try { + hook=null; + finalize(); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + Runtime.getRuntime().addShutdownHook(hook); + } + public File getBase() { + return base; + } + @Override + public OutputStream getOutputStream() { + return theProcess.getOutputStream(); + } + @Override + public InputStream getInputStream() { + return theProcess.getInputStream(); + } + @Override + public InputStream getErrorStream() { + return theProcess.getErrorStream(); + } + @Override + public int waitFor() throws InterruptedException { + return theProcess.waitFor(); + } + @Override + public int exitValue() { + return theProcess.exitValue(); + } + @Override + public void destroy() { + theProcess.destroy(); + //System.out.println("frpc已关闭"); + if(hook!=null) { + Runtime.getRuntime().removeShutdownHook(hook); + } + } + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + return theProcess.waitFor(timeout, unit); + } + @Override + public Process destroyForcibly() { + Process b= theProcess.destroyForcibly(); + //System.out.println("frpc已关闭"); + if(hook!=null) { + Runtime.getRuntime().removeShutdownHook(hook); + } + return b; + } + @Override + public boolean supportsNormalTermination() { + return theProcess.supportsNormalTermination(); + } + @Override + public boolean isAlive() { + return theProcess.isAlive(); + } + @Override + public long pid() { + return theProcess.pid(); + } + @Override + public CompletableFuture onExit() { + return theProcess.onExit(); + } + @Override + public ProcessHandle toHandle() { + return theProcess.toHandle(); + } + @Override + public Info info() { + return theProcess.info(); + } + @Override + public Stream children() { + return theProcess.children(); + } + @Override + public Stream descendants() { + return theProcess.descendants(); + } + public static void main(String[] args) throws IOException { + String str=""" + [common] +server_addr = us.afrps.cn +server_port = 7000 +token = afrps.cn +protocol=kcp +[test5] +type = tcp +local_ip = 127.0.0.1 +local_port = 7461 +remote_port = 49966 +use_encryption = true +use_compression = true + """; + FrpcIni fi=new FrpcIni(str); + + str=fi.storeToString(); + //System.out.println(fi.getServerPort()); + FrpcProcess fp=new FrpcProcess(str); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + @Override + protected void finalize() throws Throwable { + + if(theProcess!=null) { + destroy(); + } + } +} diff --git a/src/org/kne/cloud/network/frpc/FrpcServerSocket.java b/src/org/kne/cloud/network/frpc/FrpcServerSocket.java new file mode 100644 index 0000000..70dddae --- /dev/null +++ b/src/org/kne/cloud/network/frpc/FrpcServerSocket.java @@ -0,0 +1,158 @@ +package org.kne.cloud.network.frpc; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketImpl; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.Map.Entry; + +import org.ini4j.Profile.Section; +import org.kne.cloud.network.MultipurposeSocketAddress; + +public class FrpcServerSocket extends ServerSocket { + private FrpcProcess frpc; + private FrpcIni frpcini; + private String tunnel; + public FrpcServerSocket() throws IOException { + super(); + } + + public FrpcServerSocket(FrpcIni fi, int port, int backlog, InetAddress bindAddr) throws IOException { + super(); + this.frpcini=fi; + if (port < 0 || port > 0xFFFF) + throw new IllegalArgumentException("Port value out of range: " + port); + if (backlog < 1) + backlog = 50; + + try { + bind(new InetSocketAddress(bindAddr, port), backlog); + } catch (IOException | SecurityException e) { + close(); + throw e; + } + } + + public FrpcServerSocket(FrpcIni fi,int port, int backlog) throws IOException { + this(fi,port, backlog, null); + } + + public FrpcServerSocket(FrpcIni fi,int port) throws IOException { + this(fi,port, 50, null); + } + public FrpcServerSocket(FrpcIni fi) throws IOException { + this(fi,0, 50, null); + } + protected FrpcServerSocket(SocketImpl impl) { + super(impl); + } + + @Override + public void bind(SocketAddress endpoint) throws IOException { + bind(endpoint, 50); + } + + + public String getTunnelHost() { + if (!isBound()) + return null; + return frpcini.getServerAddr(); + } + public InetAddress getTunnelInetAddress() throws UnknownHostException { + if (!isBound()) + return null; + return InetAddress.getByName( frpcini.getServerAddr()); + } + + public int getTunnelPort() { + if (!isBound()) + return -1; + return frpcini.getRemotePort(tunnel); + } + + public MultipurposeSocketAddress getTunnelSocketAddress() { + if (!isBound()) + return null; + return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort()); + } + + @Override + public void bind(SocketAddress endpoint, int backlog) throws IOException { + if(frpcini!=null) { + if(tunnel==null) { + var e=frpcini.keySet(); + for (Iterator iterator = e.iterator(); iterator.hasNext();) { + String key = iterator.next(); + if(!key.equals("common")) { + tunnel=key; + break; + } + } + if(tunnel==null) { + throw new IllegalArgumentException("cant found avaliable tunnel!"); + } + } + super.bind(endpoint, backlog); + InetAddress iad=((InetSocketAddress) endpoint).getAddress(); + frpcini.setLocalIP(tunnel,iad.isAnyLocalAddress()?"127.0.0.1": iad.getHostAddress()); + frpcini.setLocalPort(tunnel, getLocalPort()); + frpcini.setConnectServerLocalIp(((InetSocketAddress)endpoint).getAddress().getHostAddress()); + frpc=new FrpcProcess(frpcini.storeToString()); + }else { + super.bind(endpoint, backlog); + } + } + + public FrpcIni getFrpcini() { + return frpcini; + } + + public void setFrpcini(FrpcIni frpcini) { + this.frpcini = frpcini; + } + + public String getTunnel() { + return tunnel; + } + + public void setTunnel(String tunnel) { + this.tunnel = tunnel; + } + + @Override + public void close() throws IOException { + if(frpc!=null) { + frpc.destroy(); + } + super.close(); + } + /*public MultipurposeSocketAddress getTunnelRemoteSocketAddress() { + return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(), frpcini.getServerAddr(), frpcini.getRemotePort(tunnel)); + }*/ + public static void main(String[] args) throws IOException { + + String str=""" + [common] +server_addr = us.afrps.cn +server_port = 7000 +token = afrps.cn +protocol=kcp +[test5] +type = tcp +local_ip = 127.0.0.1 +local_port = 7461 +remote_port = 49966 +use_encryption = true +use_compression = true + """; + FrpcIni fi=new FrpcIni(str); + FrpcServerSocket fss= new FrpcServerSocket(fi); + System.out.println(fss.getTunnelSocketAddress()); + System.out.println(fss.accept()); + } +} diff --git a/src/org/kne/cloud/network/klalb/BindableKLALBPacketConsumer.java b/src/org/kne/cloud/network/klalb/BindableKLALBPacketConsumer.java new file mode 100644 index 0000000..95b9a98 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/BindableKLALBPacketConsumer.java @@ -0,0 +1,12 @@ +package org.kne.cloud.network.klalb; + +import java.net.InetAddress; +import java.net.ServerSocket; + +public interface BindableKLALBPacketConsumer extends KLALBPacketConsumer { + public InetAddress getRemoteInetAddress() ; + public InetAddress getLocalInetAddress() ; + public int getPort() ; + public int getLocalPort(); + public void setLocalPort(int i); +} diff --git a/src/org/kne/cloud/network/klalb/CONST.java b/src/org/kne/cloud/network/klalb/CONST.java new file mode 100644 index 0000000..396ed3b --- /dev/null +++ b/src/org/kne/cloud/network/klalb/CONST.java @@ -0,0 +1,6 @@ +package org.kne.cloud.network.klalb; + +public class CONST { + public static String klalb="KLALB"; + public static String klalbver="2.2"; +} diff --git a/src/org/kne/cloud/network/klalb/DatagramKLALBPacketLink.java b/src/org/kne/cloud/network/klalb/DatagramKLALBPacketLink.java new file mode 100644 index 0000000..a0b305d --- /dev/null +++ b/src/org/kne/cloud/network/klalb/DatagramKLALBPacketLink.java @@ -0,0 +1,68 @@ +package org.kne.cloud.network.klalb; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; + +public class DatagramKLALBPacketLink implements KLALBPacketLink { + + @Override + public String toString() { + return "DatagramKLALBPacketLink [ds=" + ds + "]"; + } + + private DatagramSocket ds; + + public DatagramKLALBPacketLink(DatagramSocket connectDatagramSocket) throws IOException { + this.ds=connectDatagramSocket; + } + + private ByteArrayOutputStream bos=new ByteArrayOutputStream(65535); + private DataOutputStream dos=new DataOutputStream(bos); + @Override + public void writePacket(KLALBPacket kp) throws IOException { + //System.out.println(" TX:"+kp); + bos.reset(); + KLALBOutputStream.writeKLALBPacketToStream(dos, kp); + byte[]bt=bos.toByteArray(); + DatagramPacket dp=new DatagramPacket(bt,bt.length); + ds.send(dp); + } + + private byte[]bc=new byte[65535]; + @Override + public KLALBPacket readPacket() throws IOException { + DatagramPacket dp=new DatagramPacket(bc, bc.length); + ds.receive(dp); + DataInputStream dis=new DataInputStream(new ByteArrayInputStream(bc, 0, dp.getLength())); + KLALBPacket kp=KLALBInputStream.readKLALBPacketFromStream(dis); + dis.close(); + //System.out.println(" RX:"+kp); + return kp; + } + + @Override + public void close() throws IOException { + ds.close(); + } + + @Override + public boolean isClosed() { + return ds.isClosed(); + } + + @Override + public void setSoTimeout(int val) throws SocketException { + ds.setSoTimeout(val); + } + + @Override + public int getSoTimeout() throws SocketException { + return ds.getSoTimeout(); + } +} diff --git a/src/org/kne/cloud/network/klalb/KLALBPacketConsumer.java b/src/org/kne/cloud/network/klalb/KLALBPacketConsumer.java new file mode 100644 index 0000000..4021a84 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/KLALBPacketConsumer.java @@ -0,0 +1,7 @@ +package org.kne.cloud.network.klalb; + +import java.util.function.BiConsumer; + +public interface KLALBPacketConsumer extends BiConsumer { + +} diff --git a/src/org/kne/cloud/network/klalb/KLALBPacketLink.java b/src/org/kne/cloud/network/klalb/KLALBPacketLink.java new file mode 100644 index 0000000..c47e8a0 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/KLALBPacketLink.java @@ -0,0 +1,13 @@ +package org.kne.cloud.network.klalb; + +import java.io.IOException; +import java.net.SocketException; + +public interface KLALBPacketLink { + public void writePacket(KLALBPacket kp) throws IOException; + public KLALBPacket readPacket()throws IOException; + public void close() throws IOException; + public boolean isClosed(); + public void setSoTimeout(int val) throws SocketException; + public int getSoTimeout() throws SocketException; +} diff --git a/src/org/kne/cloud/network/klalb/KLALBProtocolDetectSocketListener.java b/src/org/kne/cloud/network/klalb/KLALBProtocolDetectSocketListener.java new file mode 100644 index 0000000..2b97d33 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/KLALBProtocolDetectSocketListener.java @@ -0,0 +1,97 @@ +package org.kne.cloud.network.klalb; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.function.Consumer; + +import org.kne.cloud.network.KLALBDetectorItem; +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.ProtocolDetector; +import org.kne.cloud.network.ProtocolDetectorServerSocket; +import org.kne.cloud.network.ProtocolDetectorSocket; +import org.kne.cloud.network.SocketListener; + +public class KLALBProtocolDetectSocketListener extends SocketListener { + + private volatile Consumer conx; + private KLALBController klalbController; + + @Override + protected void open() throws UnknownHostException, IOException { + if(serverSocket==null) + serverSocket=getMultipurposeSocketAddress().listenServerSocket(); + if(!(serverSocket instanceof ProtocolDetectorServerSocket)) { + ProtocolDetector pd=new ProtocolDetector(); + pd.add(new KLALBDetectorItem()); + serverSocket=new ProtocolDetectorServerSocket(serverSocket,pd); + } + super.open(); + } + + public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException { + super(multipurposeSocketAddress); + this.klalbController=klalbc; + } + + public KLALBProtocolDetectSocketListener(ServerSocket tserverSocket,KLALBController klalbc) throws IOException { + super(tserverSocket); + this.klalbController=klalbc; + } + { + + super.setCon((soc)->{ + ProtocolDetectorSocket pds=(ProtocolDetectorSocket) soc; + if(!pds.getProtocolStack().isEmpty()&&pds.getProtocolStack().pop().getName().equals("KLALB")) { + KLALBRemoteLine krs=null; + try { + krs = new KLALBRemoteLine(new StreamKLALBPacketLink(pds)); + klalbController.addRemoteLine(krs); + } catch (Exception e) { + e.printStackTrace(); + try { + soc.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + } + }else { + if(conx!=null) { + try { + conx.accept(soc); + } catch (Exception e) { + e.printStackTrace(); + try { + soc.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + } + }else { + try { + soc.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + }); + } + @Override + public Consumer getCon() { + return conx; + } + + @Override + public void setCon(Consumer conx) { + this.conx=conx; + } + + public KLALBController getKlalbController() { + return klalbController; + } + +} diff --git a/src/org/kne/cloud/network/klalb/KLALBRemoteLine.java b/src/org/kne/cloud/network/klalb/KLALBRemoteLine.java new file mode 100644 index 0000000..e3abf54 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/KLALBRemoteLine.java @@ -0,0 +1,502 @@ +package org.kne.cloud.network.klalb; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.net.BindException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.Vector; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.ThreadTool; + +public class KLALBRemoteLine implements Comparable{ + + + private volatile boolean closed=false; + + private volatile Supplier localVaddrSupplier; + + public Supplier getLocalVaddrSupplier() { + return localVaddrSupplier; + } + + public void setLocalVaddrSupplier(Supplier localVaddrSupplier) { + this.localVaddrSupplier = localVaddrSupplier; + } + + + private volatile Inet6Address remoteVaddr; + public Inet6Address getRemoteVaddr() throws SocketTimeoutException { + return remoteVaddr; + } + + public void waitForRemoteVaddrAvaliable(long timeout) throws SocketTimeoutException { + long s=System.nanoTime(); + while(remoteVaddr==null) { + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if(System.nanoTime()-s>timeout*1000000) + throw new SocketTimeoutException(); + } + } + + public Monitor getMonitor() { + return monitor; + } + public String toString() { + return remoteVaddr+"\t"+monitor.toString()+"\t"+sumNextPacketCount(); + } + + public String toString2() { + return remoteVaddr+"\t"+monitor.toString2(); + } + private Monitor monitor; + private volatile KLALBPacketLink kplink; + private MultipurposeSocketAddress bindAddress; + private MultipurposeSocketAddress socketAddress; + + private Thread lthd; + + + public void setKplink(KLALBPacketLink kplink) { + this.kplink = kplink; + } + + public KLALBPacketLink getKplink() { + return kplink; + } + + + public MultipurposeSocketAddress getSocketAddress() { + return socketAddress; + } + + + + public MultipurposeSocketAddress getBindAddress() { + return bindAddress; + } + + protected static KLALBPacketLink createLink(MultipurposeSocketAddress bindAddress,MultipurposeSocketAddress mpa) throws IOException { + if(mpa.isStream()) { + if(bindAddress!=null) { + return new StreamKLALBPacketLink(mpa.connectSocket(InetAddress.getByName( bindAddress.getHost()),bindAddress.getPort())); + }else { + return new StreamKLALBPacketLink(mpa.connectSocket()); + } + } + else { + if(bindAddress!=null) { + return new DatagramKLALBPacketLink(mpa.connectDatagramSocket(InetAddress.getByName( bindAddress.getHost()),bindAddress.getPort())); + }else { + return new DatagramKLALBPacketLink(mpa.connectDatagramSocket()); + } + } + } + public KLALBRemoteLine(MultipurposeSocketAddress mpa) { + this(mpa,new Monitor()); + } + public KLALBRemoteLine(MultipurposeSocketAddress mpa,Monitor monitor) { + this(mpa,null,monitor); + } + public KLALBRemoteLine(KLALBPacketLink kpl) { + this(kpl,new Monitor()); + } + public KLALBRemoteLine(KLALBPacketLink kpl,Monitor monitor) { + this.kplink=kpl; + this.monitor=monitor; + monitor.setName(kpl.toString()); + } + + public KLALBRemoteLine(MultipurposeSocketAddress mpa,MultipurposeSocketAddress bindaddr,Monitor monitor ) { + this.socketAddress=mpa; + this.bindAddress=bindaddr; + this.monitor=monitor; + monitor.setName(mpa.toString()); + } + + public KLALBRemoteLine(MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr) { + this(mpa,bindaddr,new Monitor()); + } + PrintStream pw; + private void startRecord() { + try { + pw=new PrintStream(UUID.randomUUID()+".csv"); + + pw.println("时间,状态,上传速度,下载速度,上传延迟,下载延迟,往返延迟,抖动"); + ThreadTool.makePDaemonThreadIfSupport("BigData Record", ()->{ + while(true) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + pw.println(System.currentTimeMillis()+","+monitor.getState()+","+monitor.getOutSpeedAvg()+","+monitor.getInSpeedAvg()+","+sndDelayFactor+","+rcvDelayFactor+","+monitor.getLatencyAvg()+","+monitor.getJitter()); + } + }).start(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + private void endRecord() { + if(pw!=null) + pw.close(); + } + + protected void startIO() { + ThreadTool.makePDaemonThreadIfSupport("远程接收线程", () -> { + //startRecord(); + while(true) { + try { + + monitor.setState(Monitor.CONNECTING); + if(socketAddress==null) { + if(kplink.isClosed()) { + monitor.setState(Monitor.OFFLINE); + closed=true; + break; + } + }else { + try { + kplink=createLink(bindAddress,socketAddress); + }catch(BindException be){ + //be.printStackTrace(); + close(); + throw be; + } + } + kplink.setSoTimeout(10000); + Thread t=ThreadTool.makePDaemonThreadIfSupport("远程发送线程", () -> { + try { + writePacketToKPL(new VADDRREQPacket()); + writePacketToKPL(new VADDRREQPacket()); + writePacketToKPL(new VADDRREQPacket()); + while ((!kplink.isClosed())&&(!closed)) { + if(checkPingTime()) { + writePacketToKPL(new PINGPacket(System.nanoTime())); + }else { + KLALBPacket kpp=getNextPacket(); + long lth=statLengthBefore(10); + if(lth>65536*2) { + monitor.updateOutSpeedMax(); + } + if(kpp!=null) { + pingInterval= 100000000L; + writePacketToKPL(kpp); + }else { + pingInterval=1000000000L; + tlock=Thread.currentThread(); + LockSupport.parkNanos(1000000); + } + } + Thread.yield(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + kplink.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + t.start(); + boolean fst=true; + long stime=5000000000L; + while ((!kplink.isClosed())&&(!closed)) { + long readStart=System.nanoTime(); + KLALBPacket kpp = readPacketFromKPL(); + long readTime=System.nanoTime()-readStart; + if(readTime>stime) { + stime=readTime; + }else { + stime=(stime*99+readTime)/100; + } + if(stime<3000000000L) { + stime=3000000000L; + } + + //System.out.println(readTime); + kplink.setSoTimeout((int) (stime/1000000)); + if(kpp==null) + break; + switch (kpp.getType()) { + case KLALBPacket.PING: + sendPacket(new PONGPacket(((PINGPacket) kpp).getTime(),kpp.getRcvtime(),System.nanoTime()), -1); + break; + case KLALBPacket.PONG: + PONGPacket png=(PONGPacket) kpp; + long ul=png.getRcvtime()-( png.getTimepingsnd()+(png.getTimepongsnd()-png.getTimepingrcv())); + //System.out.println(toString()+"\t"+ul); + monitor.updateLatency (ul); + long DsndDelayFactor=png.getTimepingrcv()- png.getTimepingsnd(); + long DrcvDelayFactor=png.getRcvtime()- png.getTimepongsnd(); + //sndDelayFactor=DsndDelayFactor; + //rcvDelayFactor=DrcvDelayFactor; + if(sndDelayFactor==Long.MIN_VALUE) { + sndDelayFactor=DsndDelayFactor; + }else { + sndDelayFactor=(sndDelayFactor+ DsndDelayFactor)/2; + } + if(rcvDelayFactor==Long.MIN_VALUE) { + rcvDelayFactor=DrcvDelayFactor; + }else { + rcvDelayFactor=(rcvDelayFactor+ DrcvDelayFactor)/2; + } + LockSupport.unpark(tlock); + break; + case KLALBPacket.VADDRREQ: + sendPacket(new VADDRPacket(localVaddrSupplier.get()), -1); + break; + case KLALBPacket.VADDR: + remoteVaddr=((VADDRPacket) kpp).getVaddr(); + sendPacket(new VADDRACKPacket(), -1); + break; + case KLALBPacket.VADDRACK: + if(fst) { + monitor.resetCoolingTime(); + monitor.setState(Monitor.ONLINE); + fst=false; + } + break; + case KLALBPacket.TEST: + break; + default: + while(rec==null) { + Thread.sleep(1); + } + rec.accept(KLALBRemoteLine.this,kpp); + break; + } + Thread.yield(); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + monitor.setState(Monitor.OFFLINE); + try { + if(kplink!=null) + kplink.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + if(closed) { + break; + } + //Thread.sleep(monitor.getCoolingTime()); + lthd=Thread.currentThread(); + LockSupport.parkNanos(monitor.getCoolingTime()*1000000L); + monitor.incCoolingTime(); + + } + endRecord(); + + }).start(); + + } + + + + + private KLALBPacket readPacketFromKPL() throws IOException { + KLALBPacket packet=kplink.readPacket(); + /*if(!(packet instanceof PINGPacket) ) + if(!(packet instanceof PONGPacket) ) + System.out.println("RX:" + packet);*/ + if(packet!=null) + monitor.getInTrafficAL().addAndGet(packet.getLength()); + return packet; + } + private void writePacketToKPL(KLALBPacket packet) throws IOException { + monitor.getOutTrafficAL().addAndGet(packet.getLength()); + kplink.writePacket(packet); + + /*if(!(packet instanceof PINGPacket) ) + if(!(packet instanceof PONGPacket) ) + System.err.println("TX:" + packet);*/ + } + + public void close() { + closed=true; + try { + if(kplink!=null) + kplink.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + public boolean isClosed() { + return closed; + } + + private volatile long time = System.nanoTime(); + private volatile long pingInterval=1000000000L; + + private boolean checkPingTime() { + long cu = System.nanoTime(); + if (cu - time > pingInterval) { + time = cu; + return true; + } else { + return false; + } + } + + + + private volatile BiConsumer rec; + + //private Object lock=new Object(); + private volatile Thread tlock; + private List> sendDequeList =new ArrayList>(); + { + for(int i=0;i<12;i++) { + sendDequeList.add(new SumQueue(new ConcurrentLinkedQueue<>())); + } + } + private KLALBPacket getNextPacket() { + for (Iterator> iterator = sendDequeList.iterator(); iterator.hasNext();) { + SumQueue queue = (SumQueue) iterator.next(); + KLALBPacket v=queue.poll(); + if(v!=null) + return v; + if(monitor.getLatencyCurr()>(monitor.getLatencyMin()<<3)) { + //System.out.println(monitor.getLatencyCurr()+" "+monitor.getLatencyMin()); + return null; + } + } + return null; + } + private long sumNextPacketSize() { + long i=0; + for (Iterator> iterator = sendDequeList.iterator(); iterator.hasNext();) { + SumQueue queue = (SumQueue) iterator.next(); + i+=queue.getSumValue(); + } + return i; + } + private long sumNextPacketCount() { + long i=0; + for (Iterator> iterator = sendDequeList.iterator(); iterator.hasNext();) { + SumQueue queue = (SumQueue) iterator.next(); + i+=queue.size(); + } + return i; + } + + public void sendPacket(KLALBPacket kp) { + sendPacket(kp, 5); + } + + public void sendPacket(KLALBPacket blk,int prio) { + sendDequeList.get(prio+1).add(blk); + LockSupport.unpark(tlock); + } + + public void setPacketReceiver(BiConsumer rec) { + this.rec = rec; + } + + + + public void remoeFromSendQueue(KLALBPacket klalbPacket) { + for (Iterator> iterator = sendDequeList.iterator(); iterator.hasNext();) { + SumQueue queue = iterator.next(); + queue.removeIf((x)->{ + return x.equals(klalbPacket); + }); + } + } + + public long statLengthBefore(int priority) { + AtomicLong al=new AtomicLong(); + int n=0; + for (Iterator> iterator = sendDequeList.iterator(); iterator.hasNext();n++) { + if(n>priority) { + break; + } + SumQueue queue = iterator.next(); + al.addAndGet(queue.getSumValue()); + + } + return al.get(); + } + + private long sndDelayFactor=Long.MIN_VALUE; + private long rcvDelayFactor=Long.MIN_VALUE; + + public long getSndDelayFactor() { + return sndDelayFactor; + } + public long getRcvDelayFactor() { + return rcvDelayFactor; + } + private volatile long predictTime; + public void runPredict(KLALBPacket curr,int priority) { + predictTime=sndDelayFactor; + //System.out.println(this+" "+sndDelayFactor); + long al=0; + al=statLengthBefore(priority); + long speed=getMonitor(). getOutSpeed(); + if(speed==0) { + if(al>0) { + predictTime=Long.MAX_VALUE; + } + }else { + predictTime+=al*1000000000L/speed; + } + } + @Override + public int compareTo(KLALBRemoteLine o2) { + long t1=this.predictTime; + long t2=o2.predictTime; + if(t1>t2) { + return 1; + }else if(t1 { - try { - - while (!isClosed()) { - KLALBPacket kpp = getInputStream().readPacket(); - if(kpp instanceof PINGPacket) { - sendPacket(new PONGPacket(((PINGPacket) kpp).getTime()), 65540); - }else if(kpp instanceof PONGPacket) { - PONGPacket png=(PONGPacket) kpp; - getMonitor().updateLatency (System.nanoTime()- png.getTime()); - try { - socket.setSoTimeout(600); - } catch (SocketException e1) { - // TODO 自动生成的 catch 块 - e1.printStackTrace(); - } - }else { - - System.out.println("RX:" + kpp); - if(kpp instanceof VADDRPacket) { - remoteVaddr=((VADDRPacket) kpp).getVaddr(); - } - while(rec==null) { - Thread.sleep(1); - } - rec.accept(kpp); - } - } - } catch (IOException e) { - //e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }).start(); - ThreadTool.makeVDaemonThreadIfSupport("远程发送线程", () -> { - try { - - while (!isClosed()) { - if(checkPingTime()) { - getOutputStream().writePacket(new PINGPacket(System.nanoTime())); - }else { - PQItem pqi=sendQueue.poll(); - if(pqi!=null) { - KLALBPacket kpp=pqi.getPacket(); - if(!(kpp instanceof PONGPacket)) - System.out.println("TX:" + kpp); - if(kpp instanceof PONGPacket) { - PONGPacket pp=(PONGPacket) kpp; - pp.redeltaTime(System.nanoTime()-pqi.getAddtime()); - } - getOutputStream().writePacket(kpp); - //System.out.println(sendQueue.size()); - }else { - synchronized (sendQueue) { - sendQueue.wait(10); - } - //Thread.sleep(1); - } - } - } - } catch (IOException e) { - // e.printStackTrace(); - } catch (InterruptedException e) { - } finally { - try { - close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }).start(); - } - - - private volatile long time = System.nanoTime(); - private volatile long pingInterval=500000000L; - - public long getPingInterval() { - return pingInterval; - } - - public void setPingInterval(long ns) { - this.pingInterval = ns; - } - - private boolean checkPingTime() { - long cu = System.nanoTime(); - if (cu - time > pingInterval) { - time = cu; - return true; - } else { - return false; - } - } - - - - - - private KLALBInputStream kis; - private KLALBOutputStream kos; - - @Override - public KLALBInputStream getInputStream() throws IOException { - if (kis == null) - kis = new KLALBInputStream(super.getInputStream()); - return kis; - } - - @Override - public KLALBOutputStream getOutputStream() throws IOException { - if (kos == null) - kos = new KLALBOutputStream(super.getOutputStream()); - return kos; - } - - - private volatile Consumer rec; - private Consumer clo; - private PriorityBlockingQueue sendQueue=new PriorityBlockingQueue(); - - protected PriorityBlockingQueue getSendQueue() { - return sendQueue; - } - - public void sendPacket(KLALBPacket kp) { - sendPacket(kp, 5); - } - - public void sendPacket(KLALBPacket kp,int priority) { - sendQueue.add(new PQItem(kp, priority)); - synchronized (sendQueue) { - sendQueue.notifyAll(); - } - } - - public void setPacketReceiver(Consumer rec) { - this.rec = rec; - } - public void setCloseListener(Consumer clo) { - this.clo = clo; - } - @Override - public synchronized void close() throws IOException { - if(!isClosed()&&clo!=null) - clo.accept(this); - cdl.countDown(); - super.close(); - sendQueue.forEach((x)->{ - try { - //System.out.println("断线重发"); - controller.sendPacketToAddress(remoteVaddr, x.getPacket(), x.getPriority()+1); - } catch (NoRouteToHostException e) { - } - }); - } - - @Override - public boolean isClosed() { - return cdl.getCount()<=0; - } - - private AtomicLong al=new AtomicLong(0); - protected class PQItem implements Comparable{ - private KLALBPacket packet; - private long index=al.getAndIncrement(); - private int priority=5; - private long addtime=System.nanoTime(); - public long getAddtime() { - return addtime; - } - public PQItem(KLALBPacket value, int priority) { - super(); - this.packet = value; - this.priority = priority; - } - public KLALBPacket getPacket() { - return packet; - } - public long getIndex() { - return index; - } - public int getPriority() { - return priority; - } - @Override - public int compareTo(PQItem o) { - if(priorityo.priority) { - return -1; - }else { - if(index>o.index) { - return 1; - }else if(index{ - return x.getPacket().equals(klalbPacket); - }); - } -} diff --git a/src/org/kne/cloud/network/klalb/KLALBStateGUI.java b/src/org/kne/cloud/network/klalb/KLALBStateGUI.java new file mode 100644 index 0000000..bae5f47 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/KLALBStateGUI.java @@ -0,0 +1,201 @@ +package org.kne.cloud.network.klalb; + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.MenuItem; +import java.awt.PopupMenu; +import java.awt.SystemTray; +import java.awt.TrayIcon; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; + +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.SocketBridge; +import org.kne.cloud.network.SocketListener; +import org.kne.cloud.network.ThreadTool; +import org.kne.ui.XFrame; +import org.kne.ui.YScrollPane; +import javax.swing.JProgressBar; + +public class KLALBStateGUI extends XFrame { + private BufferedImage bi; + + private TimerTask tsk; + + private SystemTray st; + + private TrayIcon ti; + public KLALBStateGUI(KLALBController kc,String title) throws IOException { + setResizable(false); + try { + bi = ImageIO.read(KLALBStateGUI.class.getResourceAsStream("/assets/KNEL.png")); + } catch (IOException e) { + e.printStackTrace(); + } + if(bi!=null) + setIconImage(bi); + setTitleColor(new Color(0,0,0,80)); + getContentPane().setBackground(new Color(0,0,0,80)); + getTitlelabel().setForeground(Color.WHITE); + + + + YScrollPane ysp = new YScrollPane(660); + ysp.setOpaque(false); + getContentPane().add(ysp); + JPanel wv = ysp.getView(); + + /*JProgressBar progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setForeground(Color.YELLOW); + progressBar.setBorder(null); + progressBar.setPreferredSize(new Dimension(100, 4)); + ysp.add(progressBar, BorderLayout.NORTH);*/ + wv.addComponentListener(new ComponentListener() { + + @Override + public void componentShown(ComponentEvent e) { + // TODO 自动生成的方法存根 + + } + + @Override + public void componentResized(ComponentEvent e) { + // TODO 自动生成的方法存根 + + } + + @Override + public void componentMoved(ComponentEvent e) { + repaint(); + } + + @Override + public void componentHidden(ComponentEvent e) { + // TODO 自动生成的方法存根 + + } + }); + setTitle(title); + setSize(680, 430); + setLocationRelativeTo(null); + + //setVisible(true); + + if (SystemTray.isSupported()) { + st = SystemTray.getSystemTray(); + ti = new TrayIcon(bi); + ti.setImageAutoSize(true); + PopupMenu jpm = new PopupMenu(); + MenuItem mix = new MenuItem("open"); + mix.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + setVisible(true); + } + }); + jpm.add(mix); + MenuItem mi = new MenuItem("exit"); + mi.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + close(); + } + }); + jpm.add(mi); + ti.setPopupMenu(jpm); + try { + st.add(ti); + } catch (AWTException e1) { + e1.printStackTrace(); + } + ti.addActionListener((x) -> { + setVisible(true); + }); + } + + repaint(); + + /*progressBar.setIndeterminate(false); + progressBar.setValue(100); + progressBar.setForeground(Color.GREEN);*/ + Timer t= new Timer("状态刷新线程",true); + tsk=new TimerTask() { + + @Override + public void run() { + synchronized (kc.getLines()) { + loop:for (Iterator iterator = kc.getLines().iterator(); iterator.hasNext();) { + KLALBRemoteLine ent = iterator.next(); + Component[] count=ysp.getView().getComponents(); + for (int i = 0; i < count.length; i++) { + Component tp=count[i]; + if(tp instanceof TPanel) { + if(((TPanel) tp).getTunnel().equals(ent)) { + continue loop; + } + } + } + TPanel tp=new TPanel(ent); + ysp.getView().add(tp); + } + Component[] count=ysp.getView().getComponents(); + for (int i = 0; i < count.length; i++) { + Component tp=count[i]; + if(tp instanceof TPanel) { + if(kc.getLines().contains(((TPanel) tp).getTunnel())) { + ((TPanel) tp).updateTraffic(); + }else { + ysp.getView().remove(tp); + } + } + } + + } + repaint(); + } + }; + t.schedule(tsk, 500, 500); + } + + public void close() { + setVisible(false); + if(tsk!=null) { + tsk.cancel(); + } + st.remove(ti); + } + + public static void main(String[] args) throws IOException, InterruptedException {//,new MultipurposeSocketAddress("frp-mom.top:22226"),new MultipurposeSocketAddress("0.0.0.0:26468") + KLALBStateGUI kcg=new KLALBStateGUI(new KLALBController(),"KNE云网络负载均衡客户端 V2.0"); + Thread.sleep(10000); + kcg.close(); + } +} diff --git a/src/org/kne/cloud/network/klalb/LineManager.java b/src/org/kne/cloud/network/klalb/LineManager.java deleted file mode 100644 index 8e5e15a..0000000 --- a/src/org/kne/cloud/network/klalb/LineManager.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.kne.cloud.network.klalb; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.util.Iterator; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.Hashtable; - -import org.kne.cloud.network.klalb.LineManager.LineEntry; -import org.kne.cloud.network.mport.MultipurposeSocketAddress; -import org.kne.cloud.network.mport.ThreadTool; - -public class LineManager extends Hashtable{ - private Object lock=new Object(); - private KLALBController controller; - public LineManager(KLALBController controller) { - super(); - this.controller = controller; - } - /*public void applyList(HostPortList hpl) { - HostPortList lh=(HostPortList) hpl.clone(); - for (Iterator iterator = hm.iterator(); iterator.hasNext();) { - LineEntry hostPort = (LineEntry) iterator.next(); - lh.remove(hostPort.getHp()); - } - for (Iterator iterator = lh.iterator(); iterator.hasNext();) { - MultipurposeSocketAddress hostPort = (MultipurposeSocketAddress) iterator.next(); - LineEntry le=new LineEntry(hostPort); - hm.add(le); - ThreadTool.makeVDaemonThreadIfSupport("线路监视线程", le).start(); - } - - for (Iterator iterator = hm.iterator(); iterator.hasNext();) { - LineEntry hostPort = (LineEntry) iterator.next(); - if(!hpl.contains(hostPort.getHp())) { - hostPort.close(); - iterator.remove(); - } - } - - }*/ - public void retry() { - synchronized (lock) { - lock.notifyAll(); - } - } - public synchronized void addHostPort(MultipurposeSocketAddress v) { - LineEntry le=new LineEntry(v); - if(putIfAbsent(v,le)==null) - le.open(); - } - public synchronized void removeHostPort(MultipurposeSocketAddress v) { - for (Iterator> iterator = entrySet().iterator(); iterator.hasNext();) { - java.util.Map.Entry hostPort = iterator.next(); - if(hostPort.getValue().equals(v)) { - hostPort.getValue().close(); - iterator.remove(); - } - } - } - - - - public KLALBController getController() { - return controller; - } - - public class LineEntry implements Runnable{ - private MultipurposeSocketAddress hp; - private KLALBRemoteSocket krs; - private volatile boolean flag=true; - private Monitor monitor=new Monitor(); - - - public Monitor getMonitor() { - return monitor; - } - - public MultipurposeSocketAddress getHp() { - return hp; - } - - public LineEntry(MultipurposeSocketAddress hp) { - super(); - this.hp = hp; - } - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(hp.toString()); - sb.append('\t'); - sb.append(monitor.toString()); - return sb.toString(); - } - - public String toString2() { - StringBuilder sb=new StringBuilder(); - - sb.append(hp.toString()); - sb.append('\n'); - sb.append(monitor.toString2()); - return sb.toString(); - } - - @Override - public void run() { - while(true){ - try { - monitor.setState(Monitor.CONNECTING); - krs=new KLALBRemoteSocket(hp.connectSocket(3000),monitor); - //System.out.println("open"); - controller.addRemoteSocket(krs); - monitor.resetCoolingTime(); - monitor.setState(Monitor.ONLINE); - krs.waitforlose(); - //System.out.println("close"); - } catch (UnknownHostException e) { - } catch (IOException e) { - //e.printStackTrace(); - }finally { - monitor.setState(Monitor.OFFLINE); - try { - if(krs!=null) - krs.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - if(!flag) { - break; - } - try { - synchronized (lock) { - lock.wait(monitor.getCoolingTime()); - } - monitor.incCoolingTime(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - } - public void open() { - - ThreadTool.makeVDaemonThreadIfSupport("线路监视线程", this).start(); - } - public void close() { - flag=false; - if(krs!=null) - try { - krs.close(); - } catch (IOException e) { - // TODO 自动生成的 catch 块 - e.printStackTrace(); - } - - } - } -} diff --git a/src/org/kne/cloud/network/klalb/NACKTPacket.java b/src/org/kne/cloud/network/klalb/NACKTPacket.java new file mode 100644 index 0000000..1162d18 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/NACKTPacket.java @@ -0,0 +1,63 @@ +package org.kne.cloud.network.klalb; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class NACKTPacket extends KLALBPacket implements PortPacket{ + private int sport,dport; + private long number; + + + public NACKTPacket(int sport,int dport,long number) { + super(NACKT); + this.sport=sport; + this.dport=dport; + this.number=number; + } + + public NACKTPacket() { + super(NACKT); + } + + @Override + public String toString() { + return "NACKT "+sport+"->"+dport+" "+number+"[]"; + } + + public int getSport() { + return sport; + } + + @Override + public long getLength() { + return super.getLength()+17; + } + + public int getDport() { + return dport; + } + + public long getNumber() { + return number; + } + + + @Override + protected void writeToStream(DataOutput dto) throws IOException { + super.writeToStream(dto); + dto.writeInt(sport); + dto.writeInt(dport); + dto.writeLong(number); + } + + @Override + protected void readFromStream(DataInput din) throws IOException { + super.readFromStream(din); + sport=din.readInt(); + dport=din.readInt(); + number=din.readLong(); + } + +} diff --git a/src/org/kne/cloud/network/klalb/PortBinder.java b/src/org/kne/cloud/network/klalb/PortBinder.java new file mode 100644 index 0000000..31d93b7 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/PortBinder.java @@ -0,0 +1,243 @@ +package org.kne.cloud.network.klalb; + +import java.net.BindException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.google.gson.internal.Pair; + +public class PortBinder { + private static Inet6Address ANYLA; + static { + try { + ANYLA=(Inet6Address) Inet6Address.getByAddress(new byte[16]); + } catch (UnknownHostException e) { + } + } + private KLALBController controller; + public PortBinder(KLALBController controller) { + super(); + this.controller = controller; + } + private ReentrantReadWriteLock bindMaplock=new ReentrantReadWriteLock(); + private MapbindMap=new ConcurrentHashMap<>(); + private ReentrantReadWriteLock listenMaplock=new ReentrantReadWriteLock(); + private MaplistenMap=new ConcurrentHashMap<>(); + private ReentrantReadWriteLock connectMaplock=new ReentrantReadWriteLock(); + private Map,BindableKLALBPacketConsumer>connectMap=new ConcurrentHashMap<>(); + private volatile int portn=65535; + public void bind(BindableKLALBPacketConsumer ks) throws BindException { + bindMaplock.writeLock().lock(); + try { + int pold=ks.getLocalPort(); + if(pold==0) { + int portnod=portn+1; + if(portnod>65535) { + portnod=1; + } + int cnt=0; + while(true) { + portn++; + if(portn>65535) { + portn=1; + } + if(portn==portnod) { + cnt++; + if(cnt>1) { + throw new BindException("can't alloc port"); + } + } + boolean flag=true; + for (Iterator iterator = bindMap.keySet().iterator(); iterator.hasNext();) { + InetSocketAddress bindableKLALBPacketConsumer = (InetSocketAddress) iterator + .next(); + if(Objects.equals(ks.getLocalInetAddress(), bindableKLALBPacketConsumer.getAddress())&&portn==bindableKLALBPacketConsumer.getPort()) { + flag=false; + break; + } + } + if(flag) + break; + } + ks.setLocalPort(portn); + }else { + for (Iterator iterator = bindMap.keySet().iterator(); iterator.hasNext();) { + InetSocketAddress bindableKLALBPacketConsumer = (InetSocketAddress) iterator + .next(); + if(Objects.equals(ks.getLocalInetAddress(), bindableKLALBPacketConsumer.getAddress())&&pold==bindableKLALBPacketConsumer.getPort()) { + throw new BindException("port " + pold + " is already bind!"); + } + } + } + bindMap.put(new InetSocketAddress(ks.getLocalInetAddress(), ks.getLocalPort()),ks); + }finally { + bindMaplock.writeLock().unlock(); + } + } + + public void unbind(BindableKLALBPacketConsumer ks) { + bindMaplock.writeLock().lock(); + try { + removeAsValue(bindMap, ks); + }finally { + bindMaplock.writeLock().unlock(); + } + } + public void connect(BindableKLALBPacketConsumer ks) throws BindException { + connectMaplock.writeLock().lock(); + try { + connectMap.put(new Pair( new InetSocketAddress(ks.getLocalInetAddress(), ks.getLocalPort()), new InetSocketAddress(ks.getRemoteInetAddress(), ks.getPort())),ks); + }finally { + connectMaplock.writeLock().unlock(); + } + } + public void disconnect(BindableKLALBPacketConsumer ks) throws BindException { + connectMaplock.writeLock().lock(); + try { + removeAsValue(connectMap, ks); + }finally { + connectMaplock.writeLock().unlock(); + } + } + + public void listen(BindableKLALBPacketConsumer ks) throws BindException { + listenMaplock.writeLock().lock(); + try { + listenMap.put(new InetSocketAddress(ks.getLocalInetAddress(), ks.getLocalPort()),ks); + }finally { + listenMaplock.writeLock().unlock(); + } + } + public void unlisten(BindableKLALBPacketConsumer ks) throws BindException { + /*System.out.println(listenMap); + new RuntimeException("!").printStackTrace();*/ + listenMaplock.writeLock().lock(); + try { + removeAsValue(listenMap, ks); + }finally { + listenMaplock.writeLock().unlock(); + } + } + private static void removeAsValue(Map m,Object obj) { + Collection col= m.values(); + for (Iterator iterator = col.iterator(); iterator.hasNext();) { + Object object = (Object) iterator.next(); + if(object.equals(obj)) { + iterator.remove(); + } + } + } + /*private ReadWriteLock rwl=new ReentrantReadWriteLock(); + private List bindlist=new ArrayList<>(); + public void bind(BindableKLALBPacketConsumer ks) throws BindException { + rwl.writeLock().lock(); + try { + int pold=ks.getLocalPort(); + if(pold==0) { + int i=1; + while(true) { + if(i>65535) { + throw new BindException("can't alloc port"); + } + boolean flag=true; + for (Iterator iterator = bindlist.iterator(); iterator.hasNext();) { + BindableKLALBPacketConsumer bindableKLALBPacketConsumer = (BindableKLALBPacketConsumer) iterator + .next(); + if(Objects.equals(ks.getLocalInetAddress(), bindableKLALBPacketConsumer.getLocalInetAddress())&&i==bindableKLALBPacketConsumer.getLocalPort()&&Objects.equals( ks.getRemoteInetAddress(), bindableKLALBPacketConsumer.getRemoteInetAddress())&&(ks.getPort()==bindableKLALBPacketConsumer.getPort())) { + flag=false; + break; + } + } + if(flag) + break; + i++; + } + ks.setLocalPort(i); + }else { + for (Iterator iterator = bindlist.iterator(); iterator.hasNext();) { + BindableKLALBPacketConsumer bindableKLALBPacketConsumer = (BindableKLALBPacketConsumer) iterator + .next(); + if(Objects.equals(ks.getLocalInetAddress(), bindableKLALBPacketConsumer.getLocalInetAddress())&&pold==bindableKLALBPacketConsumer.getLocalPort()&&Objects.equals( ks.getRemoteInetAddress(), bindableKLALBPacketConsumer.getRemoteInetAddress())&&(ks.getPort()==bindableKLALBPacketConsumer.getPort())) { + throw new BindException("port " + pold + " is already bind!"); + } + } + } + bindlist.add(ks); + }finally { + rwl.writeLock().unlock(); + } + } + public boolean distributePacketToConsumer(KLALBRemoteLine krl,PortPacket packet) throws SocketTimeoutException { + rwl.readLock().lock(); + boolean b=false; + try { + for (Iterator iterator = bindlist.iterator(); iterator.hasNext();) { + BindableKLALBPacketConsumer bindableKLALBPacketConsumer = (BindableKLALBPacketConsumer) iterator.next(); + if(bindableKLALBPacketConsumer.getLocalPort()==packet.getDport()&&(controller.getSelf().equals(bindableKLALBPacketConsumer.getLocalInetAddress())||ANYLA.equals(bindableKLALBPacketConsumer.getLocalInetAddress()))) { + if(bindableKLALBPacketConsumer.getRemoteInetAddress()==null||bindableKLALBPacketConsumer.getRemoteInetAddress().equals(ANYLA)) { + bindableKLALBPacketConsumer.accept(krl, (KLALBPacket) packet); + b=true; + }else { + if(bindableKLALBPacketConsumer.getPort()==packet.getSport()&&bindableKLALBPacketConsumer.getRemoteInetAddress().equals(krl.getRemoteVaddr())) { + bindableKLALBPacketConsumer.accept(krl, (KLALBPacket) packet); + b=true; + } + } + } + } + }finally { + rwl.readLock().unlock(); + } + return b; + }*/ + public boolean distributePacketToConsumer(KLALBRemoteLine krl,PortPacket packet) throws SocketTimeoutException { + InetSocketAddress local=new InetSocketAddress(controller.getSelf(), packet.getDport()); + InetSocketAddress remote=new InetSocketAddress(krl.getRemoteVaddr(), packet.getSport()); + BindableKLALBPacketConsumer bkc=connectMap.get(new Pair(local, remote)); + if(bkc!=null) { + bkc.accept(krl, (KLALBPacket) packet); + return true; + } + InetSocketAddress localany=new InetSocketAddress(ANYLA, packet.getDport()); + bkc=connectMap.get(new Pair(localany, remote)); + if(bkc!=null) { + bkc.accept(krl, (KLALBPacket) packet); + return true; + } + + bkc=listenMap.get(local); + if(bkc!=null) { + bkc.accept(krl, (KLALBPacket) packet); + return true; + } + bkc=listenMap.get(localany); + if(bkc!=null) { + bkc.accept(krl, (KLALBPacket) packet); + return true; + } + + + return false; + } + public boolean checkIsBind(KLALBVirtualSocketImpl klalbVirtualSocketImpl) { + return bindMap.containsValue(klalbVirtualSocketImpl); + } + public boolean checkIsConnect(BindableKLALBPacketConsumer kservers,InetSocketAddress isaf) throws BindException { + return connectMap.containsKey(new Pair( new InetSocketAddress(kservers.getLocalInetAddress(), kservers.getLocalPort()),isaf)); + } + +} \ No newline at end of file diff --git a/src/org/kne/cloud/network/klalb/PortPacket.java b/src/org/kne/cloud/network/klalb/PortPacket.java new file mode 100644 index 0000000..aaf6e9d --- /dev/null +++ b/src/org/kne/cloud/network/klalb/PortPacket.java @@ -0,0 +1,8 @@ +package org.kne.cloud.network.klalb; + +public interface PortPacket { + + public int getSport(); + + public int getDport(); +} diff --git a/src/org/kne/cloud/network/klalb/StreamKLALBPacketLink.java b/src/org/kne/cloud/network/klalb/StreamKLALBPacketLink.java new file mode 100644 index 0000000..0ca035f --- /dev/null +++ b/src/org/kne/cloud/network/klalb/StreamKLALBPacketLink.java @@ -0,0 +1,59 @@ +package org.kne.cloud.network.klalb; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketException; +import java.nio.Buffer; + +public class StreamKLALBPacketLink implements KLALBPacketLink { + + @Override + public String toString() { + return "StreamKLALBPacketLink [connectSocket=" + connectSocket + "]"; + } + + private Socket connectSocket; + private KLALBInputStream in; + private KLALBOutputStream out; + public StreamKLALBPacketLink(Socket connectSocket) throws IOException { + this.connectSocket=connectSocket; + connectSocket.setTcpNoDelay(true); + out=new KLALBOutputStream(connectSocket.getOutputStream()); + out.flush(); + in=new KLALBInputStream( connectSocket.getInputStream()); + //connectSocket.setSendBufferSize(1024*1024*1024); + } + + @Override + public void writePacket(KLALBPacket kp) throws IOException { + out.writePacket(kp); + } + + @Override + public KLALBPacket readPacket() throws IOException { + return in.readPacket(); + } + + @Override + public void close() throws IOException { + connectSocket.close(); + } + + @Override + public boolean isClosed() { + return connectSocket.isClosed(); + } + + @Override + public void setSoTimeout(int val) throws SocketException { + connectSocket.setSoTimeout(val); + } + + @Override + public int getSoTimeout() throws SocketException { + return connectSocket.getSoTimeout(); + } + +} diff --git a/src/org/kne/cloud/network/klalb/SumQueue.java b/src/org/kne/cloud/network/klalb/SumQueue.java new file mode 100644 index 0000000..ea8eac6 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/SumQueue.java @@ -0,0 +1,216 @@ +package org.kne.cloud.network.klalb; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; +import java.util.Spliterator; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class SumQueue extends AbstractQueue implements Queue { + private AbstractQueue abq; + public SumQueue(AbstractQueue abq) { + this.abq=abq; + } + private AtomicLong sumed=new AtomicLong(); + public long getSumValue() { + return sumed.get(); + } + + private void resum() { + AtomicLong l=new AtomicLong(); + forEach((t)->{ + l.addAndGet(t.getValue()); + }); + sumed.set(l.get()); + } + + + + @Override + public boolean removeIf(Predicate filter) { + boolean removeIf = abq.removeIf(filter); + resum(); + return removeIf; + } + + + @Override + public boolean offer(T e) { + boolean offer = abq.offer(e); + if(offer) + sumed.addAndGet(e.getValue()); + return offer; + } + + @Override + public T poll() { + T poll = abq.poll(); + if(poll!=null) { + sumed.addAndGet(-poll.getValue()); + } + return poll; + } + + + + @Override + public boolean add(T e) { + boolean add = abq.add(e); + if(add) { + sumed.addAndGet(e.getValue()); + } + return add; + } + + @Override + public T remove() { + T remove = abq.remove(); + if(remove!=null) { + sumed.addAndGet(-remove.getValue()); + } + return remove; + } + + + @Override + public void clear() { + abq.clear(); + sumed.set(0); + } + + @Override + public boolean addAll(Collection c) { + boolean addAll = abq.addAll(c); + resum(); + return addAll; + } + + @Override + public Iterator iterator() { + return null; + } + + + + + + + + + + @Override + public boolean remove(Object o) { + boolean b = abq.remove(o); + if(b) + sumed.addAndGet(((Sumable)o).getValue()); + return b; + } + + + @Override + public boolean removeAll(Collection c) { + boolean removeAll = abq.removeAll(c); + resum(); + return removeAll; + } + + @Override + public boolean retainAll(Collection c) { + boolean retainAll = abq.retainAll(c); + resum(); + return retainAll; + } + + + + + + + + + + + + + + + @Override + public T element() { + return (T) abq.element(); + } + + @Override + public T peek() { + return (T) abq.peek(); + } + @Override + public int size() { + return abq.size(); + } + + @Override + public boolean isEmpty() { + return abq.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return abq.contains(o); + } + + @Override + public Object[] toArray() { + return abq.toArray(); + } + + @Override + public Spliterator spliterator() { + return abq.spliterator(); + } + + @Override + public Stream stream() { + // TODO 自动生成的方法存根 + return abq.stream(); + } + + @Override + public Stream parallelStream() { + // TODO 自动生成的方法存根 + return abq.parallelStream(); + } + + @Override + public void forEach(Consumer action) { + // TODO 自动生成的方法存根 + abq.forEach(action); + } + + @Override + public boolean containsAll(Collection c) { + // TODO 自动生成的方法存根 + return abq.containsAll(c); + } + + @Override + public T[] toArray(IntFunction generator) { + return (T[]) abq.toArray(generator); + } +@Override + public T[] toArray(T[] a) { + // TODO 自动生成的方法存根 + return (T[]) abq.toArray(a); + } + @Override + public String toString() { + // TODO 自动生成的方法存根 + return abq.toString(); + } + +} \ No newline at end of file diff --git a/src/org/kne/cloud/network/klalb/Sumable.java b/src/org/kne/cloud/network/klalb/Sumable.java new file mode 100644 index 0000000..b6da654 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/Sumable.java @@ -0,0 +1,5 @@ +package org.kne.cloud.network.klalb; + +public interface Sumable { + public long getValue(); +} \ No newline at end of file diff --git a/src/org/kne/cloud/network/klalb/TESTPacket.java b/src/org/kne/cloud/network/klalb/TESTPacket.java new file mode 100644 index 0000000..c56dfe7 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/TESTPacket.java @@ -0,0 +1,37 @@ +package org.kne.cloud.network.klalb; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class TESTPacket extends KLALBPacket { + private static byte[]K=new byte[1024]; + @Override + public long getLength() { + return super.getLength()+1024; + } + + + public TESTPacket() { + super(TEST); + } + + + + @Override + public String toString() { + return "TEST"; + } + + @Override + protected void writeToStream(DataOutput dto) throws IOException { + super.writeToStream(dto); + dto.write(K); + } + + @Override + protected void readFromStream(DataInput din) throws IOException { + super.readFromStream(din); + din.readFully(K, 0, K.length); + } +} diff --git a/src/org/kne/cloud/network/klalb/TPanel.java b/src/org/kne/cloud/network/klalb/TPanel.java new file mode 100644 index 0000000..5224ea4 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/TPanel.java @@ -0,0 +1,132 @@ +package org.kne.cloud.network.klalb; + +import javax.swing.JPanel; +import javax.swing.JLabel; +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.SwingConstants; +import javax.swing.border.LineBorder; + +import java.awt.Font; +import java.util.Objects; + +public class TPanel extends JPanel { + private JLabel tname; + private JLabel targ; + private KLALBRemoteLine tunnel; + + @Override + public int hashCode() { + return Objects.hash(tunnel); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TPanel other = (TPanel) obj; + return Objects.equals(tunnel, other.tunnel); + } + + public JLabel getTname() { + return tname; + } + + public JLabel getTarg() { + return targ; + } + + /** + * @wbp.parser.constructor + */ + public TPanel(KLALBRemoteLine t) { + this.tunnel = t; + setOpaque(false); + setLayout(new BorderLayout(0, 0)); + + tname = new JLabel(); + if(t.getSocketAddress()!=null) { + tname.setText(t.getSocketAddress().toString()); + }else { + tname.setText(t.getKplink().toString()); + } + tname.setFont(new Font("宋体", Font.BOLD, 16)); + add(tname, BorderLayout.WEST); + + targ = new JLabel(); + targ.setFont(new Font("宋体", Font.PLAIN, 13)); + targ.setHorizontalAlignment(SwingConstants.TRAILING); + targ.setForeground(Color.YELLOW); + add(targ, BorderLayout.CENTER); + + setBorder(new LineBorder(Color.BLACK)); + setSize(650, 25); + setPreferredSize(getSize()); + } + + private volatile long up, down, ups, downs; + private volatile long delay=Long.MAX_VALUE; + + public long getDelay() { + return delay; + } + + private void updateText() { + String dl; + if (delay == Long.MIN_VALUE) + dl = "??"; + else + dl = Long.toString(delay / 1000000L); + targ.setText(bytesUnit(up) + "\u2191 " + bytesUnit(down) + "\u2193 " + bytesUnit(ups) + "/s\u2191 " + + bytesUnit(downs) + "/s\u2193 " + dl + "ms"); + } + + public KLALBRemoteLine getTunnel() { + return tunnel; + } + + private String bytesUnit(long v) { + if (v >= 1024L * 1024 * 1024 * 1024 * 1024) { + return String.format("%.1f", v / (1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)) + "PB"; + } else if (v >= 1024L * 1024 * 1024 * 1024) { + return String.format("%.1f", v / (1024.0 * 1024.0 * 1024.0 * 1024.0)) + "TB"; + } else if (v >= 1024L * 1024 * 1024) { + return String.format("%.1f", v / (1024.0 * 1024.0 * 1024.0)) + "GB"; + } else if (v >= 1024L * 1024) { + return String.format("%.1f", v / (1024.0 * 1024.0)) + "MB"; + } else if (v >= 1024L) { + return String.format("%.1f", v / (1024.0)) + "KB"; + } else { + return v + "B"; + } + } + + public void updateTraffic() { + switch (tunnel.getMonitor().getState()) { + case Monitor.OFFLINE: + tname.setForeground(Color.RED); + break; + case Monitor.CONNECTING: + tname.setForeground(Color.YELLOW); + break; + case Monitor.ONLINE: + tname.setForeground(Color.GREEN); + break; + default: + break; + } + up = tunnel.getMonitor().getOutTraffic(); + down = tunnel.getMonitor().getInTraffic(); + this.ups = tunnel.getMonitor().getOutSpeed(); + this.downs = tunnel.getMonitor().getInSpeed(); + this.delay = tunnel.getMonitor().getLatencyAvg(); + //System.out.println(delay); + updateText(); + } + +} diff --git a/src/org/kne/cloud/network/klalb/VADDRACKPacket.java b/src/org/kne/cloud/network/klalb/VADDRACKPacket.java new file mode 100644 index 0000000..1ad2de8 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/VADDRACKPacket.java @@ -0,0 +1,14 @@ +package org.kne.cloud.network.klalb; + +public class VADDRACKPacket extends KLALBPacket { + + public VADDRACKPacket() { + super(VADDRACK); + } + + @Override + public String toString() { + return "VADDRACK"; + } + +} diff --git a/src/org/kne/cloud/network/klalb/VADDRREQPacket.java b/src/org/kne/cloud/network/klalb/VADDRREQPacket.java new file mode 100644 index 0000000..1859285 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/VADDRREQPacket.java @@ -0,0 +1,14 @@ +package org.kne.cloud.network.klalb; + +public class VADDRREQPacket extends KLALBPacket { + + public VADDRREQPacket() { + super(VADDRREQ); + } + + @Override + public String toString() { + return "VADDRREQ"; + } + +} diff --git a/src/org/kne/cloud/network/minecraft/KLALBJsonMotdInserter.java b/src/org/kne/cloud/network/minecraft/KLALBJsonMotdInserter.java new file mode 100644 index 0000000..e8eef76 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/KLALBJsonMotdInserter.java @@ -0,0 +1,37 @@ +package org.kne.cloud.network.minecraft; + +import java.net.Inet6Address; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.kne.cloud.network.klalb.CONST; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class KLALBJsonMotdInserter implements Function { + private Supplier vaddrsup; + private Suppliervportsup; + @Override + public MinecraftPacket apply(MinecraftPacket t) { + if(t instanceof MinecraftMotdResponse) { + String src=((MinecraftMotdResponse) t).getJson(); + JsonObject jo=(JsonObject) new JsonParser().parse(src); + JsonObject jok=new JsonObject(); + jok.addProperty("version", CONST.klalbver); + jok.addProperty("vaddr", vaddrsup.get().getHostAddress()); + jok.addProperty("vport", vportsup.get()); + jo.add("KLALB", jok); + String des=jo.toString(); + //System.out.println(des); + return new MinecraftMotdResponse(des); + } + return t; + } + public KLALBJsonMotdInserter(Supplier vaddrsup, Supplier vportsup) { + super(); + this.vaddrsup = vaddrsup; + this.vportsup = vportsup; + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftDefaultPacket.java b/src/org/kne/cloud/network/minecraft/MinecraftDefaultPacket.java new file mode 100644 index 0000000..0860a88 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftDefaultPacket.java @@ -0,0 +1,35 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftDefaultPacket extends MinecraftPacket { + private byte[]data; + + public byte[] getData() { + return data; + } + + public MinecraftDefaultPacket(int type) { + super(type ); + } + + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.write(data); + } + + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + data=din.readAllBytes(); + } + + public MinecraftDefaultPacket( int type, byte[] data) { + super(type); + this.data = data; + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftHandShakePacket.java b/src/org/kne/cloud/network/minecraft/MinecraftHandShakePacket.java new file mode 100644 index 0000000..7e79785 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftHandShakePacket.java @@ -0,0 +1,57 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftHandShakePacket extends MinecraftPacket { + int version; + String addr; + int port; + int nextstate; + public MinecraftHandShakePacket() { + super(HANDSHAKE_MOTD); + } + public MinecraftHandShakePacket( int version, String addr, int port, int nextstate) { + super(HANDSHAKE_MOTD); + this.version = version; + this.addr = addr; + this.port = port; + this.nextstate = nextstate; + } + public int getVersion() { + return version; + } + @Override + public String toString() { + return "MinecraftHandShakePacket [version=" + version + ", addr=" + addr + ", port=" + port + ", nextstate=" + + nextstate + "]"; + } + public String getAddr() { + return addr; + } + public int getPort() { + return port; + } + public int getNextstate() { + return nextstate; + } + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.writeVarInt(version); + dto.writeVarUTF8(addr); + dto.writeShort(port); + dto.writeVarInt(nextstate); + } + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + version=din.readVarInt(); + addr=din.readVarUTF8(); + port=din.readUnsignedShort(); + nextstate=din.readVarInt(); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftInputStream.java b/src/org/kne/cloud/network/minecraft/MinecraftInputStream.java new file mode 100644 index 0000000..9d6b1fc --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftInputStream.java @@ -0,0 +1,116 @@ +package org.kne.cloud.network.minecraft; +import static org.kne.cloud.network.minecraft.MinecraftPacket.*; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.kne.cloud.network.klalb.PINGPacket; +import org.kne.io.VarDataInputStream; + +public class MinecraftInputStream extends VarDataInputStream { + private MinecraftProtocolContext context; + public MinecraftInputStream(InputStream in,MinecraftProtocolContext context) { + super(grin(in)); + this.context=context; + } + private static InputStream grin(InputStream in) { + InputStream rin; + if(in.markSupported()) { + rin=in; + }else { + rin=new BufferedInputStream(in,8192); + } + return rin; + } + public MinecraftProtocolContext getContext() { + return context; + } + public MinecraftPacket readPacket() throws IOException { + mark(1); + read(); + reset(); + if(context.getState()==2) { + MinecraftUnsolvedPacket mup=new MinecraftUnsolvedPacket(); + mup.readFromStream(this); + if(mup.getLenth()==-1) { + return null; + }else { + return mup; + } + } + byte[]b=readPacketVarBytes(); + if(b==null) + return null; + VarDataInputStream vdi=new VarDataInputStream(new ByteArrayInputStream(b)); + int type=vdi.readVarInt(); + MinecraftPacket mp; + if(context.isServer()) { + switch(context.getState()){ + case 0: + switch(type) { + case HANDSHAKE_MOTD: + mp=new MinecraftHandShakePacket(); + mp.readFromStream(vdi); + context.setState(((MinecraftHandShakePacket) mp).getNextstate()); + break; + + default : + mp=new MinecraftDefaultPacket(type); + mp.readFromStream(vdi); + break; + } + break; + case 1: + switch(type) { + case HANDSHAKE_MOTD: + mp=new MinecraftMotdRequest(); + mp.readFromStream(vdi); + break; + case PING_PONG: + mp=new MinecraftPingPacket(); + mp.readFromStream(vdi); + break; + default : + mp=new MinecraftDefaultPacket(type); + mp.readFromStream(vdi); + break; + } + break; + default: + mp=new MinecraftDefaultPacket(type); + mp.readFromStream(vdi); + break; + } + + }else { + switch(context.getState()) { + case 1: + switch(type) { + case HANDSHAKE_MOTD: + + mp=new MinecraftMotdResponse(); + mp.readFromStream(vdi); + break; + case PING_PONG: + mp=new MinecraftPongPacket(); + mp.readFromStream(vdi); + break; + default: + mp=new MinecraftDefaultPacket(type); + mp.readFromStream(vdi); + break; + } + break; + + default : + mp=new MinecraftDefaultPacket(type); + mp.readFromStream(vdi); + } + } + //System.out.println(mp); + return mp; + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftMotdRequest.java b/src/org/kne/cloud/network/minecraft/MinecraftMotdRequest.java new file mode 100644 index 0000000..972efbd --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftMotdRequest.java @@ -0,0 +1,14 @@ +package org.kne.cloud.network.minecraft; + +public class MinecraftMotdRequest extends MinecraftPacket { + + @Override + public String toString() { + return "MotdMinecraftRequest []"; + } + + public MinecraftMotdRequest() { + super(HANDSHAKE_MOTD); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftMotdResponse.java b/src/org/kne/cloud/network/minecraft/MinecraftMotdResponse.java new file mode 100644 index 0000000..7d8fbb9 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftMotdResponse.java @@ -0,0 +1,38 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftMotdResponse extends MinecraftPacket { + private String json; + + public String getJson() { + return json; + } + public MinecraftMotdResponse(String json) { + this(); + this.json=json; + } + @Override + public String toString() { + return "MotdMinecraftResponse [json=" + json + "]"; + } + public MinecraftMotdResponse() { + super(HANDSHAKE_MOTD); + } + + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.writeVarUTF8(json); + } + + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + json=din.readVarUTF8(); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftOutputStream.java b/src/org/kne/cloud/network/minecraft/MinecraftOutputStream.java new file mode 100644 index 0000000..64bea18 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftOutputStream.java @@ -0,0 +1,38 @@ +package org.kne.cloud.network.minecraft; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.net.ssl.HandshakeCompletedEvent; + +import org.kne.io.VarDataOutputStream; + +public class MinecraftOutputStream extends VarDataOutputStream { + ByteArrayOutputStream bao=new ByteArrayOutputStream(); + private MinecraftProtocolContext context; + + public MinecraftOutputStream(OutputStream out,MinecraftProtocolContext context) { + super(out); + this.context=context; + } + public MinecraftProtocolContext getContext() { + return context; + } + public void writePacket(MinecraftPacket mp) throws IOException { + if (mp instanceof MinecraftUnsolvedPacket) { + mp.writeToStream(this); + }else { + VarDataOutputStream vdo=new VarDataOutputStream(bao); + vdo.writeVarInt(mp.getType()); + mp.writeToStream(vdo); + byte[]baob=bao.toByteArray(); + bao.reset(); + writeVarBytes(baob); + if(mp instanceof MinecraftHandShakePacket) { + MinecraftHandShakePacket hsmp=(MinecraftHandShakePacket) mp; + context.setState(hsmp.getNextstate()); + } + } + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftPacket.java b/src/org/kne/cloud/network/minecraft/MinecraftPacket.java new file mode 100644 index 0000000..c86f3b0 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftPacket.java @@ -0,0 +1,45 @@ +package org.kne.cloud.network.minecraft; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public abstract class MinecraftPacket { + public static final int HANDSHAKE_MOTD=0x00; + public static final int PING_PONG=0x01; + private int type; + private boolean solved; + public int getType() { + return type; + } + +public boolean isSolved() { + return solved; + } + +@Override + public String toString() { + return "MinecraftPacket [type=" + type + "]"; + } + +public MinecraftPacket(boolean solved) { + super(); + this.solved = solved; +} + +public MinecraftPacket(int type) { + super(); + this.type = type; + } + +protected void writeToStream(VarDataOutputStream dto) throws IOException { + + } + protected void readFromStream(VarDataInputStream din) throws IOException { + + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftPingPacket.java b/src/org/kne/cloud/network/minecraft/MinecraftPingPacket.java new file mode 100644 index 0000000..54235ef --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftPingPacket.java @@ -0,0 +1,35 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftPingPacket extends MinecraftPacket{ + private long time; + public long getTime() { + return time; + } + public MinecraftPingPacket() { + super(PING_PONG); + } + public MinecraftPingPacket( long time) { + super(PING_PONG); + this.time = time; + } + @Override + public String toString() { + return "MinecraftPingPacket [time=" + time + "]"; + } + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.writeLong(time); + } + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + time=din.readLong(); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftPongPacket.java b/src/org/kne/cloud/network/minecraft/MinecraftPongPacket.java new file mode 100644 index 0000000..0a69940 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftPongPacket.java @@ -0,0 +1,34 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftPongPacket extends MinecraftPacket{ + private long time; + public long getTime() { + return time; + } + public MinecraftPongPacket( long time) { + super(PING_PONG); + this.time = time; + } + @Override + public String toString() { + return "MinecraftPongPacket [time=" + time + "]"; + } + public MinecraftPongPacket() { + super(PING_PONG); + } + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.writeLong(time); + } + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + time=din.readLong(); + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftProtocolContext.java b/src/org/kne/cloud/network/minecraft/MinecraftProtocolContext.java new file mode 100644 index 0000000..a5d639a --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftProtocolContext.java @@ -0,0 +1,22 @@ +package org.kne.cloud.network.minecraft; + +public class MinecraftProtocolContext { + private boolean server; + private volatile int state=0; + public boolean isServer() { + return server; + } + public int getState() { + return state; + } + protected void setState(int state) { + this.state = state; + } + public MinecraftProtocolContext() { + this(false); + } + public MinecraftProtocolContext(boolean server) { + super(); + this.server = server; + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftServerAddress.java b/src/org/kne/cloud/network/minecraft/MinecraftServerAddress.java new file mode 100644 index 0000000..946a77b --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftServerAddress.java @@ -0,0 +1,119 @@ +package org.kne.cloud.network.minecraft; + +import java.net.IDN; +import java.util.Hashtable; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.kne.cloud.network.MultipurposeSocketAddress; + +public class MinecraftServerAddress extends MultipurposeSocketAddress +{ + + private String originAddr; + private int originPort; + private String originStr; + public int getOriginPort() { + return originPort; + } + public String getOriginAddr() { + return originAddr; + } + public String toOriginString() { + return originStr; + } + private MinecraftServerAddress(String ip, int port,String originaddr, int originPort, String addr) + { + super(IDN.toASCII(ip),port); + this.originAddr=originaddr; + this.originPort=originPort; + this.originStr=addr; + } + public static MinecraftServerAddress findAddress(String addr) + { + if (addr == null) + { + return null; + } + else + { + String[] astring = addr.split(":"); + + if (addr.startsWith("[")) + { + int i = addr.indexOf("]"); + + if (i > 0) + { + String s = addr.substring(1, i); + String s1 = addr.substring(i + 1).trim(); + + if (s1.startsWith(":") && s1.length() > 0) + { + s1 = s1.substring(1); + astring = new String[] {s, s1}; + } + else + { + astring = new String[] {s}; + } + } + } + + if (astring.length > 2) + { + astring = new String[] {addr}; + } + + String s2 = astring[0]; + String oaddr=s2; + int j = astring.length > 1 ? parseIntWithDefault(astring[1], 25565) : 25565; + int oport=j; + if (j == 25565) + { + String[] astring1 = getServerAddress(s2); + s2 = astring1[0]; + j = parseIntWithDefault(astring1[1], 25565); + } + + return new MinecraftServerAddress(s2, j,oaddr,oport,addr); + } + } + + /** + * Returns a server's address and port for the specified hostname, looking up the SRV record if possible + */ + private static String[] getServerAddress(String p_78863_0_) + { + try + { + String s = "com.sun.jndi.dns.DnsContextFactory"; + Class.forName("com.sun.jndi.dns.DnsContextFactory"); + Hashtable hashtable = new Hashtable(); + hashtable.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); + hashtable.put("java.naming.provider.url", "dns:"); + hashtable.put("com.sun.jndi.dns.timeout.retries", "1"); + DirContext dircontext = new InitialDirContext(hashtable); + Attributes attributes = dircontext.getAttributes("_minecraft._tcp." + p_78863_0_, new String[] {"SRV"}); + String[] astring = attributes.get("srv").get().toString().split(" ", 4); + return new String[] {astring[3], astring[2]}; + } + catch (Throwable var6) + { + return new String[] {p_78863_0_, Integer.toString(25565)}; + } + } + + private static int parseIntWithDefault(String str, int def) + { + try + { + return Integer.parseInt(str.trim()); + } + catch (Exception e) + { + return def; + } + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftServerPinger.java b/src/org/kne/cloud/network/minecraft/MinecraftServerPinger.java new file mode 100644 index 0000000..8748e92 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftServerPinger.java @@ -0,0 +1,299 @@ +package org.kne.cloud.network.minecraft; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Base64; + +import javax.imageio.ImageIO; + +import org.kne.cloud.network.MultipurposeSocketAddress; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +public class MinecraftServerPinger { + private Socket socket; + private MinecraftProtocolContext mpc=new MinecraftProtocolContext(); + private MultipurposeSocketAddress targetaddr; + + + + private long latency=-1; + private String json; + private BufferedImage icon; + private int max; + private int online; + private String description; + private boolean isOldDescription; + private String version; + private int protocol; + private String klalbversion; + private Inet6Address klalbvaddr; + private int klalbvport; + + public MinecraftServerPinger(MultipurposeSocketAddress target) throws UnknownHostException, IOException { + targetaddr=target; + socket=target.connectSocket(); + socket.setTcpNoDelay(true); + ping(); + analyseJson(); + } + + private void ping() throws IOException { + MinecraftInputStream in=null; + MinecraftOutputStream out=null; + try { + in=new MinecraftInputStream(socket.getInputStream(), mpc); + out=new MinecraftOutputStream(socket.getOutputStream(), mpc); + if(targetaddr instanceof MinecraftServerAddress) { + out.writePacket(new MinecraftHandShakePacket(763, ((MinecraftServerAddress) targetaddr).getOriginAddr(), ((MinecraftServerAddress) targetaddr).getOriginPort(), 1)); + }else { + out.writePacket(new MinecraftHandShakePacket(763, targetaddr.getHost(), targetaddr.getPort(), 1)); + } + out.writePacket(new MinecraftMotdRequest()); + out.flush(); + MinecraftMotdResponse resp=(MinecraftMotdResponse) in.readPacket(); + if(resp==null) + throw new EOFException(); + json=resp.getJson(); + long pst=System.nanoTime(); + out.writePacket(new MinecraftPingPacket(pst)); + out.flush(); + try { + MinecraftPongPacket repo=(MinecraftPongPacket) in.readPacket(); + latency=System.nanoTime()-repo.getTime(); + }catch(EOFException e) { + + } + + }finally { + try { + if(in!=null) + in.close(); + }catch(IOException e) {} + + try { + if(out!=null) + out.close(); + + }catch(IOException e) {} + try { + if(socket!=null) + socket.close(); + }catch(IOException e) {} + } + } + private void analyseJson() { + // System.out.println(json); + JsonObject jobj= (JsonObject) new JsonParser().parse(json); + + JsonObject versiont=(JsonObject) jobj.get("version"); + if(versiont!=null) { + version=versiont.get("name").getAsString(); + protocol=versiont.get("protocol").getAsInt(); + } + + JsonObject players=(JsonObject) jobj.get("players"); + if(players!=null) { + max=players.get("max").getAsInt(); + online=players.get("online").getAsInt(); + } + + JsonElement descriptionj =jobj.get("description"); + if(descriptionj!=null) { + isOldDescription=!(descriptionj instanceof JsonObject); + if(isOldDescription) { + description=descriptionj.getAsString(); + }else { + description=descriptionj.toString(); + } + } + + JsonElement klalb= jobj.get("KLALB"); + if(klalb!=null&&(klalb instanceof JsonObject)) { + JsonObject jobk=(JsonObject) klalb; + klalbversion=jobk.get("version").getAsString(); + try { + klalbvaddr=(Inet6Address) InetAddress.getByName( jobk.get("vaddr").getAsString()); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + klalbvport=jobk.get("vport").getAsInt(); + } + + JsonElement favicon= jobj.get("favicon"); + if(favicon!=null) { + String s=favicon.getAsString(); + if(s.startsWith("data:image/png;base64,")){ + s=s.replace("\n", ""); + try { + icon=ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(s.substring("data:image/png;base64,".length())))); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + /*try { + JsonReader jr = new JsonReader(new StringReader(json)); + jr.setLenient(true); + jr.beginObject(); + while (jr.hasNext()) { + String n0 = jr.nextName(); + switch (n0) { + case "version": + jr.beginObject(); + while (jr.hasNext()) { + String n1 = jr.nextName(); + switch (n1) { + case "name": + version=jr.nextString(); + break; + + default: + jr.skipValue(); + break; + } + } + jr.endObject(); + break; + case "players": + jr.beginObject(); + while (jr.hasNext()) { + String n1 = jr.nextName(); + switch (n1) { + case "max": + max=jr.nextInt(); + break; + case "online": + online=jr.nextInt(); + break; + default: + jr.skipValue(); + break; + } + } + jr.endObject(); + break; + case "description": + JsonToken j=jr.peek(); + + if(j.equals(JsonToken.BEGIN_OBJECT)){ + jr.beginObject(); + while (jr.hasNext()) { + String n1 = jr.nextName(); + switch (n1) { + + case "text": + description=jr.nextString(); + break; + default: + jr.skipValue(); + break; + } + } + jr.endObject(); + }else{ + description=jr.nextString(); + } + break; + case "favicon": + String s=jr.nextString(); + if(s.startsWith("data:image/png;base64,")){ + s=s.replace("\n", ""); + icon=ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(s.substring("data:image/png;base64,".length())))); + } + break; + case "KLALB": + klalbversion=jr.nextString(); + break; + default: + jr.skipValue(); + break; + } + } + jr.endObject(); + jr.close(); + //System.out.println(time); + } catch (IOException e) { + e.printStackTrace(); + }*/ + } + + @Override + public String toString() { + return "MinecraftServerPinger [targetaddr=" + targetaddr + ", latency=" + latency + ", json=" + json + ", icon=" + + icon + ", max=" + max + ", online=" + online + ", description=" + description + ", isOldDescription=" + + isOldDescription + ", version=" + version + ", protocol=" + protocol + ", klalbversion=" + + klalbversion + ", klalbvaddr=" + klalbvaddr+ ", klalbvport=" + klalbvport+ "]"; + } + + public String getKlalbversion() { + return klalbversion; + } + + public Inet6Address getKlalbvaddr() { + return klalbvaddr; + } + + public int getKlalbvport() { + return klalbvport; + } + + public BufferedImage getIcon() { + return icon; + } + + public int getMax() { + return max; + } + + public int getOnline() { + return online; + } + + public String getDescription() { + return description; + } + + public String getVersion() { + return version; + } + + public int getProtocol() { + return protocol; + } + + + public boolean isOldDescription() { + return isOldDescription; + } + + public String getJson() { + return json; + } + + public MultipurposeSocketAddress getTargetaddr() { + return targetaddr; + } + + public long getLatency() { + return latency; + } + + public static void main(String[] args) throws UnknownHostException, IOException {//hypixel.net + MinecraftServerPinger sp=new MinecraftServerPinger(MinecraftServerAddress.findAddress("mc.hypixel.cn")); + System.out.println(sp); + } +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftSocketBridge.java b/src/org/kne/cloud/network/minecraft/MinecraftSocketBridge.java new file mode 100644 index 0000000..ba5a98c --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftSocketBridge.java @@ -0,0 +1,47 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; +import java.net.Socket; + +import org.kne.cloud.network.SocketBridge; +import org.kne.cloud.network.StreamBridge; + +public class MinecraftSocketBridge extends SocketBridge{ + private MinecraftProtocolContext mpca; + private MinecraftProtocolContext mpcb; + public MinecraftSocketBridge(Socket a, Socket b) throws IOException { + super(a, b); + } + + @Override + protected void createStreamBridge() throws IOException { + if(mpca==null) + mpca=new MinecraftProtocolContext(true); + if(mpcb==null) + mpcb=new MinecraftProtocolContext(false); + bridgeAB = new MinecraftStreamBridge(a.getInputStream(),mpca, b.getOutputStream(),mpcb,true); + bridgeBA = new MinecraftStreamBridge(b.getInputStream(),mpcb, a.getOutputStream(),mpca,false); + } + + public MinecraftProtocolContext getMpca() { + return mpca; + } + + public MinecraftProtocolContext getMpcb() { + return mpcb; + } + + @Override + public MinecraftStreamBridge getBridgeAB() { + // TODO 自动生成的方法存根 + return (MinecraftStreamBridge) super.getBridgeAB(); + } + + @Override + public MinecraftStreamBridge getBridgeBA() { + // TODO 自动生成的方法存根 + return (MinecraftStreamBridge) super.getBridgeBA(); + } + + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftSocketToSocketProxy.java b/src/org/kne/cloud/network/minecraft/MinecraftSocketToSocketProxy.java new file mode 100644 index 0000000..0364865 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftSocketToSocketProxy.java @@ -0,0 +1,55 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.kne.cloud.network.HostPortMap; +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.SocketToSocketProxy; + +public class MinecraftSocketToSocketProxy extends SocketToSocketProxy { + private List> transformersAB=new ArrayList>(); + private List> transformersBA=new ArrayList>(); + public List> getTransformersAB() { + return transformersAB; + } + + public List> getTransformersBA() { + return transformersBA; + } + + public MinecraftSocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, + MultipurposeSocketAddress defaultConnect, Map detectedConnect) + throws IOException { + super(listen, cbind, defaultConnect, detectedConnect); + } + + public MinecraftSocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, + MultipurposeSocketAddress defaultConnect) throws IOException { + super(listen, cbind, defaultConnect); + } + + public MinecraftSocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) + throws IOException { + super(listen, connect); + } + + public MinecraftSocketToSocketProxy(String l, String r) throws IOException { + super(l, r); + } + + + + @Override + protected void runBridge(Socket sk, Socket sox) throws IOException {//new KLALBJsonMotdInserter(()->{return kc.getSelf();},()->{return 23333;}) + MinecraftSocketBridge dsb=new MinecraftSocketBridge(sk,sox); + dsb.getBridgeAB().getTransformers().addAll(transformersAB); + dsb.getBridgeBA().getTransformers().addAll(transformersBA); + dsb.run(); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftStreamBridge.java b/src/org/kne/cloud/network/minecraft/MinecraftStreamBridge.java new file mode 100644 index 0000000..300cec9 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftStreamBridge.java @@ -0,0 +1,59 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.kne.cloud.network.StreamBridge; + +public class MinecraftStreamBridge extends StreamBridge { + private List>transformers=new ArrayList>(); + public List> getTransformers() { + return transformers; + } + + @Override + public void runTask() { + MinecraftInputStream min=(MinecraftInputStream) in; + MinecraftOutputStream mout=(MinecraftOutputStream) out; + try { + MinecraftPacket mp=null; + while ((mp=min.readPacket())!=null) { + for (Iterator> iterator = transformers.iterator(); iterator.hasNext();) { + Function function = (Function) iterator + .next(); + mp=function.apply(mp); + } + mout.writePacket(mp); + mout.flush(); + //System.out.println(mp); + } + //super.runTask(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + }finally { + try { + in.close(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } + try { + out.close(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } + } + } + + public MinecraftStreamBridge(InputStream in,MinecraftProtocolContext cin, OutputStream out,MinecraftProtocolContext cout, boolean server) { + super(new MinecraftInputStream( in,cin), new MinecraftOutputStream( out,cout)); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/MinecraftUnsolvedPacket.java b/src/org/kne/cloud/network/minecraft/MinecraftUnsolvedPacket.java new file mode 100644 index 0000000..2d57a07 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/MinecraftUnsolvedPacket.java @@ -0,0 +1,39 @@ +package org.kne.cloud.network.minecraft; + +import java.io.IOException; + +import org.kne.io.VarDataInputStream; +import org.kne.io.VarDataOutputStream; + +public class MinecraftUnsolvedPacket extends MinecraftPacket{ + private byte[]data; + public byte[] getData() { + return data; + } + public int getLenth() { + return length; + } + public MinecraftUnsolvedPacket( ) { + super(false); + this.data = new byte[8192]; + this.length = data.length; + + } + public MinecraftUnsolvedPacket( byte[] data, int length) { + super(false); + this.data = data; + this.length = length; + } + private int length; + @Override + protected void writeToStream(VarDataOutputStream dto) throws IOException { + super.writeToStream(dto); + dto.write(data, 0, length); + } + @Override + protected void readFromStream(VarDataInputStream din) throws IOException { + super.readFromStream(din); + length=din.read(data); + } + +} diff --git a/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftClient.java b/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftClient.java new file mode 100644 index 0000000..8d06415 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftClient.java @@ -0,0 +1,67 @@ +package org.kne.cloud.network.minecraft; + +import java.io.File; +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.Scanner; + +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.SocketBridge; +import org.kne.cloud.network.SocketListener; +import org.kne.cloud.network.SocketToSocketProxy; +import org.kne.cloud.network.klalb.CONST; +import org.kne.cloud.network.klalb.KLALBController; +import org.kne.cloud.network.klalb.KLALBRemoteLine; +import org.kne.util.AutoProperties; + +public class SimpleKLALBMinecraftClient { +public static void main(String[] args) throws UnknownHostException, IOException { + + System.out.println(CONST.klalb+" V"+CONST.klalbver); + Scanner scn=new Scanner(System.in); + Properties def=new Properties(); + def.setProperty("server", ""); + def.setProperty("local", ""); + AutoProperties ap=new AutoProperties(new File("klalbclient.ini"),def); + KLALBController kc=new KLALBController(); + kc.registerToProxyTypeAs("KLALB"); + MinecraftServerAddress msa=MinecraftServerAddress.findAddress(ap.getProperty("server")); + MinecraftServerPinger msp=new MinecraftServerPinger(msa); + if(msp.getKlalbversion()==null) { + System.out.println("服务器不支持加速!!"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.exit(0); + } + Inet6Address vad= kc.getRemoteVaddrBySocketAddress(msa); + MultipurposeSocketAddress vmsa=new MultipurposeSocketAddress("KLALB_Stream",new InetSocketAddress(vad, msp.getKlalbvport())); + System.out.println("连接成功:"+ap.getProperty("server")); + new SocketToSocketProxy(new MultipurposeSocketAddress(ap.getProperty("local")), vmsa); + System.out.println("提示:输入state并回车可以查看当前线路状态"); + while(true) { + String s=scn.nextLine(); + switch(s) { + case "state": + System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t延迟\t抖动\t下一次重试"); + for (Iterator iterator = kc.getLines().iterator(); iterator.hasNext();) { + KLALBRemoteLine hostPort = iterator.next(); + System.out.println(hostPort .toString2()); + //System.out.println(); + } + break; + default : + System.out.println("未知命令!"); + + } + } +} +} diff --git a/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftServer.java b/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftServer.java new file mode 100644 index 0000000..4ea5099 --- /dev/null +++ b/src/org/kne/cloud/network/minecraft/SimpleKLALBMinecraftServer.java @@ -0,0 +1,194 @@ +package org.kne.cloud.network.minecraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.Scanner; + +import org.kne.cloud.network.MultipurposeSocketAddress; +import org.kne.cloud.network.ProtocolDetectorServerSocketFactory; +import org.kne.cloud.network.ProtocolDetectorSocket; +import org.kne.cloud.network.SocketListener; +import org.kne.cloud.network.SocketType; +import org.kne.cloud.network.klalb.CONST; +import org.kne.cloud.network.klalb.KLALBController; +import org.kne.cloud.network.klalb.KLALBRemoteLine; +import org.kne.cloud.network.klalb.KLALBVirtualSocket; +import org.kne.cloud.network.klalb.ServerPropties; +import org.kne.cloud.network.klalb.StreamKLALBPacketLink; + +public class SimpleKLALBMinecraftServer { + public static SocketListener tcpl,tcpl2; + public static ServerPropties sp; + public static KLALBController kc; + static { + MultipurposeSocketAddress.getSocketTypeRegister().put("DETTCP",new SocketType(null, new ProtocolDetectorServerSocketFactory())); + } + public static void main(String[] args) throws IOException { + + System.out.println(CONST.klalb+"[Minecraft版] V"+CONST.klalbver); + sp=new ServerPropties(); + + System.out.println("虚拟地址:"+sp.getVirtualIP().getHostAddress()); + kc=new KLALBController(sp.getVirtualIP()); + kc.setSelflineTableSupplier(()->{ + return fileRead("linetable.txt"); + }); + kc.registerToProxyTypeAs("KLALB"); + + System.out.println("开放端口:"+sp.getBind()); + openPort(sp.getBind()); + System.out.println("本地服务:"+sp.getLocal()); + openLocalPort(sp.getLocal()); + + + + Scanner scn=new Scanner(System.in); + while(true) { + String s=scn.next(); + String[]sc=s.split(" "); + switch(sc[0]) { + case "help": + System.out.print("state:查看线路状态"); + System.out.println("reload:重新加载线路配置"); + break; + case "state": + System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t延迟\t抖动\t下一次重试"); + for (Iterator iterator = kc.getLines().iterator(); iterator.hasNext();) { + KLALBRemoteLine hostPort = iterator.next(); + System.out.println(hostPort .toString2()); + //System.out.println(); + } + break; + default: + System.out.println("未知命令,请输入help以查询指令说明"); + } + } + } + private static void openPort(String bip) throws IOException { + if(tcpl!=null) + tcpl.close(); + MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(bip,"DETTCP"); + tcpl=new SocketListener(mpsa); + tcpl.setCon((soc)->{ + ProtocolDetectorSocket pds=(ProtocolDetectorSocket) soc; + if(!pds.getProtocolStack().isEmpty()&&pds.getProtocolStack().pop().getName().equals("KLALB")) { + KLALBRemoteLine krs=null; + try { + krs = new KLALBRemoteLine(new StreamKLALBPacketLink(pds)); + kc.addRemoteLine(krs); + } catch (IOException e) { + e.printStackTrace(); + } + }else { + MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(sp.getLocal()); + Socket s=null; + try { + s=mpsa2.connectSocket(); + MinecraftSocketBridge dsb=new MinecraftSocketBridge(pds, s); + dsb.getBridgeBA().getTransformers().add(new KLALBJsonMotdInserter(()->{return kc.getSelf();},()->{return 23333;})); + dsb.run(); + } catch (UnknownHostException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + }finally { + if(s!=null) + try { + s.close(); + } catch (IOException e1) { + // TODO 自动生成的 catch 块 + e1.printStackTrace(); + } + try { + soc.close(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } + } + } + }); + } + private static void openLocalPort(String bip) throws IOException { + if(tcpl2!=null) + tcpl2.close(); + MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(bip); + tcpl2=new SocketListener(new MultipurposeSocketAddress("KLALB_Stream", "::0", 23333)); + tcpl2.setCon((soc)->{ + Socket s=null; + try { + s=mpsa.connectSocket(); + s.setTcpNoDelay(true); + //soc.setTcpNoDelay(true); + //((KLALBVirtualSocket) soc).setCompress(1); + MinecraftSocketBridge dsb=new MinecraftSocketBridge(soc, s); + dsb.getBridgeBA().getTransformers().add(new KLALBJsonMotdInserter(()->{return kc.getSelf();},()->{return 23333;})); + //dsb.getSab().setDelay(1); + dsb.run(); + /*CompressedSocketBridge csb=new CompressedSocketBridge(s, soc); + csb.getSab().setDelay(1); + csb.run();*/ + } catch (UnknownHostException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + }finally { + if(s!=null) + try { + s.close(); + } catch (IOException e1) { + // TODO 自动生成的 catch 块 + e1.printStackTrace(); + } + try { + soc.close(); + } catch (IOException e) { + // TODO 自动生成的 catch 块 + e.printStackTrace(); + } + } + + }); + } + private static String fileRead(String filePath){ + //1.定义一个BufferedReader对象,将文件内容读取到缓存 + BufferedReader bufferedReader =null; + String returnInfo=""; + try{ + // 2.定义一个file对象 + File file = new File(filePath);//定义一个file对象,用来初始化FileReader + // 3.定义一个fileReader对象 + FileReader reader = new FileReader(file); + // 4.定义一个BufferedReader对象,将文件内容读取到缓存 + bufferedReader = new BufferedReader(reader); + // 5.定义一个字符串缓存,将字符串存放缓存中 + StringBuilder stringBuilder = new StringBuilder(); + String str = ""; + while ((str =bufferedReader.readLine()) != null) {//逐行读取文件内容,不读取换行符和末尾的空格 + stringBuilder.append(str + "\n");//将读取的字符串添加换行符后累加存放在缓存中 + } + returnInfo = stringBuilder.toString(); + }catch (Exception e){ + e.printStackTrace(); + }finally { + try{ + if(bufferedReader!=null){ + bufferedReader.close(); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + return returnInfo; + } + +} diff --git a/src/org/kne/cloud/network/nathole/ICMPNatholeTest.java b/src/org/kne/cloud/network/nathole/ICMPNatholeTest.java new file mode 100644 index 0000000..5a1b7f5 --- /dev/null +++ b/src/org/kne/cloud/network/nathole/ICMPNatholeTest.java @@ -0,0 +1,27 @@ +package org.kne.cloud.network.nathole; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; + +public class ICMPNatholeTest { + public static void main(String[] args) throws IllegalStateException, IOException { + + } + public static String toString(byte[] a) { + if (a == null) + return "null"; + int iMax = a.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append( Integer.toString(a[i]&0xff,16)); + if (i == iMax) + return b.append(']').toString(); + b.append(", "); + } + } +} diff --git a/src/org/kne/cloud/network/nathole/UDPNatholeTest.java b/src/org/kne/cloud/network/nathole/UDPNatholeTest.java new file mode 100644 index 0000000..69d95ef --- /dev/null +++ b/src/org/kne/cloud/network/nathole/UDPNatholeTest.java @@ -0,0 +1,42 @@ +package org.kne.cloud.network.nathole; + +import java.io.IOException; +import java.net.BindException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketOption; +import java.net.SocketOptions; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class UDPNatholeTest { + public static ExecutorService exc=Executors.newCachedThreadPool(); + public static void main(String[] args) throws IOException { + InetAddress addr=InetAddress.getByName("10.235.16.1"); + for (int i1 = 1024; i1 < 65536; i1++) { + MulticastSocket ms=new MulticastSocket(i1); + //ms.setReuseAddress(true); + ms.setSendBufferSize(65536); + DatagramPacket fp=new DatagramPacket(new byte[1], 1, addr, 13000); + ms.send(fp); + new Thread(()->{ + DatagramPacket rp=new DatagramPacket(new byte[10], 10); + try { + ms.receive(rp); + System.err.println(rp.getAddress()); + System.err.println(rp.getLength()); + System.exit(0); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + System.out.println(i1); + } + } +} diff --git a/src/org/kne/debug/Debuger.java b/src/org/kne/debug/Debuger.java new file mode 100644 index 0000000..25e7668 --- /dev/null +++ b/src/org/kne/debug/Debuger.java @@ -0,0 +1,129 @@ +package org.kne.debug; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +import java.util.Set; +final class Int implements Comparable,Cloneable { + public int value; + + public Int(int value) { + super(); + this.value = value; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public Object clone() throws CloneNotSupportedException { + // TODO �Զ����ɵķ������ + return super.clone(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Int other = (Int) obj; + if (value != other.value) + return false; + return true; + } + + @Override + public String toString() { + return new Integer(value).toString(); + } + + @Override + public int compareTo(Int o) { + + return o.value-value; + } + + + +} + +public class Debuger extends Thread { + Map ,Object[]>md=new HashMap,Object[]>(); + @Override + public void run() { + while(true){ + for(int iz=0;iz<100;iz++){ + Map m=Thread.getAllStackTraces(); + Set> s=m.entrySet(); + Iterator> i=s.iterator(); + while(i.hasNext()){ + Entryelements =i.next(); + StackTraceElement[]value=elements.getValue(); + List l=new ArrayList(value.length); + for (int j = 0; j < value.length; j++) { + l.add(value[j]); + } + if(!l.isEmpty()) + if(md.containsKey(l)){ + ((Int)md.get(l)[0]).value++; + }else{ + md.put(l,new Object[]{new Int(1),elements.getKey()} ); + } + } + + } + ArrayList, Object[]>> tmp=new ArrayList(); + Set, Object[]>> sn=md.entrySet(); + Iterator, Object[]>> in=sn.iterator(); + while(in.hasNext()){ + Entry, Object[]>elements =in.next(); + + tmp.add(elements); + } + tmp.sort(new Comparator, Object[]>>() { + + @Override + public int compare(Entry, Object[]> o1, + Entry, Object[]> o2) { + + return ((Int)o2.getValue()[0]).value-((Int)o1.getValue()[0]).value; + } + }); + for(int d=0;d, Object[]>elements=tmp.get(d); + if(((Thread)(elements.getValue()[1])).getName().startsWith("SocketBridge A->B")){ + + }else{ + continue; + } + List k=elements.getKey(); + StringBuilder sb=new StringBuilder(); + for(int d2=0;d2 5) { + throw new RuntimeException("VarInt too big"); + } + + if ((b0 & 128) != 128) { + break; + } + } + + return i; + } + public long readVarLong() throws IOException { + long i = 0L; + int j = 0; + + while (true) { + byte b0 = this.readByte(); + i |= (long) (b0 & 127) << j++ * 7; + + if (j > 10) { + throw new RuntimeException("VarLong too big"); + } + + if ((b0 & 128) != 128) { + break; + } + } + + return i; + } +} diff --git a/src/org/kne/io/VarDataOutputStream.java b/src/org/kne/io/VarDataOutputStream.java new file mode 100644 index 0000000..f6512b6 --- /dev/null +++ b/src/org/kne/io/VarDataOutputStream.java @@ -0,0 +1,43 @@ +package org.kne.io; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +public class VarDataOutputStream extends DataOutputStream { + + public VarDataOutputStream(OutputStream out) { + super(out); + } + int cache=0; + public void writeVarInt_add(int input)throws IOException{ + writeVarInt (input-cache); + cache=input; + } + public void writeVarBytes(byte[]b) throws IOException { + writeVarInt(b.length); + write(b); + } + public void writeVarUTF8(String str) throws IOException { + writeVarBytes(str.getBytes(Charset.forName("UTF-8"))); + } + public void writeVarInt(int input) throws IOException { + while ((input & -128) != 0) { + this.out.write(input & 127 | 128); + input >>>= 7; + } + + this.out.write(input); + } + + + public void writeVarLong(long value) throws IOException { + while ((value & -128L) != 0L) { + this.writeByte((int) (value & 127L) | 128); + value >>>= 7; + } + + this.writeByte((int) value); + } +} diff --git a/src/org/kne/ui/KTabUI.java b/src/org/kne/ui/KTabUI.java deleted file mode 100644 index 9f4eb00..0000000 --- a/src/org/kne/ui/KTabUI.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.kne.ui; - -import java.awt.Graphics; -import java.awt.Rectangle; - -import javax.swing.JComponent; -import javax.swing.plaf.metal.MetalTabbedPaneUI; - -public class KTabUI extends MetalTabbedPaneUI{ - - @Override - public void paint(Graphics g, JComponent c) { - - } - - @Override - protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { - - } - - @Override - protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, - boolean isSelected) { - } - - - - - - - - - -} diff --git a/src/org/kne/ui/XFrame.java b/src/org/kne/ui/XFrame.java deleted file mode 100644 index b2d5577..0000000 --- a/src/org/kne/ui/XFrame.java +++ /dev/null @@ -1,573 +0,0 @@ -package org.kne.ui; - -import java.awt.AWTEvent; -import java.awt.Color; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Point; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; - -import javax.swing.JFrame; -import javax.swing.SpringLayout; -import javax.swing.JPanel; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JLabel; -import java.awt.Font; -import java.awt.Image; - -import javax.swing.SwingConstants; -import java.awt.BorderLayout; - -public class XFrame extends JFrame { - MouseListener lsr=new MouseListener() { - - @Override - public void mouseReleased(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mousePressed(MouseEvent e) { - if(e.getButton()==MouseEvent.BUTTON1) { - delta.x = e.getXOnScreen()-XFrame.this.getX(); - delta.y = e.getYOnScreen()-XFrame.this.getY(); - delta0.x = e.getXOnScreen()-(XFrame.this.getX()+XFrame.this.getWidth()); - delta0.y = e.getYOnScreen()-(XFrame.this.getY()+XFrame.this.getHeight()); - total.x=XFrame.this.getWidth()+XFrame.this.getX(); - total.y=XFrame.this.getHeight()+XFrame.this.getY(); - xb=e.getY()<=8; - } - - } - - @Override - public void mouseExited(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseEntered(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseClicked(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - }; - - @Override - public Container getContentPane() { - return content; - } - - private static final Color invisible=new Color(255,255,255, 1); - private JPanel titlepanel; - private Point delta = new Point(); - private Point delta0 = new Point(); - private Point total = new Point(); - private boolean xb; - private int state=0; - - private JPanel content; - private JLabel titlelabel; - private JLabel iconi; - - @Override - public void setIconImage(Image image) { - iconi.setIcon(new ImageIcon(image.getScaledInstance(27,27, Image.SCALE_SMOOTH))); - super.setIconImage(image); - repaint(); - } - public JPanel getTitlepanel() { - return titlepanel; - } - public JLabel getTitlelabel() { - return titlelabel; - } - public void setTitleColor(Color c) { - titlepanel.setBackground(c); - repaint(); - } - public Color getTitleColor() { - return titlepanel.getBackground(); - } - - @Override - public void setTitle(String title) { - titlelabel.setText(title); - super.setTitle(title); - repaint(); - } - @Override - public String getTitle() { - return super.getTitle(); - } - - public XFrame() { - setUndecorated(true); - - super.setBackground(new Color(255, 255, 255, 0)); - Container contentPane = super.getContentPane(); - contentPane.setBackground(new Color(255, 255, 255, 0)); - SpringLayout springLayout = new SpringLayout(); - contentPane.setLayout(springLayout); - - titlepanel = new JPanel(); - //titlepanel.setOpaque(false); - titlepanel.setBorder(null); - springLayout.putConstraint(SpringLayout.WEST, titlepanel, 8, SpringLayout.WEST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, titlepanel, 31, SpringLayout.NORTH, contentPane); - springLayout.putConstraint(SpringLayout.EAST, titlepanel, -8, SpringLayout.EAST, contentPane); - springLayout.putConstraint(SpringLayout.NORTH, titlepanel, 0, SpringLayout.NORTH, contentPane); - contentPane.add(titlepanel); - titlepanel.addMouseListener(lsr); - titlepanel.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - if(e.getY()<=8) { - titlepanel.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); - }else { - titlepanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - - @Override - public void mouseDragged(MouseEvent e) { - - Point p=e.getLocationOnScreen(); - if(xb) { - if (!isResizable()) - return; - XFrame.this.setLocation(XFrame.this.getX(),p.y-delta.y); - XFrame.this.setSize(XFrame.this.getWidth(), total.y-(p.y-delta.y)); - }else { - XFrame.this.setLocation(p.x-delta.x,p.y-delta.y); - } - } - }); - - JPanel lu = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, lu, 0, SpringLayout.NORTH, contentPane); - springLayout.putConstraint(SpringLayout.WEST, lu, 0, SpringLayout.WEST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, lu, 8, SpringLayout.NORTH, contentPane); - springLayout.putConstraint(SpringLayout.EAST, lu, 8, SpringLayout.WEST, contentPane); - lu.setBackground(invisible); - lu.addMouseListener(lsr); - lu.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); - lu.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setLocation(p.x-delta.x,p.y-delta.y); - XFrame.this.setSize( total.x-(p.x-delta.x),total.y-(p.y-delta.y)); - - } - }); - contentPane.add(lu); - - JPanel ru = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, ru, 0, SpringLayout.NORTH, contentPane); - springLayout.putConstraint(SpringLayout.WEST, ru, -8, SpringLayout.EAST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, ru, 8, SpringLayout.NORTH,contentPane); - springLayout.putConstraint(SpringLayout.EAST, ru, 0, SpringLayout.EAST, contentPane); - ru.setBackground(invisible); - ru.addMouseListener(lsr); - ru.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)); - ru.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setLocation(XFrame.this.getX(),p.y-delta.y); - XFrame.this.setSize(p.x+delta0.x-XFrame.this.getX(), total.y-(p.y-delta.y)); - - } - }); - contentPane.add(ru); - - JPanel ld = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, ld, -8, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.WEST, ld, 0, SpringLayout.WEST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, ld, 0, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.EAST, ld, 8, SpringLayout.WEST, contentPane); - ld.setBackground(invisible); - ld.addMouseListener(lsr); - ld.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)); - ld.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setLocation(p.x-delta.x,XFrame.this.getY()); - XFrame.this.setSize( total.x-(p.x-delta.x),p.y+delta0.y-XFrame.this.getY()); - } - }); - contentPane.add(ld); - - JPanel rd = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, rd, -8, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.WEST, rd, -8, SpringLayout.EAST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, rd, 0, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.EAST, rd, 0, SpringLayout.EAST, contentPane); - rd.setBackground(invisible); - rd.addMouseListener(lsr); - rd.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); - rd.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setSize(p.x+delta0.x-XFrame.this.getX(), p.y+delta0.y-XFrame.this.getY()); - - } - }); - contentPane.add(rd); - - JPanel l = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, l, 0, SpringLayout.SOUTH, lu); - springLayout.putConstraint(SpringLayout.WEST, l, 0, SpringLayout.WEST, contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, l, 0, SpringLayout.NORTH, ld); - springLayout.putConstraint(SpringLayout.EAST, l, 8, SpringLayout.WEST, contentPane); - l.setBackground(invisible); - l.addMouseListener(lsr); - l.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); - l.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setLocation(p.x-delta.x,XFrame.this.getY()); - XFrame.this.setSize( total.x-(p.x-delta.x),XFrame.this.getHeight()); - - } - }); - contentPane.add(l); - - JPanel r = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, r, 0, SpringLayout.SOUTH, ru); - springLayout.putConstraint(SpringLayout.WEST, r, -8, SpringLayout.EAST,contentPane); - springLayout.putConstraint(SpringLayout.SOUTH, r, 0, SpringLayout.NORTH, rd); - springLayout.putConstraint(SpringLayout.EAST, r, 0, SpringLayout.EAST, contentPane); - r.setBackground(invisible); - r.addMouseListener(lsr); - r.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); - r.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setSize(p.x+delta0.x-XFrame.this.getX(), XFrame.this.getHeight()); - - } - }); - - contentPane.add(r); - - JPanel d = new JPanel(); - springLayout.putConstraint(SpringLayout.NORTH, d, -8, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.WEST, d, 0, SpringLayout.EAST, ld); - springLayout.putConstraint(SpringLayout.SOUTH, d, 0, SpringLayout.SOUTH, contentPane); - springLayout.putConstraint(SpringLayout.EAST, d, 0, SpringLayout.WEST, rd); - d.setBackground(invisible); - d.addMouseListener(lsr); - d.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); - d.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseMoved(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseDragged(MouseEvent e) { - if (!isResizable()) - return; - Point p=e.getLocationOnScreen(); - XFrame.this.setSize(XFrame.this.getWidth(), p.y+delta0.y-XFrame.this.getY()); - - } - }); - contentPane.add(d); - - content = new JPanel(); - content.setBorder(null); - springLayout.putConstraint(SpringLayout.NORTH, content, 0, SpringLayout.SOUTH, titlepanel); - SpringLayout sl_titlepanel = new SpringLayout(); - titlepanel.setLayout(sl_titlepanel); - - JLabel close = new JLabel(""); - close.setHorizontalAlignment(SwingConstants.CENTER); - close.setBackground(invisible); - close.setForeground(Color.WHITE); - close.setIcon(new ImageIcon(XFrame.class.getResource("/assets/close.png"))); - sl_titlepanel.putConstraint(SpringLayout.NORTH, close, 0, SpringLayout.NORTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.WEST, close, -46, SpringLayout.EAST, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.SOUTH, close, 0, SpringLayout.SOUTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.EAST, close, 0, SpringLayout.EAST, titlepanel); - close.addMouseListener(new MouseListener() { - - @Override - public void mouseClicked(MouseEvent e) { - XFrame.super.processWindowEvent(new WindowEvent(XFrame.this, WindowEvent.WINDOW_CLOSING)); - - } - - @Override - public void mousePressed(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseReleased(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseEntered(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseExited(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - }); - titlepanel.add(close); - iconi = new JLabel(""); - iconi.setHorizontalAlignment(SwingConstants.CENTER); - sl_titlepanel.putConstraint(SpringLayout.NORTH, iconi, 0, SpringLayout.NORTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.WEST, iconi, 0, SpringLayout.WEST, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.SOUTH, iconi, 0, SpringLayout.SOUTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.EAST, iconi, 31, SpringLayout.WEST, titlepanel); - iconi.setOpaque(false); - iconi.setBackground(invisible); - titlepanel.add(iconi); - JLabel maxium = new JLabel(""); - maxium.setHorizontalAlignment(SwingConstants.CENTER); - maxium.setForeground(Color.WHITE); - maxium.setBackground(Color.WHITE); - maxium.setIcon(new ImageIcon(XFrame.class.getResource("/assets/maxium.png"))); - sl_titlepanel.putConstraint(SpringLayout.NORTH, maxium, 0, SpringLayout.NORTH, close); - sl_titlepanel.putConstraint(SpringLayout.WEST, maxium, -46, SpringLayout.WEST, close); - sl_titlepanel.putConstraint(SpringLayout.SOUTH, maxium, 0, SpringLayout.SOUTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.EAST, maxium, 0, SpringLayout.WEST, close); - //maxium.setBorderPainted(false); - maxium.setBackground(invisible); - maxium.addMouseListener(new MouseListener() { - - @Override - public void mouseClicked(MouseEvent e) { - if (!isResizable()) - return; - int s=XFrame.this.getExtendedState(); - if(s==JFrame.NORMAL) - XFrame.this.setExtendedState(JFrame.MAXIMIZED_BOTH); - if(s==JFrame.MAXIMIZED_BOTH) - XFrame.this.setExtendedState(JFrame.NORMAL); - } - - @Override - public void mousePressed(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseReleased(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseEntered(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseExited(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - }); - titlepanel.add(maxium); - - JLabel minium = new JLabel(""); - minium.setHorizontalAlignment(SwingConstants.CENTER); - minium.setForeground(Color.WHITE); - minium.setBackground(Color.WHITE); - minium.setIcon(new ImageIcon(XFrame.class.getResource("/assets/minium.png"))); - sl_titlepanel.putConstraint(SpringLayout.NORTH, minium, 0, SpringLayout.NORTH, close); - sl_titlepanel.putConstraint(SpringLayout.WEST, minium, -46, SpringLayout.WEST, maxium); - sl_titlepanel.putConstraint(SpringLayout.SOUTH, minium, 0, SpringLayout.SOUTH, close); - sl_titlepanel.putConstraint(SpringLayout.EAST, minium, 0, SpringLayout.WEST, maxium); - minium.setBackground(invisible); - minium.addMouseListener(new MouseListener() { - - @Override - public void mouseReleased(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mousePressed(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseExited(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseEntered(MouseEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void mouseClicked(MouseEvent e) { - // TODO 自动生成的方法存根 - int s=XFrame.this.getExtendedState(); - if(s!=JFrame.ICONIFIED) { - state=s; - XFrame.this.setExtendedState(JFrame.ICONIFIED); - } else { - XFrame.this.setExtendedState(state); - } - } - }); - titlepanel.add(minium); - - titlelabel = new JLabel(); - sl_titlepanel.putConstraint(SpringLayout.WEST, titlelabel, 31, SpringLayout.WEST, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.EAST, titlelabel, -31, SpringLayout.EAST, titlepanel); - titlelabel.setFont(new Font("宋体", Font.PLAIN, 16)); - sl_titlepanel.putConstraint(SpringLayout.NORTH, titlelabel, 0, SpringLayout.NORTH, titlepanel); - sl_titlepanel.putConstraint(SpringLayout.SOUTH, titlelabel, 0, SpringLayout.SOUTH, titlepanel); - titlelabel.setOpaque(false); - titlelabel.setBackground(invisible); - titlepanel.add(titlelabel); - springLayout.putConstraint(SpringLayout.WEST, content, 0, SpringLayout.EAST, l); - springLayout.putConstraint(SpringLayout.SOUTH, content, 0, SpringLayout.NORTH, d); - springLayout.putConstraint(SpringLayout.EAST, content, 0, SpringLayout.WEST, r); - contentPane.add(content); - content.setLayout(new BorderLayout(0, 0)); - addWindowListener(new WindowListener() { - - @Override - public void windowOpened(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void windowIconified(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void windowDeiconified(WindowEvent e) { - XFrame.this.setExtendedState(state); - - } - - @Override - public void windowDeactivated(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void windowClosing(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void windowClosed(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - - @Override - public void windowActivated(WindowEvent e) { - // TODO 自动生成的方法存根 - - } - }); - - } -} diff --git a/src/org/kne/ui/YScrollPane.java b/src/org/kne/ui/YScrollPane.java deleted file mode 100644 index a325511..0000000 --- a/src/org/kne/ui/YScrollPane.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.kne.ui; - -import javax.swing.JPanel; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.AdjustmentEvent; -import java.awt.event.AdjustmentListener; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.ContainerEvent; -import java.awt.event.ContainerListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; - -import javax.swing.JScrollBar; - -public class YScrollPane extends JPanel { - private JPanel settings; - private JScrollBar scrollBar; - public YScrollPane(int contentx){ - this(contentx,BorderLayout.EAST); - } - public YScrollPane(int contentx, String d) { - setLayout(new BorderLayout(0, 0)); - - setBackground(Color.WHITE); - scrollBar = new JScrollBar(); - add(scrollBar,d); - - JPanel panel = new JPanel(); - add(panel, BorderLayout.CENTER); - panel.setLayout(null); - panel.setOpaque(false); - - settings = new JPanel(); - settings.setOpaque(false); - settings.setMaximumSize(new Dimension(contentx, Integer.MAX_VALUE-10)); - settings.setPreferredSize(new Dimension(contentx, 0)); - settings.setSize(new Dimension(contentx, 0)); - FlowLayout flowLayout = (FlowLayout) settings.getLayout(); - flowLayout.setAlignment(FlowLayout.LEFT); - panel.add(settings); -settings.addContainerListener(new ContainerListener() { - - @Override - public void componentRemoved(ContainerEvent e) { - int maxv=0; - for (int i = 0; i - * Warning: - * Serialized objects of this class will not be compatible with - * future Swing releases. The current serialization support is - * appropriate for short term storage or RMI between applications running - * the same version of Swing. As of 1.4, support for long term storage - * of all JavaBeans™ - * has been added to the java.beans package. - * Please see {@link java.beans.XMLEncoder}. - * - * @author Michael C. Albers - */ -public class KNEProgressBarUI extends BasicProgressBarUI { - - // private Rectangle innards; - // private Rectangle box; - - public static ComponentUI createUI(JComponent c) { - return new KNEProgressBarUI(); - } - - /** - * Draws a bit of special highlighting on the progress bar. - * The core painting is deferred to the BasicProgressBar's - * paintDeterminate method. - * @since 1.4 - */ - public void paintDeterminate(Graphics g, JComponent c) { - super.paintDeterminate(g,c); - - - } - - /** - * Draws a bit of special highlighting on the progress bar - * and bouncing box. - * The core painting is deferred to the BasicProgressBar's - * paintIndeterminate method. - * @since 1.4 - */ - public void paintIndeterminate(Graphics g, JComponent c) { - super.paintIndeterminate(g, c); - } -} diff --git a/~$ALB协议规范V2.1.docx b/~$ALB协议规范V2.1.docx new file mode 100644 index 0000000000000000000000000000000000000000..fcd42e3fa9819ae3c2e99e74ae57ff52b9d59275 GIT binary patch literal 162 xcmd;eOv%m6%PcM_N-W7QVjvRmGB`4%Fyu01GUNeqF+&MM5kn%7%m>oc1^`8%3f%wz literal 0 HcmV?d00001