UNPKG

the-wireguard-effect

Version:

Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts

208 lines (206 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.requestWireguardDemoConfig = exports.WireguardDemoServerSchema = exports.WireguardDemoServer = void 0; var NodeHttpServer = _interopRequireWildcard(require("@effect/platform-node/NodeHttpServer")); var NodeSocket = _interopRequireWildcard(require("@effect/platform-node/NodeSocket")); var HttpServer = _interopRequireWildcard(require("@effect/platform/HttpServer")); var HttpServerResponse = _interopRequireWildcard(require("@effect/platform/HttpServerResponse")); var Socket = _interopRequireWildcard(require("@effect/platform/Socket")); var SocketServer = _interopRequireWildcard(require("@effect/platform/SocketServer")); var Array = _interopRequireWildcard(require("effect/Array")); var DateTime = _interopRequireWildcard(require("effect/DateTime")); var Effect = _interopRequireWildcard(require("effect/Effect")); var Function = _interopRequireWildcard(require("effect/Function")); var Layer = _interopRequireWildcard(require("effect/Layer")); var HashMap = _interopRequireWildcard(require("effect/MutableHashMap")); var Option = _interopRequireWildcard(require("effect/Option")); var Queue = _interopRequireWildcard(require("effect/Queue")); var Schema = _interopRequireWildcard(require("effect/Schema")); var Sink = _interopRequireWildcard(require("effect/Sink")); var Stream = _interopRequireWildcard(require("effect/Stream")); var String = _interopRequireWildcard(require("effect/String")); var Tuple = _interopRequireWildcard(require("effect/Tuple")); var dns = _interopRequireWildcard(require("node:dns")); var http = _interopRequireWildcard(require("node:http")); var InternetSchemas = _interopRequireWildcard(require("./InternetSchemas.js")); var WireguardConfig = _interopRequireWildcard(require("./WireguardConfig.js")); var WireguardInterface = _interopRequireWildcard(require("./WireguardInterface.js")); var WireguardKey = _interopRequireWildcard(require("./WireguardKey.js")); var WireguardPeer = _interopRequireWildcard(require("./WireguardPeer.js")); var internalInternetSchemas = _interopRequireWildcard(require("./internal/internetSchemas.js")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /** * Utilities for connecting to the Wireguard demo server at demo.wireguard.com * * @since 1.0.0 */ /** * @since 1.0.0 * @category Schema */ const WireguardDemoServerSchema = exports.WireguardDemoServerSchema = /*#__PURE__*/Schema.transform(Schema.TemplateLiteral(Schema.Literal("OK"), Schema.Literal(":"), Schema.String, Schema.Literal(":"), Schema.Number, Schema.Literal(":"), Schema.String, Schema.Literal("\n")), Schema.Struct({ serverPort: InternetSchemas.Port, serverPublicKey: WireguardKey.WireguardKey, yourWireguardAddress: InternetSchemas.Address }), { decode: input => { const [_status, key, port, address] = internalInternetSchemas.splitLiteral(input, ":"); return { serverPublicKey: key, serverPort: Number.parseInt(port), yourWireguardAddress: address.slice(0, -1) }; }, encode: ({ serverPort, serverPublicKey, yourWireguardAddress }) => `OK:${serverPublicKey}:${serverPort}:${yourWireguardAddress}\n` }).annotations({ identifier: "WireguardDemoSchema", description: "Wireguard demo server response" }); /** * Attempts a DNS lookup of the given host (needed because wireguard will not * perform dns lookups). * * @internal */ const dnsLookup = host => Effect.async(resume => { dns.lookup(host, (err, address, _family) => { if (err) { const error = new Socket.SocketGenericError({ cause: `Could not lookup ${host}`, reason: "Open" }); return resume(Effect.fail(error)); } else { return resume(Effect.succeed(address)); } }); }); /** * Creates a Wireguard configuration to connect to demo.wireguard.com. When * connected, you should be able to see the hidden page at 192.168.4.1 * * @since 1.0.0 * @see https://git.zx2c4.com/wireguard-tools/plain/contrib/ncat-client-server/client.sh */ const requestWireguardDemoConfig = (connectOptions = { port: 42912, host: "demo.wireguard.com" }, { privateKey, publicKey } = WireguardKey.generateKeyPair()) => Function.pipe( // Connect to the server and send our public key Stream.make(`${publicKey}\n`), Stream.concat(Stream.never), Stream.pipeThroughChannelOrFail(NodeSocket.makeNetChannel(connectOptions)), // Decode the server's response Stream.decodeText(), Stream.run(Sink.head()), Effect.map(Option.getOrUndefined), Effect.flatMap(Schema.decodeUnknown(WireguardDemoServerSchema)), // Create the wireguard configuration Effect.andThen(serverResponse => Effect.gen(function* () { // TODO: Is this a safe assumption? const netmask = "/24"; const host = yield* dnsLookup(connectOptions.host); const address = `${serverResponse.yourWireguardAddress.ip}${netmask}`; const cidr = yield* Schema.decode(InternetSchemas.CidrBlockFromString)(address); const networkAddress = yield* cidr.networkAddress(); const allowedIps = new Set([`${networkAddress.ip}${netmask}`]); return yield* Schema.decode(WireguardConfig.WireguardConfig)({ ListenPort: 0, Dns: "1.1.1.1", Address: address, PrivateKey: privateKey, Peers: [{ AllowedIPs: allowedIps, PersistentKeepalive: 25, PublicKey: serverResponse.serverPublicKey, Endpoint: `${host}:${serverResponse.serverPort}` }] }); }))); /** @internal */ exports.requestWireguardDemoConfig = requestWireguardDemoConfig; const hiddenPageContent = `<title>WireGuard Demo Configuration: Success!</title> <body bgcolor="#444444"> <script src="snowstorm.js"></script> <script src="trail.js"></script> <center> <blink> <marquee width="100%" behavior="alternate" direction="right" scrollamount="10"> <marquee height="100%" behavior="alternate" direction="down"> <marquee width="100%" bgcolor="#33aadd" direction="right" behavior="alternate"><font face="comic sans ms" size="7" style="font-size: 3vw" color="#ddaa33">Congrats! You've successfully configured WireGuard!</font><br><marquee scrollamount="30"><img src="emblem.svg" width="20%"></marquee><br><marquee direction="left" scrollamount="40" behavior="alternate"><script>document.write('<iframe frameborder="0" height="80%" width="70%" src="/?' + (((document.location.search.substring(1)|0) + 1) % 4) + '"></iframe>');</script></marquee><br><br></marquee> </marquee> </marquee> </blink> </center> </body> `; /** * Mock implementation of the Wireguard demo server at demo.wireguard.com * * @since 1.0.0 * @see https://git.zx2c4.com/wireguard-tools/plain/contrib/ncat-client-server/server.sh */ const WireguardDemoServer = options => Effect.gen(function* () { const server = yield* SocketServer.SocketServer; // Generate the server's wireguard keys and network const serverWireguardKeys = WireguardKey.generateKeyPair(); const wireguardNetwork = yield* Schema.decode(InternetSchemas.CidrBlockFromString)(options.wireguardNetwork); const networkSize = yield* wireguardNetwork.total; // Setup the wireguard peer address pool const serverWireguardAddressPool = yield* Queue.dropping(Math.min(options?.maxPeers || 256, Number(networkSize))); yield* Function.pipe(wireguardNetwork.range, Stream.drop(2), Stream.run(Sink.fromQueue(serverWireguardAddressPool))); const addressReservationLookup = HashMap.empty(); // Setup the wireguard interface and wireguard server config const serverWireguardInterface = yield* WireguardInterface.WireguardInterface.getNextAvailableInterface; const serverWireguardConfig = yield* Schema.decode(WireguardConfig.WireguardConfig)({ Address: options.wireguardNetwork, PrivateKey: serverWireguardKeys.privateKey, ListenPort: options.serverEndpoint.listenPort }); yield* serverWireguardInterface.upScoped(serverWireguardConfig); const requestHandler = socket => Effect.gen(function* () { const responses = yield* Queue.unbounded(); yield* Stream.fromQueue(responses).pipe(Stream.pipeThroughChannel(Socket.toChannel(socket)), Stream.decodeText(), Stream.map(String.replace("\n", "")), Stream.mapEffect(Schema.decode(WireguardKey.WireguardKey)), Stream.mapEffect(request => Schema.decode(WireguardPeer.WireguardPeer)({ PublicKey: request, PersistentKeepalive: 25, AllowedIPs: new Set(["0.0.0.0/0"]) })), // Prune the oldest peer if we run out of addresses in the queue Stream.mapEffect(peer => Effect.gen(function* () { const size = yield* Queue.size(serverWireguardAddressPool); if (size > 0) return peer; const config = yield* serverWireguardInterface.getConfig(options.wireguardNetwork); const lastPeer = Function.pipe(config.Peers, Array.sort((a, b) => { const aLastHandshake = DateTime.toEpochMillis(a.lastHandshake); const bLastHandshake = DateTime.toEpochMillis(b.lastHandshake); if (aLastHandshake < bLastHandshake) return -1;else if (aLastHandshake > bLastHandshake) return 1;else return 0; }), Array.head, Option.getOrThrow); yield* serverWireguardInterface.removePeer(lastPeer); const freedAddress = HashMap.get(addressReservationLookup, lastPeer.PublicKey).pipe(Option.getOrThrow); yield* Queue.offer(serverWireguardAddressPool, freedAddress); HashMap.remove(addressReservationLookup, lastPeer.PublicKey); return peer; })), Stream.mapEffect(peer => Function.pipe(serverWireguardAddressPool, Queue.take, Effect.map(ip => ({ yourWireguardAddress: ip, serverPort: options.serverEndpoint.natPort, serverPublicKey: serverWireguardKeys.publicKey })), Effect.map(response => Tuple.make(peer, response)))), Stream.runForEach(([peer, res]) => Effect.gen(function* () { const encoded = yield* Schema.encode(WireguardDemoServerSchema)(res); yield* responses.offer(encoded); HashMap.set(addressReservationLookup, peer.PublicKey, res.yourWireguardAddress); yield* serverWireguardInterface.addPeer(peer); }))); }); // Start the server Layer.launch(HttpServer.serve(Effect.succeed(HttpServerResponse.html(hiddenPageContent)))).pipe(Effect.provide(NodeHttpServer.layer(() => http.createServer(), { port: 8080, host: "192.168.4.1" }))).pipe(Effect.runFork); yield* server.run(requestHandler); }); exports.WireguardDemoServer = WireguardDemoServer; //# sourceMappingURL=WireguardServer.js.map