From b53f66b6acf609299f45b2ff3511f79c6ca57405 Mon Sep 17 00:00:00 2001 From: md_5 Date: Fri, 5 Oct 2012 14:12:01 +1000 Subject: [PATCH] Add server connection / proxying. --- src/main/java/net/md_5/bungee/BungeeCord.java | 1 - .../java/net/md_5/bungee/Configuration.java | 16 +-- .../net/md_5/bungee/GenericConnection.java | 18 +++- .../java/net/md_5/bungee/InitialHandler.java | 13 +-- .../java/net/md_5/bungee/KickException.java | 8 ++ .../net/md_5/bungee/ServerConnection.java | 63 +++++++---- .../java/net/md_5/bungee/UserConnection.java | 101 +++++++++++++++++- src/main/java/net/md_5/bungee/Util.java | 3 - .../md_5/bungee/packet/PacketInputStream.java | 4 + 9 files changed, 181 insertions(+), 46 deletions(-) create mode 100644 src/main/java/net/md_5/bungee/KickException.java diff --git a/src/main/java/net/md_5/bungee/BungeeCord.java b/src/main/java/net/md_5/bungee/BungeeCord.java index d665a559..c33ddbea 100644 --- a/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/src/main/java/net/md_5/bungee/BungeeCord.java @@ -61,7 +61,6 @@ public class BungeeCord { } public static void main(String[] args) throws IOException { - System.out.println(Util.hex(15)); instance = new BungeeCord(); $().info("Enabled BungeeCord version " + instance.version); instance.start(); diff --git a/src/main/java/net/md_5/bungee/Configuration.java b/src/main/java/net/md_5/bungee/Configuration.java index 3f949b49..e9db38f4 100644 --- a/src/main/java/net/md_5/bungee/Configuration.java +++ b/src/main/java/net/md_5/bungee/Configuration.java @@ -172,13 +172,12 @@ public class Configuration { } } - public InetSocketAddress getHostFor(String user, InetSocketAddress requestedHost) { - String entry = user + ";" + Util.getAddr(requestedHost); - String host = requestedHost.getHostString(); + public InetSocketAddress getHostFor(String user, String requestedHost) { + String entry = user + ";" + requestedHost; String hostLine; - if (forcedServers.containsKey(host)) { - hostLine = servers.get(forcedServers.get(host)); + if (forcedServers.containsKey(requestedHost)) { + hostLine = servers.get(forcedServers.get(requestedHost)); } else { hostLine = reconnectLocations.get(entry); } @@ -188,8 +187,13 @@ public class Configuration { return Util.getAddr(hostLine); } + public void setHostFor(UserConnection user, String host) { + String entry = user.username + ";" + Util.getAddr(user.handshake.host); + reconnectLocations.put(entry, host); + } + public InetSocketAddress getServer(String name) { - String hostline = servers.get(name); + String hostline = (name == null) ? defaultServerName : name; if (hostline != null) { return Util.getAddr(hostline); } else { diff --git a/src/main/java/net/md_5/bungee/GenericConnection.java b/src/main/java/net/md_5/bungee/GenericConnection.java index d9be636d..436df98b 100644 --- a/src/main/java/net/md_5/bungee/GenericConnection.java +++ b/src/main/java/net/md_5/bungee/GenericConnection.java @@ -5,6 +5,7 @@ import java.io.OutputStream; import java.net.Socket; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; +import static net.md_5.bungee.Logger.$; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketInputStream; @@ -12,13 +13,18 @@ import net.md_5.bungee.packet.PacketInputStream; @RequiredArgsConstructor public class GenericConnection { - private final Socket socket; - private final PacketInputStream in; - private final OutputStream out; + protected final Socket socket; + protected final PacketInputStream in; + protected final OutputStream out; + public String username; public void disconnect(String reason) { + if (socket.isClosed()) { + return; + } + log("disconnected with " + reason); try { - out.write(new PacketFFKick(reason).getPacket()); + out.write(new PacketFFKick("[Proxy] " + reason).getPacket()); } catch (IOException ex) { } finally { try { @@ -28,4 +34,8 @@ public class GenericConnection { } } } + + public void log(String message) { + $().info(socket.getInetAddress() + ((username == null) ? " " : " [" + username + "] ") + message); + } } diff --git a/src/main/java/net/md_5/bungee/InitialHandler.java b/src/main/java/net/md_5/bungee/InitialHandler.java index 4e073c00..16856334 100644 --- a/src/main/java/net/md_5/bungee/InitialHandler.java +++ b/src/main/java/net/md_5/bungee/InitialHandler.java @@ -44,13 +44,16 @@ public class InitialHandler implements Runnable { out.write(new PacketFCEncryptionResponse().getPacket()); in = new PacketInputStream(new CipherInputStream(socket.getInputStream(), EncryptionUtil.getCipher(false, shared))); - out = new BufferedOutputStream(new CipherOutputStream(socket.getOutputStream(), EncryptionUtil.getCipher(true, shared)), 5120); + out = new CipherOutputStream(socket.getOutputStream(), EncryptionUtil.getCipher(true, shared)); int ciphId = Util.getId(in.readPacket()); if (ciphId != 0xCD) { throw new KickException("Unable to receive encrypted client status"); } + UserConnection userCon = new UserConnection(socket, in, out, handshake); + userCon.register(); + userCon.connect(BungeeCord.instance.config.getHostFor(handshake.username, handshake.host)); break; case 0xFE: throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers); @@ -70,16 +73,10 @@ public class InitialHandler implements Runnable { } catch (IOException ioe) { } finally { try { + out.flush(); socket.close(); } catch (IOException ioe2) { } } } - - private class KickException extends RuntimeException { - - public KickException(String message) { - super(message); - } - } } diff --git a/src/main/java/net/md_5/bungee/KickException.java b/src/main/java/net/md_5/bungee/KickException.java new file mode 100644 index 00000000..2ca59711 --- /dev/null +++ b/src/main/java/net/md_5/bungee/KickException.java @@ -0,0 +1,8 @@ +package net.md_5.bungee; + +public class KickException extends RuntimeException { + + public KickException(String message) { + super(message); + } +} diff --git a/src/main/java/net/md_5/bungee/ServerConnection.java b/src/main/java/net/md_5/bungee/ServerConnection.java index 814def7c..c9cefe62 100644 --- a/src/main/java/net/md_5/bungee/ServerConnection.java +++ b/src/main/java/net/md_5/bungee/ServerConnection.java @@ -10,46 +10,63 @@ import net.md_5.bungee.packet.Packet2Handshake; import net.md_5.bungee.packet.PacketCDClientStatus; import net.md_5.bungee.packet.PacketFCEncryptionResponse; import net.md_5.bungee.packet.PacketFDEncryptionRequest; +import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketInputStream; import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherOutputStream; public class ServerConnection extends GenericConnection { - private final Packet1Login loginPacket; + public final Packet1Login loginPacket; public ServerConnection(Socket socket, PacketInputStream in, OutputStream out, Packet1Login loginPacket) { super(socket, in, out); this.loginPacket = loginPacket; } - public static ServerConnection connect(InetSocketAddress address, Packet2Handshake handshake) throws Exception { - Socket socket = new Socket(); - socket.connect(address, BungeeCord.instance.config.timeout); - BungeeCord.instance.setSocketOptions(socket); + public static ServerConnection connect(InetSocketAddress address, Packet2Handshake handshake, boolean retry) { + try { + Socket socket = new Socket(); + socket.connect(address, BungeeCord.instance.config.timeout); + BungeeCord.instance.setSocketOptions(socket); - PacketInputStream in = new PacketInputStream(socket.getInputStream()); - OutputStream out = socket.getOutputStream(); + PacketInputStream in = new PacketInputStream(socket.getInputStream()); + OutputStream out = socket.getOutputStream(); - out.write(handshake.getPacket()); - PacketFDEncryptionRequest encryptRequest = new PacketFDEncryptionRequest(in.readPacket()); + out.write(handshake.getPacket()); + PacketFDEncryptionRequest encryptRequest = new PacketFDEncryptionRequest(in.readPacket()); - SecretKey myKey = EncryptionUtil.getSecret(); - PublicKey pub = EncryptionUtil.getPubkey(encryptRequest); + SecretKey myKey = EncryptionUtil.getSecret(); + PublicKey pub = EncryptionUtil.getPubkey(encryptRequest); - PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(EncryptionUtil.getShared(myKey, pub), EncryptionUtil.encrypt(pub, encryptRequest.verifyToken)); + PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(EncryptionUtil.getShared(myKey, pub), EncryptionUtil.encrypt(pub, encryptRequest.verifyToken)); + out.write(response.getPacket()); - int ciphId = Util.getId(in.readPacket()); - if (ciphId != 0xFC) { - throw new RuntimeException("Server did not send encryption enable"); + int ciphId = Util.getId(in.readPacket()); + if (ciphId != 0xFC) { + throw new RuntimeException("Server did not send encryption enable"); + } + + in = new PacketInputStream(new CipherInputStream(socket.getInputStream(), EncryptionUtil.getCipher(false, myKey))); + out = new CipherOutputStream(out, EncryptionUtil.getCipher(true, myKey)); + + out.write(new PacketCDClientStatus((byte) 0).getPacket()); + byte[] loginResponse = in.readPacket(); + if (Util.getId(loginResponse) == 0xFF) { + throw new KickException("[Kicked] " + new PacketFFKick(loginResponse).message); + } + Packet1Login login = new Packet1Login(loginResponse); + + return new ServerConnection(socket, in, out, login); + } catch (KickException ex) { + throw ex; + } catch (Exception ex) { + InetSocketAddress def = BungeeCord.instance.config.getServer(null); + if (retry && address != def) { + return connect(def, handshake, false); + } else { + throw new RuntimeException("Could not connect to target server"); + } } - - in = new PacketInputStream(new CipherInputStream(socket.getInputStream(), EncryptionUtil.getCipher(false, myKey))); - out = new CipherOutputStream(out, EncryptionUtil.getCipher(true, myKey)); - - out.write(new PacketCDClientStatus((byte) 0).getPacket()); - Packet1Login login = new Packet1Login(in.readPacket()); - - return new ServerConnection(socket, in, out, login); } } diff --git a/src/main/java/net/md_5/bungee/UserConnection.java b/src/main/java/net/md_5/bungee/UserConnection.java index 617498e2..172f96af 100644 --- a/src/main/java/net/md_5/bungee/UserConnection.java +++ b/src/main/java/net/md_5/bungee/UserConnection.java @@ -1,16 +1,115 @@ package net.md_5.bungee; +import java.io.IOException; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.Socket; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.Packet2Handshake; +import net.md_5.bungee.packet.Packet3Chat; import net.md_5.bungee.packet.PacketInputStream; public class UserConnection extends GenericConnection { - private final Packet2Handshake handshake; + public final Packet2Handshake handshake; + public Queue packetQueue = new ConcurrentLinkedQueue<>(); + private ServerConnection server; + private UpstreamBridge upBridge; + private DownstreamBridge downBridge; public UserConnection(Socket socket, PacketInputStream in, OutputStream out, Packet2Handshake handshake) { super(socket, in, out); this.handshake = handshake; + username = handshake.username; + } + + public void connect(InetSocketAddress serverAddr) { + try { + ServerConnection newServer = ServerConnection.connect(serverAddr, handshake, true); + if (server == null) { + out.write(newServer.loginPacket.getPacket()); + upBridge = new UpstreamBridge(); + upBridge.start(); + } + if (downBridge != null) { + downBridge.interrupt(); + } + downBridge = new DownstreamBridge(); + server = newServer; + downBridge.start(); + } catch (KickException ex) { + destory(ex.getMessage()); + } catch (Exception ex) { + if (server == null) { + destory("Could not connect to server"); + } else { + packetQueue.add(new Packet3Chat(ChatColor.YELLOW + "The server you selected is not up at the moment.")); + } + } + } + + public void register() { + BungeeCord.instance.connections.put(username, this); + } + + private void destory(String reason) { + if (BungeeCord.instance.isRunning) { + BungeeCord.instance.connections.remove(username); + } + if (upBridge != null) { + upBridge.interrupt(); + } + if (downBridge != null) { + downBridge.interrupt(); + } + disconnect(reason); + if (server != null) { + server.disconnect("Quitting"); + } + } + + private class UpstreamBridge extends Thread { + + public UpstreamBridge() { + super("Upstream Bridge - " + username); + } + + @Override + public void run() { + while (!interrupted()) { + try { + byte[] packet = in.readPacket(); + server.out.write(packet); + } catch (IOException ex) { + destory("Reached end of stream"); + } catch (Exception ex) { + destory(Util.exception(ex)); + } + } + } + } + + private class DownstreamBridge extends Thread { + + public DownstreamBridge() { + super("Downstream Bridge - " + username); + } + + @Override + public void run() { + while (!interrupted()) { + try { + byte[] packet = server.in.readPacket(); + out.write(packet); + out.flush(); + } catch (IOException ex) { + destory("Reached end of stream"); + } catch (Exception ex) { + destory(Util.exception(ex)); + } + } + } } } diff --git a/src/main/java/net/md_5/bungee/Util.java b/src/main/java/net/md_5/bungee/Util.java index d069e9c1..381f3135 100644 --- a/src/main/java/net/md_5/bungee/Util.java +++ b/src/main/java/net/md_5/bungee/Util.java @@ -15,9 +15,6 @@ public class Util { */ public static InetSocketAddress getAddr(String hostline) { String[] split = hostline.split(":"); - if (split.length < 2) { - throw new IllegalArgumentException("Invalid split format"); - } int port = DEFAULT_PORT; if (split.length > 1) { port = Integer.parseInt(split[1]); diff --git a/src/main/java/net/md_5/bungee/packet/PacketInputStream.java b/src/main/java/net/md_5/bungee/packet/PacketInputStream.java index 4e5d1c13..7e99e207 100644 --- a/src/main/java/net/md_5/bungee/packet/PacketInputStream.java +++ b/src/main/java/net/md_5/bungee/packet/PacketInputStream.java @@ -2,6 +2,7 @@ package net.md_5.bungee.packet; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import net.md_5.bungee.Util; @@ -20,6 +21,9 @@ public class PacketInputStream { public byte[] readPacket() throws IOException { tracker.out.reset(); int id = tracker.read(); + if (id == -1) { + throw new EOFException(); + } Packet codec = VanillaPackets.packets[id]; if (codec == null) { throw new RuntimeException("No Packet id: " + Util.hex(id));