import { LOG_NET } from "../const.js"; import { dbg_log } from "../log.js"; import { create_eth_encoder_buf, handle_fake_networking, TCPConnection, TCP_STATE_SYN_RECEIVED, } from "./fake_network.js"; // For Types Only import { BusConnector } from "../bus.js"; /** * @constructor * * @param {String} wisp_url * @param {BusConnector} bus * @param {*=} config */ export function WispNetworkAdapter(wisp_url, bus, config) { this.register_ws(wisp_url); this.last_stream = 1; this.connections = {0: {congestion: 0}}; this.congested_buffer = []; config = config || {}; this.bus = bus; this.id = config.id || 0; this.router_mac = new Uint8Array((config.router_mac || "52:54:0:1:2:3").split(":").map(function(x) { return parseInt(x, 16); })); this.router_ip = new Uint8Array((config.router_ip || "192.168.86.1").split(".").map(function(x) { return parseInt(x, 10); })); this.vm_ip = new Uint8Array((config.vm_ip || "192.168.86.100").split(".").map(function(x) { return parseInt(x, 10); })); this.masquerade = config.masquerade === undefined || !!config.masquerade; this.vm_mac = new Uint8Array(6); this.dns_method = config.dns_method || "doh"; this.doh_server = config.doh_server; this.tcp_conn = {}; this.eth_encoder_buf = create_eth_encoder_buf(); this.bus.register("net" + this.id + "-mac", function(mac) { this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); })); }, this); this.bus.register("net" + this.id + "-send", function(data) { this.send(data); }, this); } WispNetworkAdapter.prototype.register_ws = function(wisp_url) { this.wispws = new WebSocket(wisp_url.replace("wisp://", "ws://").replace("wisps://", "wss://")); this.wispws.binaryType = "arraybuffer"; this.wispws.onmessage = (event) => { this.process_incoming_wisp_frame(new Uint8Array(event.data)); }; this.wispws.onclose = () => { setTimeout(() => { this.register_ws(wisp_url); }, 10000); // wait 10s before reconnecting }; }; WispNetworkAdapter.prototype.send_packet = function(data, type, stream_id) { if(this.connections[stream_id]) { if(this.connections[stream_id].congestion > 0) { if(type === "DATA") { this.connections[stream_id].congestion--; } this.wispws.send(data); } else { this.connections[stream_id].congested = true; this.congested_buffer.push({data: data, type: type}); } } }; WispNetworkAdapter.prototype.process_incoming_wisp_frame = function(frame) { const view = new DataView(frame.buffer); const stream_id = view.getUint32(1, true); switch(frame[0]) { case 1: // CONNECT // The server should never send this actually dbg_log("Server sent client-only packet CONNECT", LOG_NET); break; case 2: // DATA if(this.connections[stream_id]) this.connections[stream_id].data_callback(frame.slice(5)); else throw new Error("Got a DATA packet but stream not registered. ID: " + stream_id); break; case 3: // CONTINUE if(this.connections[stream_id]) { this.connections[stream_id].congestion = view.getUint32(5, true); } if(this.connections[stream_id].congested) { for(const packet of this.congested_buffer) { this.send_packet(packet.data, packet.type, stream_id); } this.connections[stream_id].congested = false; } break; case 4: // CLOSE if(this.connections[stream_id]) this.connections[stream_id].close_callback(view.getUint8(5)); delete this.connections[stream_id]; break; case 5: // PROTOEXT dbg_log("got a wisp V2 upgrade request, ignoring", LOG_NET); // Not responding, this is wisp v1 client not wisp v2; break; default: dbg_log("Wisp server returned unknown packet: " + frame[0], LOG_NET); } }; // FrameObj will be the following // FrameObj.stream_id (number) // // FrameObj.type -- CONNECT // FrameObj.hostname (string) // FrameObj.port (number) // FrameObj.data_callback (function (Uint8Array)) // FrameObj.close_callback (function (number)) OPTIONAL // // // FrameObj.type -- DATA // FrameObj.data (Uint8Array) // // FrameObj.type -- CLOSE // FrameObj.reason (number) // // WispNetworkAdapter.prototype.send_wisp_frame = function(frame_obj) { let full_packet; let view; switch(frame_obj.type) { case "CONNECT": const hostname_buffer = new TextEncoder().encode(frame_obj.hostname); full_packet = new Uint8Array(5 + 1 + 2 + hostname_buffer.length); view = new DataView(full_packet.buffer); view.setUint8(0, 0x01); // TYPE view.setUint32(1, frame_obj.stream_id, true); // Stream ID view.setUint8(5, 0x01); // TCP view.setUint16(6, frame_obj.port, true); // PORT full_packet.set(hostname_buffer, 8); // hostname // Setting callbacks this.connections[frame_obj.stream_id] = { data_callback: frame_obj.data_callback, close_callback: frame_obj.close_callback, congestion: this.connections[0].congestion }; break; case "DATA": full_packet = new Uint8Array(5 + frame_obj.data.length); view = new DataView(full_packet.buffer); view.setUint8(0, 0x02); // TYPE view.setUint32(1, frame_obj.stream_id, true); // Stream ID full_packet.set(frame_obj.data, 5); // Actual data break; case "CLOSE": full_packet = new Uint8Array(5 + 1); view = new DataView(full_packet.buffer); view.setUint8(0, 0x04); // TYPE view.setUint32(1, frame_obj.stream_id, true); // Stream ID view.setUint8(5, frame_obj.reason); // Packet size break; default: dbg_log("Client tried to send unknown packet: " + frame_obj.type, LOG_NET); } this.send_packet(full_packet, frame_obj.type, frame_obj.stream_id); }; WispNetworkAdapter.prototype.destroy = function() { if(this.wispws) { this.wispws.onmessage = null; this.wispws.onclose = null; this.wispws.close(); this.wispws = null; } }; /** * @param {Uint8Array} packet * @param {String} tuple */ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) { let conn = new TCPConnection(); conn.state = TCP_STATE_SYN_RECEIVED; conn.net = this; conn.tuple = tuple; conn.stream_id = this.last_stream++; this.tcp_conn[tuple] = conn; conn.on("data", data => { if(data.length !== 0) { this.send_wisp_frame({ type: "DATA", stream_id: conn.stream_id, data: data }); } }); conn.on_close = () => { this.send_wisp_frame({ type: "CLOSE", stream_id: conn.stream_id, reason: 0x02 // 0x02: Voluntary stream closure }); }; // WISP doesn't implement shutdown, use close as workaround conn.on_shutdown = conn.on_close; this.send_wisp_frame({ type: "CONNECT", stream_id: conn.stream_id, hostname: packet.ipv4.dest.join("."), port: packet.tcp.dport, data_callback: (data) => { conn.write(data); }, close_callback: (data) => { conn.close(); } }); conn.accept(packet); return true; }; /** * @param {Uint8Array} data */ WispNetworkAdapter.prototype.send = function(data) { // TODO: forward UDP traffic to WISP server once this WISP client supports UDP handle_fake_networking(data, this); }; /** * @param {Uint8Array} data */ WispNetworkAdapter.prototype.receive = function(data) { this.bus.send("net" + this.id + "-receive", new Uint8Array(data)); };