1 /** 2 * TCP port forwarder. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.demo.portforward.portforward; 15 16 import ae.net.asockets; 17 import ae.sys.log; 18 import ae.utils.text; 19 20 import std.ascii; 21 import std.stdio; 22 import std.string; 23 import std.conv; 24 import std.datetime; 25 import std.getopt; 26 import std.exception; 27 28 Logger log, recordLog; 29 bool logData, record; 30 31 class Connection 32 { 33 TcpConnection outer, inner; 34 static int counter; 35 int index; 36 37 this(TcpConnection outer, string host, ushort port) 38 { 39 index = counter++; 40 this.outer = outer; 41 inner = new TcpConnection; 42 inner.handleConnect = &onInnerConnect; 43 inner.handleReadData = &onInnerData; 44 inner.handleDisconnect = &onInnerDisconnect; 45 inner.connect(host, port); 46 } 47 48 void onInnerConnect() 49 { 50 log("Connected to " ~ remoteAddressString(inner)); 51 if (record) recordLog(format("%d C %d %s", Clock.currStdTime(), index, inner.remoteAddress())); 52 outer.handleReadData = &onOuterData; 53 outer.handleDisconnect = &onOuterDisconnect; 54 } 55 56 void onOuterData(Data data) 57 { 58 if (logData) log(format("Outer connection from %s sent %d bytes:\n%s", outer.remoteAddress(), data.length, hexDump(data.unsafeContents))); 59 if (record) recordLog(format("%d < %d %s", Clock.currStdTime(), index, hexEscape(data.unsafeContents))); 60 inner.send(data); 61 } 62 63 void onOuterDisconnect(string reason, DisconnectType type) 64 { 65 log("Outer connection from " ~ outer.remoteAddressString() ~ " disconnected: " ~ reason); 66 if (record) recordLog(format("%d [ %d %s", Clock.currStdTime(), index, reason)); 67 if (type != DisconnectType.requested) 68 inner.disconnect(); 69 } 70 71 void onInnerData(Data data) 72 { 73 if (logData) log(format("Inner connection to %s sent %d bytes:\n%s", inner.remoteAddress(), data.length, hexDump(data.unsafeContents))); 74 if (record) recordLog(format("%d > %d %s", Clock.currStdTime(), index, hexEscape(data.unsafeContents))); 75 outer.send(data); 76 } 77 78 void onInnerDisconnect(string reason, DisconnectType type) 79 { 80 log("Inner connection to " ~ remoteAddressString(inner) ~ " disconnected: " ~ reason); 81 if (record) recordLog(format("%d ] %d %s", Clock.currStdTime(), index, reason)); 82 if (type != DisconnectType.requested) 83 outer.disconnect(); 84 } 85 } 86 87 class PortForwarder 88 { 89 ushort localPort; 90 string remoteHost; 91 ushort remotePort; 92 93 this(string localHost, ushort localPort, string remoteHost, ushort remotePort) 94 { 95 this.localPort = localPort; 96 this.remoteHost = remoteHost; 97 this.remotePort = remotePort; 98 99 auto listener = new TcpServer(); 100 listener.handleAccept = &onAccept; 101 listener.listen(localPort, localHost); 102 log(format("Created forwarder: %s:%d -> %s:%d", localHost ? localHost : "*", localPort, remoteHost, remotePort)); 103 if (record) recordLog(format("%d L %d", Clock.currStdTime(), localPort)); 104 } 105 106 void onAccept(TcpConnection incoming) 107 { 108 log(format("Accepted connection from %s on port %d, forwarding to %s:%d", incoming.remoteAddressString(), localPort, remoteHost, remotePort)); 109 if (record) recordLog(format("%d A %d %d", Clock.currStdTime(), Connection.counter, localPort)); 110 new Connection(incoming, remoteHost, remotePort); 111 } 112 } 113 114 void main(string[] args) 115 { 116 string listenOn = null; 117 bool quiet = false; 118 getopt(args, 119 std.getopt.config.bundling, 120 "l|listen", &listenOn, 121 "v|verbose", &logData, 122 "r|record", &record, 123 "q|quiet", &quiet); 124 125 if (args.length < 2) 126 { 127 stderr.writefln("Usage: %s [OPTION]... <destination> <sourceport>[:<targetport>] [...]", args[0]); 128 stderr.writeln("Supported options:"); 129 stderr.writeln(" -q --quiet Don't log to screen"); 130 stderr.writeln(" -l --listen=ADDRESS Listen on specified interface (all interfaces by default)"); 131 stderr.writeln(" -v --verbose Log sent/received data as well"); 132 stderr.writeln(" -r --record Log connections to a machine-readable format"); 133 return; 134 } 135 136 log = createLogger("PortForward"); 137 if (record) 138 { 139 recordLog = rawFileLogger("PortForwardRecord", true); 140 recordLog(format("%d S", Clock.currStdTime())); 141 } 142 143 string destination = args[1]; 144 foreach (portPair; args[2..$]) 145 { 146 auto segments = portPair.split(":"); 147 enforce(segments.length<=2, "Bad port syntax"); 148 new PortForwarder(listenOn, to!ushort(segments[0]), destination, to!ushort(segments.length==2 ? segments[1] : segments[0])); 149 } 150 socketManager.loop(); 151 } 152 153 string hexEscape(const(void)[] data) 154 { 155 auto bytes = cast(const(ubyte)[])data; 156 string s; 157 foreach (b; bytes) 158 if (b<0x20 || b>0x7E || b=='\\') 159 s ~= `\` ~ hexDigits[b>>4] ~ hexDigits[b&15]; 160 else 161 s ~= cast(char)b; 162 return s; 163 } 164 165 string remoteAddressString(TcpConnection c) 166 { 167 try 168 { 169 auto remoteAddress = c.remoteAddress(); 170 return remoteAddress ? remoteAddress.toString() : "(null)"; 171 } 172 catch (Exception e) 173 return "(error)"; 174 }