v86 / src /browser /wisp_network.js
peterpeter8585's picture
Upload 553 files
8df6da4 verified
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));
};