the-wireguard-effect
Version:
Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts
208 lines (206 loc) • 11 kB
JavaScript
;
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