Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,35 @@ <h4>Debugger</h4>
</td>
</tr>

<tr>
<td colspan="2"><hr></td>
</tr>

<tr>
<td><label for="relay_url">Networking proxy (leave blank to disable)</label></td>
<td>
<input id="relay_url" type="text" value="wss://relay.widgetry.org/">
</td>
</tr>

<tr>
<td><label for="net_device_type">Network Device Type</label></td>
<td>
<select id="net_device_type">
<option value="ne2k">ne2k</option>
<option value="virtio">virtio</option>
</select>
</td>
</tr>

<tr>
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
<td>
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
</td>
</tr>


<tr>
<td colspan="2"><hr></td>
</tr>
Expand Down
1 change: 1 addition & 0 deletions docs/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Backends `fetch` and `wisp` support a couple of special settings in `config.net_
| **dns_method** | str | DNS method to use, either `static` or `doh`. `static`: use built-in DNS server, `doh`: use [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) (DoH). Defaults to `static` for `fetch` and to `doh` for `wisp` backend. |
| **doh_server** | str | Host name or IP address (and optional port number) of the DoH server if `dns_method` is `doh`. The value is expanded to the URL `https://DOH_SERVER/dns-query`. Default: `cloudflare-dns.com`. |
| **cors_proxy** | str | CORS proxy server URL, do not use a proxy if undefined. Default: undefined (`fetch` backend only). |
| **mtu** | int | The MTU used for the virtual network. Increasing it can improve performance. This only works if the NIC type is `virtio`. Default: `1500` |

#### Example `net_device` settings

Expand Down
21 changes: 21 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ <h4 id="setup">Setup</h4>
</td>
</tr>

<tr>
<td colspan="2"><hr></td>
</tr>

<tr>
<td><label for="relay_url">Networking proxy</label><br>
Presets: <a id="network_none">none</a>, <a id="network_inbrowser">inbrowser</a>, <a id="network_relay">public relay</a>, <a id="network_wisp">wisp</a>, <a id="network_fetch">fetch</a></td>
Expand All @@ -255,6 +259,23 @@ <h4 id="setup">Setup</h4>
</td>
</tr>

<tr>
<td><label for="net_device_type">Network Device Type</label></td>
<td>
<select id="net_device_type">
<option value="ne2k">ne2k</option>
<option value="virtio">virtio</option>
</select>
</td>
</tr>

<tr>
<td><label for="mtu">Network MTU (only used for virtio)</label></td>
<td>
<input id="mtu" type="number" value="1500" min="68" max="65535" step="1"> B<br>
</td>
</tr>

<tr>
<td colspan="2"><hr></td>
</tr>
Expand Down
59 changes: 44 additions & 15 deletions src/browser/fake_network.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,14 @@ const TCP_DYNAMIC_PORT_RANGE = TCP_DYNAMIC_PORT_END - TCP_DYNAMIC_PORT_START;

const ETH_HEADER_SIZE = 14;
const ETH_PAYLOAD_OFFSET = ETH_HEADER_SIZE;
const ETH_PAYLOAD_SIZE = 1500;
const MTU_DEFAULT = 1500;
const ETH_TRAILER_SIZE = 4;
const ETH_FRAME_SIZE = ETH_HEADER_SIZE + ETH_PAYLOAD_SIZE + ETH_TRAILER_SIZE;
const IPV4_HEADER_SIZE = 20;
const IPV4_PAYLOAD_OFFSET = ETH_PAYLOAD_OFFSET + IPV4_HEADER_SIZE;
const IPV4_PAYLOAD_SIZE = ETH_PAYLOAD_SIZE - IPV4_HEADER_SIZE;
const UDP_HEADER_SIZE = 8;
const UDP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_SIZE;
const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE;
const TCP_HEADER_SIZE = 20;
const TCP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + TCP_HEADER_SIZE;
const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE;
const ICMP_HEADER_SIZE = 4;

const DEFAULT_DOH_SERVER = "cloudflare-dns.com";
Expand Down Expand Up @@ -161,15 +157,19 @@ class GrowableRingbuffer
}
}

export function create_eth_encoder_buf()
export function create_eth_encoder_buf(mtu = MTU_DEFAULT)
{
const ETH_FRAME_SIZE = ETH_HEADER_SIZE + mtu + ETH_TRAILER_SIZE;
const IPV4_PAYLOAD_SIZE = mtu - IPV4_HEADER_SIZE;
const UDP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - UDP_HEADER_SIZE;

const eth_frame = new Uint8Array(ETH_FRAME_SIZE);
const buffer = eth_frame.buffer;
const offset = eth_frame.byteOffset;
return {
eth_frame: eth_frame,
eth_frame_view: new DataView(buffer),
eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, ETH_PAYLOAD_SIZE),
eth_payload_view: new DataView(buffer, offset + ETH_PAYLOAD_OFFSET, mtu),
ipv4_payload_view: new DataView(buffer, offset + IPV4_PAYLOAD_OFFSET, IPV4_PAYLOAD_SIZE),
udp_payload_view: new DataView(buffer, offset + UDP_PAYLOAD_OFFSET, UDP_PAYLOAD_SIZE),
text_encoder: new TextEncoder()
Expand All @@ -190,6 +190,17 @@ function view_set_array(offset, data, view, out)
return data.length;
}

/**
* Write zeros into the view starting at offset
* @param {number} offset
* @param {number} length
* @param {DataView} view
*/
function view_set_zeros(offset, length, view, out)
{
out.eth_frame.fill(0, view.byteOffset + offset, view.byteOffset + offset + length);
}

/**
* UTF8-encode given string into view starting at offset, return number of bytes written.
*
Expand Down Expand Up @@ -949,19 +960,31 @@ function write_tcp(spec, out) {
if(tcp.ece) flags |= 0x40;
if(tcp.cwr) flags |= 0x80;

const doff = TCP_HEADER_SIZE >> 2; // header length in 32-bit words
let doff = TCP_HEADER_SIZE;
if(tcp.options) {
if(tcp.options.mss) {
view.setUint8(doff, 0x02); //mss option type
view.setUint8(doff + 1, 0x04); //option length
view.setUint16(doff + 2, tcp.options.mss);
doff += 4;
}
}

let total_length = Math.ceil(doff / 4) * 4; // needs to a multiple of 4 bytes
if(tcp.options && total_length - doff > 0) {
view_set_zeros(doff, total_length - doff, view, out); //write zeros into remaining space for options
}

view.setUint16(0, tcp.sport);
view.setUint16(2, tcp.dport);
view.setUint32(4, tcp.seq);
view.setUint32(8, tcp.ackn);
view.setUint8(12, doff << 4);
view.setUint8(12, (total_length >> 2) << 4); // header length in 32-bit words
view.setUint8(13, flags);
view.setUint16(14, tcp.winsize);
view.setUint16(16, 0); // checksum initially zero before calculation
view.setUint16(18, tcp.urgent || 0);

let total_length = TCP_HEADER_SIZE;
if(spec.tcp_data) {
total_length += view_set_array(TCP_HEADER_SIZE, spec.tcp_data, view, out);
}
Expand Down Expand Up @@ -991,7 +1014,7 @@ export function fake_tcp_connect(dport, adapter)
throw new Error("pool of dynamic TCP port numbers exhausted, connection aborted");
}

let conn = new TCPConnection();
let conn = new TCPConnection(adapter);

conn.tuple = tuple;
conn.hsrc = adapter.router_mac;
Expand All @@ -1000,7 +1023,6 @@ export function fake_tcp_connect(dport, adapter)
conn.hdest = adapter.vm_mac;
conn.dport = dport;
conn.pdest = adapter.vm_ip;
conn.net = adapter;
adapter.tcp_conn[tuple] = conn;
conn.connect();
return conn;
Expand All @@ -1017,10 +1039,14 @@ export function fake_tcp_probe(dport, adapter) {
/**
* @constructor
*/
export function TCPConnection()
export function TCPConnection(adapter)
{
this.mtu = (adapter.mtu || MTU_DEFAULT);
const IPV4_PAYLOAD_SIZE = this.mtu - IPV4_HEADER_SIZE;
const TCP_PAYLOAD_SIZE = IPV4_PAYLOAD_SIZE - TCP_HEADER_SIZE;

this.state = TCP_STATE_CLOSED;
this.net = null; // The adapter is stored here
this.net = adapter; // The adapter is stored here
this.send_buffer = new GrowableRingbuffer(2048, 0);
this.send_chunk_buf = new Uint8Array(TCP_PAYLOAD_SIZE);
this.in_active_close = false;
Expand Down Expand Up @@ -1119,7 +1145,10 @@ TCPConnection.prototype.accept = function(packet) {
ackn: this.ack,
winsize: packet.tcp.winsize,
syn: true,
ack: true
ack: true,
options: {
mss: (this.mtu - TCP_HEADER_SIZE - IPV4_HEADER_SIZE)
}
};
// dbg_log(`TCP[${this.tuple}]: accept(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH);
this.state = TCP_STATE_ESTABLISHED;
Expand Down
6 changes: 3 additions & 3 deletions src/browser/fetch_network.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export function FetchNetworkAdapter(bus, config)
this.dns_method = config.dns_method || "static";
this.doh_server = config.doh_server;
this.tcp_conn = {};
this.eth_encoder_buf = create_eth_encoder_buf();
this.mtu = config.mtu;
this.eth_encoder_buf = create_eth_encoder_buf(this.mtu);
this.fetch = (...args) => fetch(...args);

// Ex: 'https://corsproxy.io/?'
Expand All @@ -55,9 +56,8 @@ FetchNetworkAdapter.prototype.destroy = function()
FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
if(packet.tcp.dport === 80) {
let conn = new TCPConnection();
let conn = new TCPConnection(this);
conn.state = TCP_STATE_SYN_RECEIVED;
conn.net = this;
conn.on("data", on_data_http);
conn.tuple = tuple;
conn.accept(packet);
Expand Down
25 changes: 23 additions & 2 deletions src/browser/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const DEFAULT_NETWORKING_PROXIES = ["wss://relay.widgetry.org/", "ws://localhost
const DEFAULT_MEMORY_SIZE = 128;
const DEFAULT_VGA_MEMORY_SIZE = 8;
const DEFAULT_BOOT_ORDER = 0;
const DEFAULT_MTU = 1500;
const DEFAULT_NIC_TYPE = "ne2k";

const MAX_ARRAY_BUFFER_SIZE_MB = 2000;

Expand Down Expand Up @@ -1689,6 +1691,8 @@ function onload()
if(query_args.has("mute")) $("disable_audio").checked = bool_arg(query_args.get("mute"));
if(query_args.has("acpi")) $("acpi").checked = bool_arg(query_args.get("acpi"));
if(query_args.has("boot_order")) $("boot_order").value = query_args.get("boot_order");
if(query_args.has("net_device_type")) $("net_device_type").value = query_args.get("net_device_type");
if(query_args.has("mtu")) $("mtu").value = query_args.get("mtu");

for(const dev of ["fda", "fdb"])
{
Expand Down Expand Up @@ -2091,6 +2095,7 @@ function start_emulation(profile, query_args)
settings.acpi = query_args.has("acpi") ? bool_arg(query_args.get("acpi")) : settings.acpi;
settings.use_bochs_bios = query_args.get("bios") === "bochs";
settings.net_device_type = query_args.get("net_device_type") || settings.net_device_type;
settings.mtu = parseInt(query_args.get("mtu"), 10) || undefined;
}

settings.relay_url = query_args.get("relay_url");
Expand Down Expand Up @@ -2239,6 +2244,21 @@ function start_emulation(profile, query_args)
settings.bios = { url: BIOSPATH + "bochs-bios.bin" };
settings.vga_bios = { url: BIOSPATH + "bochs-vgabios.bin" };
}

const nic_type = $("net_device_type").value || DEFAULT_NIC_TYPE;
if(!settings.net_device_type || nic_type !== DEFAULT_NIC_TYPE)
{
settings.net_device_type = nic_type;
}
if(settings.net_device_type !== DEFAULT_NIC_TYPE) new_query_args.set("net_device_type", settings.net_device_type);

const mtu = parseInt($("mtu").value, 10) || DEFAULT_MTU;
if(!settings.mtu || mtu !== DEFAULT_MTU)
{
settings.mtu = mtu;
}
if(settings.mtu !== DEFAULT_MTU) new_query_args.set("mtu", settings.mtu.toString());

}

if(!query_args)
Expand All @@ -2253,9 +2273,10 @@ function start_emulation(profile, query_args)
use_graphical_text: false,
},
net_device: {
type: settings.net_device_type || "ne2k",
type: settings.net_device_type || DEFAULT_NIC_TYPE,
relay_url: settings.relay_url,
cors_proxy: settings.cors_proxy
cors_proxy: settings.cors_proxy,
mtu: settings.mtu
},
autostart: true,

Expand Down
12 changes: 7 additions & 5 deletions src/browser/wisp_network.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export function WispNetworkAdapter(wisp_url, bus, config)
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.mtu = config.mtu;
this.eth_encoder_buf = create_eth_encoder_buf(this.mtu);

this.bus.register("net" + this.id + "-mac", function(mac) {
this.vm_mac = new Uint8Array(mac.split(":").map(function(x) { return parseInt(x, 16); }));
Expand Down Expand Up @@ -93,10 +94,12 @@ WispNetworkAdapter.prototype.process_incoming_wisp_frame = function(frame) {
}

if(this.connections[stream_id].congested) {
for(const packet of this.congested_buffer) {
const buffer = this.congested_buffer.slice(0);
this.congested_buffer.length = 0;
this.connections[stream_id].congested = false;
for(const packet of buffer) {
this.send_packet(packet.data, packet.type, stream_id);
}
this.connections[stream_id].congested = false;
}
break;
case 4: // CLOSE
Expand Down Expand Up @@ -190,9 +193,8 @@ WispNetworkAdapter.prototype.destroy = function()
*/
WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple)
{
let conn = new TCPConnection();
let conn = new TCPConnection(this);
conn.state = TCP_STATE_SYN_RECEIVED;
conn.net = this;
conn.tuple = tuple;
conn.stream_id = this.last_stream++;
this.tcp_conn[tuple] = conn;
Expand Down
2 changes: 1 addition & 1 deletion src/cpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ CPU.prototype.init = function(settings, device_bus)
}
else if(settings.net_device.type === "virtio")
{
this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image);
this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image, settings.net_device.mtu);
}

if(settings.fs9p)
Expand Down
7 changes: 5 additions & 2 deletions src/virtio_net.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as marshall from "../lib/marshall.js";
import { CPU } from "./cpu.js";
import { BusConnector } from "./bus.js";

const MTU_DEFAULT = 1500;

const VIRTIO_NET_F_MAC = 5;
const VIRTIO_NET_F_CTRL_VQ = 17;
const VIRTIO_NET_F_STATUS = 16;
Expand All @@ -24,8 +26,9 @@ const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1;
* @param {CPU} cpu
* @param {BusConnector} bus
* @param {Boolean} preserve_mac_from_state_image
* @param {number} mtu
*/
export function VirtioNet(cpu, bus, preserve_mac_from_state_image)
export function VirtioNet(cpu, bus, preserve_mac_from_state_image, mtu = MTU_DEFAULT)
{
/** @const @type {BusConnector} */
this.bus = bus;
Expand Down Expand Up @@ -177,7 +180,7 @@ export function VirtioNet(cpu, bus, preserve_mac_from_state_image)
{
bytes: 2,
name: "mtu",
read: () => 1500,
read: () => mtu,
write: data => {},
}
])
Expand Down
1 change: 1 addition & 0 deletions v86.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export interface V86Options {
dns_method?: "static" | "doh";
doh_server?: string;
cors_proxy?: string;
mtu?: number;
};
}

Expand Down