1 /** 2 * Encrypted RPC using vibe-noisestream. 3 * 4 * This module provides RPC client and server using encrypted noise streams. 5 * This requires the vibe-noisestream library and this module is therefore 6 * in an extra dub subpackage: vibe-rpcchannel:noise. 7 */ 8 module vibe.rpcchannel.noise; 9 10 import vibe.core.net; 11 import vibe.noise; 12 import vibe.rpcchannel.server; 13 import vibe.rpcchannel.client; 14 15 /** 16 * Information about a noise connection. 17 * 18 * Contains the TCPConnection as well as the NoiseStream. The NoiseStream can 19 * be queried for the public key of the remote end using the remoteKey property. 20 * This can be used to implement public key based authentication. 21 */ 22 struct NoiseInfo 23 { 24 NoiseStream stream; 25 TCPConnection conn; 26 } 27 28 /** 29 * The RPC server implementation using an encrypted vibe-noisestream transport stream. 30 */ 31 class NoiseServer(Implementation, API) 32 { 33 private: 34 static struct NoiseServerSession 35 { 36 ServerSession!API session; 37 TCPConnection conn; 38 } 39 40 TCPListener[] _listener; 41 NoiseServerSession[] _sessions; 42 NoiseSettings _noiseSettings; 43 44 /* 45 * Called if other side disconnected first. 46 */ 47 void onDisconnect(ServerSession!API session) 48 { 49 size_t j = 0; 50 for (size_t i = 0; i < _sessions.length; i++) 51 { 52 if (_sessions[i].session != session) 53 { 54 _sessions[j] = _sessions[i]; 55 j++; 56 } 57 } 58 _sessions.length = j; 59 } 60 61 /* 62 * Called when TCP listener receives a new connection. 63 */ 64 void onTCPConnect(TCPConnection conn) 65 { 66 NoiseServerSession server; 67 68 auto stream = conn.createNoiseStream(_noiseSettings); 69 70 server.session = createServerSession!(Implementation, API)(stream, NoiseInfo(stream, 71 conn)); 72 server.conn = conn; 73 _sessions ~= server; 74 scope (exit) 75 { 76 stream.finalize(); 77 onDisconnect(server.session); 78 } 79 server.session.run(); 80 } 81 82 public: 83 /** 84 * Stop listening for new connections and cleanly disconnect all 85 * existing connections. 86 */ 87 void stop() 88 { 89 foreach (listener; _listener) 90 listener.stopListening(); 91 92 foreach (session; _sessions) 93 { 94 session.session.disconnect(); 95 } 96 } 97 } 98 99 /** 100 * Create a new vibe-noisestream based API server. 101 */ 102 NoiseServer!(Implementation, API) serveNoise(Implementation, API)(NoiseSettings settings, 103 ushort port) if (isServerAPI!(Implementation, NoiseInfo)) 104 { 105 auto server = new NoiseServer!(Implementation, API)(); 106 server._noiseSettings = settings; 107 server._listener = listenTCP(port, &server.onTCPConnect); 108 return server; 109 } 110 111 /// 112 unittest 113 { 114 abstract static class API 115 { 116 string someMethod(string name); 117 } 118 119 static class Implementation : API 120 { 121 static Implementation startSession(NoiseInfo info) 122 { 123 return new Implementation(); 124 } 125 126 override string someMethod(string name) 127 { 128 return "Hello " ~ name ~ "!"; 129 } 130 } 131 132 // See vibe-noisestream for how to generate keys 133 createKeys("server.key", "server.pub"); 134 createKeys("client.key", "client.pub"); 135 136 // Need to set settings correctly. See vibe-noisestream documentation 137 // for more information. It's also possible to use a callback for remote 138 // key verification. 139 auto settings = NoiseSettings(NoiseKind.server); 140 settings.privateKeyPath = Path("server.key"); 141 settings.remoteKeyPath = Path("client.pub"); 142 143 auto server = serveNoise!(Implementation, API)(settings, 8030); 144 server.stop(); 145 } 146 147 ///ditto 148 NoiseServer!(Implementation, API) serveNoise(Implementation, API)( 149 NoiseSettings settings, ushort port, string address) if ( 150 isServerAPI!(Implementation, NoiseInfo)) 151 { 152 auto server = new NoiseServer!(Implementation, API)(); 153 server._noiseSettings = settings; 154 server._listener = [listenTCP(port, &server.onTCPConnect, address)]; 155 return server; 156 } 157 158 /** 159 * An alias for a vibe-noisestream based RPCClient. 160 */ 161 template NoiseClient(API) 162 { 163 alias NoiseClient = RPCClient!(API, NoiseInfo); 164 } 165 166 /** 167 * Connect to a remote, vibe-noisestream based API server. 168 * 169 * Examples: 170 * ------- 171 * abstract static class API 172 * { 173 * string someMethod(string name); 174 * } 175 * 176 * auto settings = NoiseSettings(NoiseKind.client); 177 * settings.privateKeyPath = Path("client.key"); 178 * settings.remoteKeyPath = Path("server.pub"); 179 * 180 * auto client = clientNoise!API(settings, "127.0.0.1", 8030); 181 * client.someMethod("john"); 182 * client.closeTCP(); 183 * ------ 184 */ 185 auto clientNoise(API)(NoiseSettings settings, string host, ushort port, 186 string bind_interface = null, ushort bind_port = cast(ushort) 0u) 187 { 188 auto conn = connectTCP(host, port, bind_interface, bind_port); 189 auto stream = conn.createNoiseStream(settings); 190 return createClientSession!(API, NoiseInfo)(stream, NoiseInfo(stream, conn)); 191 } 192 193 ///ditto 194 auto clientNoise(API)(NoiseSettings settings, NetworkAddress addr, 195 NetworkAddress bind_address = anyAddress()) 196 { 197 auto conn = connectTCP(addr, bind_address); 198 auto stream = conn.createNoiseStream(settings); 199 return createClientSession!(API, NoiseInfo)(stream, NoiseInfo(stream, conn)); 200 } 201 202 void closeNoise(Client)(Client client) 203 { 204 client.disconnect(); 205 client.connectionInfo.stream.finalize(); 206 if (client.connectionInfo.conn.connected) 207 { 208 client.connectionInfo.conn.close(); 209 } 210 } 211 212 version (unittest) 213 { 214 import vibe.d, tinyevent, std.stdio; 215 216 abstract class API 217 { 218 void request1(); 219 void startEvents(); 220 void stopEvents(); 221 Event!() counterEvent; 222 } 223 224 class APIServer : API 225 { 226 NoiseInfo _info; 227 bool _eventStop = false; 228 229 this(NoiseInfo info) 230 { 231 _info = info; 232 } 233 234 static APIServer startSession(NoiseInfo info) 235 { 236 return new APIServer(info); 237 } 238 239 override void request1() 240 { 241 } 242 243 override void startEvents() 244 { 245 runTask(() { 246 while (!_eventStop) 247 { 248 counterEvent.emit(); 249 sleep(1000.usecs); 250 } 251 }); 252 } 253 254 override void stopEvents() 255 { 256 _eventStop = true; 257 } 258 } 259 } 260 261 // Test heavy event / call interleaving to make sure data does not get 262 // interleaved on the connection 263 // Test basic functions 264 unittest 265 { 266 uint done = 0; 267 NoiseServer!(APIServer, API) server; 268 269 void clientMain() 270 { 271 try 272 { 273 scope (exit) 274 { 275 done++; 276 if (done == 100) 277 { 278 // We eant to test that the client disconnects first! 279 sleep(10.msecs); 280 server.stop(); 281 exitEventLoop(); 282 } 283 } 284 285 auto settings = NoiseSettings(NoiseKind.client); 286 settings.privateKeyPath = Path("client.key"); 287 settings.remoteKeyPath = Path("server.pub"); 288 auto client = clientNoise!(API)(settings, "localhost", 8020); 289 client.startEvents(); 290 for (size_t i = 0; i < 100; i++) 291 { 292 client.request1(); 293 } 294 client.stopEvents(); 295 client.closeNoise(); 296 } 297 catch (Exception e) 298 assert(false, "Should not throw: " ~ e.toString()); 299 } 300 301 createKeys("server.key", "server.pub"); 302 createKeys("client.key", "client.pub"); 303 304 auto settings = NoiseSettings(NoiseKind.server); 305 settings.privateKeyPath = Path("server.key"); 306 settings.remoteKeyPath = Path("client.pub"); 307 308 server = serveNoise!(APIServer, API)(settings, 8020); 309 for (size_t i = 0; i < 100; i++) 310 runTask(&clientMain); 311 runEventLoop(); 312 }