Spaces:
Runtime error
Runtime error
| import { LOG_FETCH } from "../const.js"; | |
| import { h } from "../lib.js"; | |
| import { dbg_assert, dbg_log } from "../log.js"; | |
| // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml | |
| const ETHERTYPE_IPV4 = 0x0800; | |
| const ETHERTYPE_ARP = 0x0806; | |
| const ETHERTYPE_IPV6 = 0x86DD; | |
| const IPV4_PROTO_ICMP = 1; | |
| const IPV4_PROTO_TCP = 6; | |
| const IPV4_PROTO_UDP = 17; | |
| const UNIX_EPOCH = new Date("1970-01-01T00:00:00Z").getTime(); | |
| const NTP_EPOCH = new Date("1900-01-01T00:00:00Z").getTime(); | |
| const NTP_EPOC_DIFF = UNIX_EPOCH - NTP_EPOCH; | |
| const TWO_TO_32 = Math.pow(2, 32); | |
| const DHCP_MAGIC_COOKIE = 0x63825363; | |
| const V86_ASCII = [118, 56, 54]; | |
| /* For the complete TCP state diagram see: | |
| * | |
| * https://en.wikipedia.org/wiki/File:Tcp_state_diagram_fixed_new.svg | |
| * | |
| * State TIME_WAIT is not needed, we can skip it and transition directly to CLOSED instead. | |
| */ | |
| export const TCP_STATE_CLOSED = "closed"; | |
| export const TCP_STATE_SYN_RECEIVED = "syn-received"; | |
| export const TCP_STATE_SYN_SENT = "syn-sent"; | |
| export const TCP_STATE_SYN_PROBE = "syn-probe"; | |
| //const TCP_STATE_LISTEN = "listen"; | |
| export const TCP_STATE_ESTABLISHED = "established"; | |
| export const TCP_STATE_FIN_WAIT_1 = "fin-wait-1"; | |
| export const TCP_STATE_CLOSE_WAIT = "close-wait"; | |
| export const TCP_STATE_FIN_WAIT_2 = "fin-wait-2"; | |
| export const TCP_STATE_LAST_ACK = "last-ack"; | |
| export const TCP_STATE_CLOSING = "closing"; | |
| //const TCP_STATE_TIME_WAIT = "time-wait"; | |
| // source: RFC6335, 6. Port Number Ranges | |
| const TCP_DYNAMIC_PORT_START = 49152; | |
| const TCP_DYNAMIC_PORT_END = 65535; | |
| 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 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"; | |
| function a2ethaddr(bytes) { | |
| return [0,1,2,3,4,5].map((i) => bytes[i].toString(16)).map(x => x.length === 1 ? "0" + x : x).join(":"); | |
| } | |
| function iptolong(parts) { | |
| return parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]; | |
| } | |
| class GrowableRingbuffer | |
| { | |
| /** | |
| * @param {number} initial_capacity | |
| * @param {number} maximum_capacity | |
| */ | |
| constructor(initial_capacity, maximum_capacity) | |
| { | |
| initial_capacity = Math.min(initial_capacity, 16); | |
| this.maximum_capacity = maximum_capacity ? Math.max(maximum_capacity, initial_capacity) : 0; | |
| this.tail = 0; | |
| this.head = 0; | |
| this.length = 0; | |
| this.buffer = new Uint8Array(initial_capacity); | |
| } | |
| /** | |
| * @param {Uint8Array} src_array | |
| */ | |
| write(src_array) | |
| { | |
| const src_length = src_array.length; | |
| const total_length = this.length + src_length; | |
| let capacity = this.buffer.length; | |
| if(capacity < total_length) { | |
| dbg_assert(capacity > 0); | |
| while(capacity < total_length) { | |
| capacity *= 2; | |
| } | |
| if(this.maximum_capacity && capacity > this.maximum_capacity) { | |
| throw new Error("stream capacity overflow in GrowableRingbuffer.write(), package dropped"); | |
| } | |
| const new_buffer = new Uint8Array(capacity); | |
| this.peek(new_buffer); | |
| this.tail = 0; | |
| this.head = this.length; | |
| this.buffer = new_buffer; | |
| } | |
| const buffer = this.buffer; | |
| const new_head = this.head + src_length; | |
| if(new_head > capacity) { | |
| const i_split = capacity - this.head; | |
| buffer.set(src_array.subarray(0, i_split), this.head); | |
| buffer.set(src_array.subarray(i_split)); | |
| } | |
| else { | |
| buffer.set(src_array, this.head); | |
| } | |
| this.head = new_head % capacity; | |
| this.length += src_length; | |
| } | |
| /** | |
| * @param {Uint8Array} dst_array | |
| */ | |
| peek(dst_array) | |
| { | |
| const length = Math.min(this.length, dst_array.length); | |
| if(length) { | |
| const buffer = this.buffer; | |
| const capacity = buffer.length; | |
| const new_tail = this.tail + length; | |
| if(new_tail > capacity) { | |
| const buf_len_left = new_tail % capacity; | |
| const buf_len_right = capacity - this.tail; | |
| dst_array.set(buffer.subarray(this.tail)); | |
| dst_array.set(buffer.subarray(0, buf_len_left), buf_len_right); | |
| } | |
| else { | |
| dst_array.set(buffer.subarray(this.tail, new_tail)); | |
| } | |
| } | |
| return length; | |
| } | |
| /** | |
| * @param {number} length | |
| */ | |
| remove(length) | |
| { | |
| if(length > this.length) { | |
| length = this.length; | |
| } | |
| if(length) { | |
| this.tail = (this.tail + length) % this.buffer.length; | |
| this.length -= length; | |
| } | |
| return length; | |
| } | |
| } | |
| export function create_eth_encoder_buf() | |
| { | |
| 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), | |
| 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() | |
| }; | |
| } | |
| /** | |
| * Copy given data array into view starting at offset, return number of bytes written. | |
| * | |
| * @param {number} offset | |
| * @param {ArrayBuffer|ArrayBufferView} data | |
| * @param {DataView} view | |
| * @param {Object} out | |
| */ | |
| function view_set_array(offset, data, view, out) | |
| { | |
| out.eth_frame.set(data, view.byteOffset + offset); | |
| return data.length; | |
| } | |
| /** | |
| * UTF8-encode given string into view starting at offset, return number of bytes written. | |
| * | |
| * @param {number} offset | |
| * @param {string} str | |
| * @param {DataView} view | |
| * @param {Object} out | |
| */ | |
| function view_set_string(offset, str, view, out) | |
| { | |
| return out.text_encoder.encodeInto(str, out.eth_frame.subarray(view.byteOffset + offset)).written; | |
| } | |
| /** | |
| * Calculate internet checksum for view[0 : length] and return the 16-bit result. | |
| * Source: RFC768 and RFC1071 (chapter 4.1). | |
| * | |
| * @param {number} length | |
| * @param {number} checksum | |
| * @param {DataView} view | |
| * @param {Object} out | |
| */ | |
| function calc_inet_checksum(length, checksum, view, out) | |
| { | |
| const uint16_end = view.byteOffset + (length & ~1); | |
| const eth_frame = out.eth_frame; | |
| for(let i = view.byteOffset; i < uint16_end; i += 2) { | |
| checksum += eth_frame[i] << 8 | eth_frame[i+1]; | |
| } | |
| if(length & 1) { | |
| checksum += eth_frame[uint16_end] << 8; | |
| } | |
| while(checksum >>> 16) { | |
| checksum = (checksum & 0xffff) + (checksum >>> 16); | |
| } | |
| return ~checksum & 0xffff; | |
| } | |
| /** | |
| * @param {Object} out | |
| * @param {Object} spec | |
| */ | |
| function make_packet(out, spec) | |
| { | |
| dbg_assert(spec.eth); | |
| out.eth_frame.fill(0); | |
| return out.eth_frame.subarray(0, write_eth(spec, out)); | |
| } | |
| function handle_fake_tcp(packet, adapter) | |
| { | |
| const tuple = `${packet.ipv4.src.join(".")}:${packet.tcp.sport}:${packet.ipv4.dest.join(".")}:${packet.tcp.dport}`; | |
| if(packet.tcp.syn) { | |
| if(adapter.tcp_conn[tuple]) { | |
| dbg_log("SYN to already opened port", LOG_FETCH); | |
| } | |
| if(adapter.on_tcp_connection(packet, tuple)) { | |
| return; | |
| } | |
| } | |
| if(!adapter.tcp_conn[tuple]) { | |
| dbg_log(`I dont know about ${tuple}, so resetting`, LOG_FETCH); | |
| let bop = packet.tcp.ackn; | |
| if(packet.tcp.fin || packet.tcp.syn) bop += 1; | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_TCP, | |
| src: packet.ipv4.dest, | |
| dest: packet.ipv4.src | |
| }; | |
| reply.tcp = { | |
| sport: packet.tcp.dport, | |
| dport: packet.tcp.sport, | |
| seq: bop, | |
| ackn: packet.tcp.seq + (packet.tcp.syn ? 1: 0), | |
| winsize: packet.tcp.winsize, | |
| rst: true, | |
| ack: packet.tcp.syn | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| return true; | |
| } | |
| adapter.tcp_conn[tuple].process(packet); | |
| } | |
| function handle_fake_dns_static(packet, adapter) | |
| { | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_UDP, | |
| src: adapter.router_ip, | |
| dest: packet.ipv4.src, | |
| }; | |
| reply.udp = { sport: 53, dport: packet.udp.sport }; | |
| let answers = []; | |
| let flags = 0x8000; //Response, | |
| flags |= 0x0180; // Recursion | |
| // flags |= 0x0400; Authoritative | |
| for(let i = 0; i < packet.dns.questions.length; ++i) { | |
| let q = packet.dns.questions[i]; | |
| switch(q.type){ | |
| case 1: // A record | |
| answers.push({ | |
| name: q.name, | |
| type: q.type, | |
| class: q.class, | |
| ttl: 600, | |
| data: [192, 168, 87, 1] | |
| }); | |
| break; | |
| default: | |
| } | |
| } | |
| reply.dns = { | |
| id: packet.dns.id, | |
| flags: flags, | |
| questions: packet.dns.questions, | |
| answers: answers | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| return true; | |
| } | |
| function handle_fake_dns_doh(packet, adapter) | |
| { | |
| const fetch_url = `https://${adapter.doh_server || DEFAULT_DOH_SERVER}/dns-query`; | |
| const fetch_opts = { | |
| method: "POST", | |
| headers: [["content-type", "application/dns-message"]], | |
| body: packet.udp.data | |
| }; | |
| fetch(fetch_url, fetch_opts).then(async (resp) => { | |
| const reply = { | |
| eth: { | |
| ethertype: ETHERTYPE_IPV4, | |
| src: adapter.router_mac, | |
| dest: packet.eth.src | |
| }, | |
| ipv4: { | |
| proto: IPV4_PROTO_UDP, | |
| src: adapter.router_ip, | |
| dest: packet.ipv4.src | |
| }, | |
| udp: { | |
| sport: 53, | |
| dport: packet.udp.sport, | |
| data: new Uint8Array(await resp.arrayBuffer()) | |
| } | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| }); | |
| return true; | |
| } | |
| function handle_fake_dns(packet, adapter) | |
| { | |
| if(adapter.dns_method === "static") { | |
| return handle_fake_dns_static(packet, adapter); | |
| } | |
| else { | |
| return handle_fake_dns_doh(packet, adapter); | |
| } | |
| } | |
| function handle_fake_ntp(packet, adapter) { | |
| let now = Date.now(); // - 1000 * 60 * 60 * 24 * 7; | |
| let now_n = now + NTP_EPOC_DIFF; | |
| let now_n_f = TWO_TO_32 * ((now_n % 1000) / 1000); | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_UDP, | |
| src: packet.ipv4.dest, | |
| dest: packet.ipv4.src, | |
| }; | |
| reply.udp = { sport: 123, dport: packet.udp.sport }; | |
| let flags = (0 << 6) | (4 << 3) | 4; | |
| reply.ntp = Object.assign({}, packet.ntp); | |
| reply.ntp.flags = flags; | |
| reply.ntp.poll = 10; | |
| reply.ntp.ori_ts_i = packet.ntp.trans_ts_i; | |
| reply.ntp.ori_ts_f = packet.ntp.trans_ts_f; | |
| reply.ntp.rec_ts_i = now_n / 1000; | |
| reply.ntp.rec_ts_f = now_n_f; | |
| reply.ntp.trans_ts_i = now_n / 1000; | |
| reply.ntp.trans_ts_f = now_n_f; | |
| reply.ntp.stratum = 2; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| return true; | |
| } | |
| function handle_fake_dhcp(packet, adapter) { | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_UDP, | |
| src: adapter.router_ip, | |
| dest: adapter.vm_ip, | |
| }; | |
| reply.udp = { sport: 67, dport: 68, }; | |
| reply.dhcp = { | |
| htype: 1, | |
| hlen: 6, | |
| hops: 0, | |
| xid: packet.dhcp.xid, | |
| secs: 0, | |
| flags: 0, | |
| ciaddr: 0, | |
| yiaddr: iptolong(adapter.vm_ip), | |
| siaddr: iptolong(adapter.router_ip), | |
| giaddr: iptolong(adapter.router_ip), | |
| chaddr: packet.dhcp.chaddr, | |
| }; | |
| let options = []; | |
| // idk, it seems like op should be 3, but udhcpc sends 1 | |
| let fix = packet.dhcp.options.find(function(x) { return x[0] === 53; }); | |
| if( fix && fix[2] === 3 ) packet.dhcp.op = 3; | |
| if(packet.dhcp.op === 1) { | |
| reply.dhcp.op = 2; | |
| options.push(new Uint8Array([53, 1, 2])); | |
| } | |
| if(packet.dhcp.op === 3) { | |
| reply.dhcp.op = 2; | |
| options.push(new Uint8Array([53, 1, 5])); | |
| options.push(new Uint8Array([51, 4, 8, 0, 0, 0])); // Lease Time | |
| } | |
| let router_ip = [adapter.router_ip[0], adapter.router_ip[1], adapter.router_ip[2], adapter.router_ip[3]]; | |
| options.push(new Uint8Array([1, 4, 255, 255, 255, 0])); // Netmask | |
| if(adapter.masquerade) { | |
| options.push(new Uint8Array([3, 4].concat(router_ip))); // Router | |
| options.push(new Uint8Array([6, 4].concat(router_ip))); // DNS | |
| } | |
| options.push(new Uint8Array([54, 4].concat(router_ip))); // DHCP Server | |
| options.push(new Uint8Array([60, 3].concat(V86_ASCII))); // Vendor | |
| options.push(new Uint8Array([255, 0])); | |
| reply.dhcp.options = options; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| } | |
| export function handle_fake_networking(data, adapter) { | |
| let packet = {}; | |
| parse_eth(data, packet); | |
| if(packet.ipv4) { | |
| if(packet.tcp) { | |
| handle_fake_tcp(packet, adapter); | |
| } | |
| else if(packet.udp) { | |
| if(packet.dns) { | |
| handle_fake_dns(packet, adapter); | |
| } | |
| else if(packet.dhcp) { | |
| handle_fake_dhcp(packet, adapter); | |
| } | |
| else if(packet.ntp) { | |
| handle_fake_ntp(packet, adapter); | |
| } | |
| else if(packet.udp.dport === 8) { | |
| handle_udp_echo(packet, adapter); | |
| } | |
| } | |
| else if(packet.icmp && packet.icmp.type === 8) { | |
| handle_fake_ping(packet, adapter); | |
| } | |
| } | |
| else if(packet.arp && packet.arp.oper === 1 && packet.arp.ptype === ETHERTYPE_IPV4) { | |
| arp_whohas(packet, adapter); | |
| } | |
| } | |
| function parse_eth(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let ethertype = view.getUint16(12); | |
| let eth = { | |
| ethertype: ethertype, | |
| dest: data.subarray(0, 6), | |
| dest_s: a2ethaddr(data.subarray(0, 6)), | |
| src: data.subarray(6, 12), | |
| src_s: a2ethaddr(data.subarray(6, 12)), | |
| }; | |
| o.eth = eth; | |
| // TODO: Remove CRC from the end of the packet maybe? | |
| let payload = data.subarray(ETH_HEADER_SIZE, data.length); | |
| if(ethertype === ETHERTYPE_IPV4) { | |
| parse_ipv4(payload, o); | |
| } | |
| else if(ethertype === ETHERTYPE_ARP) { | |
| parse_arp(payload, o); | |
| } | |
| else if(ethertype === ETHERTYPE_IPV6) { | |
| dbg_log("Unimplemented: ipv6"); | |
| } | |
| else { | |
| dbg_log("Unknown ethertype: " + h(ethertype), LOG_FETCH); | |
| } | |
| } | |
| function write_eth(spec, out) { | |
| const view = out.eth_frame_view; | |
| view_set_array(0, spec.eth.dest, view, out); | |
| view_set_array(6, spec.eth.src, view, out); | |
| view.setUint16(12, spec.eth.ethertype); | |
| let len = ETH_HEADER_SIZE; | |
| if(spec.arp) { | |
| len += write_arp(spec, out); | |
| } | |
| else if(spec.ipv4) { | |
| len += write_ipv4(spec, out); | |
| } | |
| return len; | |
| } | |
| function parse_arp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let hlen = data[4]; | |
| let plen = data[5]; | |
| let arp = { | |
| htype: view.getUint16(0), | |
| ptype: view.getUint16(2), | |
| oper: view.getUint16(6), | |
| sha: data.subarray(8, 14), | |
| spa: data.subarray(14, 18), | |
| tha: data.subarray(18, 24), | |
| tpa: data.subarray(24, 28), | |
| }; | |
| o.arp = arp; | |
| } | |
| function write_arp(spec, out) { | |
| const view = out.eth_payload_view; | |
| view.setUint16(0, spec.arp.htype); | |
| view.setUint16(2, spec.arp.ptype); | |
| view.setUint8(4, spec.arp.sha.length); | |
| view.setUint8(5, spec.arp.spa.length); | |
| view.setUint16(6, spec.arp.oper); | |
| view_set_array(8, spec.arp.sha, view, out); | |
| view_set_array(14, spec.arp.spa, view, out); | |
| view_set_array(18, spec.arp.tha, view, out); | |
| view_set_array(24, spec.arp.tpa, view, out); | |
| return 28; | |
| } | |
| function parse_ipv4(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let version = (data[0] >> 4) & 0x0F; | |
| let ihl = data[0] & 0x0F; | |
| let tos = view.getUint8(1); | |
| let len = view.getUint16(2); | |
| let ttl = view.getUint8(8); | |
| let proto = view.getUint8(9); | |
| let ip_checksum = view.getUint16(10); | |
| let ipv4 = { | |
| version, | |
| ihl, | |
| tos, | |
| len, | |
| ttl, | |
| proto, | |
| ip_checksum, | |
| src: data.subarray(12, 12+4), | |
| dest: data.subarray(16, 16+4), | |
| }; | |
| // Ethernet minmum packet size. | |
| if(Math.max(len, 46) !== data.length) { | |
| dbg_log(`ipv4 Length mismatch: ${len} != ${data.length}`, LOG_FETCH); | |
| } | |
| o.ipv4 = ipv4; | |
| let ipdata = data.subarray(ihl * 4, len); | |
| if(proto === IPV4_PROTO_ICMP) { | |
| parse_icmp(ipdata, o); | |
| } | |
| else if(proto === IPV4_PROTO_TCP) { | |
| parse_tcp(ipdata, o); | |
| } | |
| else if(proto === IPV4_PROTO_UDP) { | |
| parse_udp(ipdata, o); | |
| } | |
| } | |
| function write_ipv4(spec, out) { | |
| const view = out.eth_payload_view; | |
| const ihl = IPV4_HEADER_SIZE >> 2; // header length in 32-bit words | |
| const version = 4; | |
| let len = IPV4_HEADER_SIZE; | |
| if(spec.icmp) { | |
| len += write_icmp(spec, out); | |
| } | |
| else if(spec.udp) { | |
| len += write_udp(spec, out); | |
| } | |
| else if(spec.tcp) { | |
| len += write_tcp(spec, out); | |
| } | |
| view.setUint8(0, version << 4 | (ihl & 0x0F)); | |
| view.setUint8(1, spec.ipv4.tos || 0); | |
| view.setUint16(2, len); | |
| view.setUint16(4, spec.ipv4.id || 0); | |
| view.setUint8(6, 2 << 5); // DF Flag | |
| view.setUint8(8, spec.ipv4.ttl || 32); | |
| view.setUint8(9, spec.ipv4.proto); | |
| view.setUint16(10, 0); // checksum initially zero before calculation | |
| view_set_array(12, spec.ipv4.src, view, out); | |
| view_set_array(16, spec.ipv4.dest, view, out); | |
| view.setUint16(10, calc_inet_checksum(IPV4_HEADER_SIZE, 0, view, out)); | |
| return len; | |
| } | |
| function parse_icmp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let icmp = { | |
| type: view.getUint8(0), | |
| code: view.getUint8(1), | |
| checksum: view.getUint16(2), | |
| data: data.subarray(4) | |
| }; | |
| o.icmp = icmp; | |
| } | |
| function write_icmp(spec, out) { | |
| const view = out.ipv4_payload_view; | |
| view.setUint8(0, spec.icmp.type); | |
| view.setUint8(1, spec.icmp.code); | |
| view.setUint16(2, 0); // checksum initially zero before calculation | |
| const data_length = view_set_array(ICMP_HEADER_SIZE, spec.icmp.data, view, out); | |
| const total_length = ICMP_HEADER_SIZE + data_length; | |
| view.setUint16(2, calc_inet_checksum(total_length, 0, view, out)); | |
| return total_length; | |
| } | |
| function parse_udp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let udp = { | |
| sport: view.getUint16(0), | |
| dport: view.getUint16(2), | |
| len: view.getUint16(4), | |
| checksum: view.getUint16(6), | |
| data: data.subarray(8), | |
| data_s: new TextDecoder().decode(data.subarray(8)) | |
| }; | |
| //dbg_assert(udp.data.length + 8 == udp.len); | |
| if(udp.dport === 67 || udp.sport === 67) { //DHCP | |
| parse_dhcp(data.subarray(8), o); | |
| } | |
| else if(udp.dport === 53 || udp.sport === 53) { | |
| parse_dns(data.subarray(8), o); | |
| } | |
| else if(udp.dport === 123) { | |
| parse_ntp(data.subarray(8), o); | |
| } | |
| o.udp = udp; | |
| } | |
| function write_udp(spec, out) { | |
| const view = out.ipv4_payload_view; | |
| let total_length = UDP_HEADER_SIZE; | |
| if(spec.dhcp) { | |
| total_length += write_dhcp(spec, out); | |
| } | |
| else if(spec.dns) { | |
| total_length += write_dns(spec, out); | |
| } | |
| else if(spec.ntp) { | |
| total_length += write_ntp(spec, out); | |
| } | |
| else { | |
| total_length += view_set_array(0, spec.udp.data, out.udp_payload_view, out); | |
| } | |
| view.setUint16(0, spec.udp.sport); | |
| view.setUint16(2, spec.udp.dport); | |
| view.setUint16(4, total_length); | |
| view.setUint16(6, 0); // checksum initially zero before calculation | |
| const pseudo_header = | |
| (spec.ipv4.src[0] << 8 | spec.ipv4.src[1]) + | |
| (spec.ipv4.src[2] << 8 | spec.ipv4.src[3]) + | |
| (spec.ipv4.dest[0] << 8 | spec.ipv4.dest[1]) + | |
| (spec.ipv4.dest[2] << 8 | spec.ipv4.dest[3]) + | |
| IPV4_PROTO_UDP + | |
| total_length; | |
| view.setUint16(6, calc_inet_checksum(total_length, pseudo_header, view, out)); | |
| return total_length; | |
| } | |
| function parse_dns(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let dns = { | |
| id: view.getUint16(0), | |
| flags: view.getUint16(2), | |
| questions: [], | |
| answers: [] | |
| }; | |
| let qdcount = view.getUint16(4); | |
| let ancount = view.getUint16(6); | |
| let nscount = view.getUint16(8); | |
| let arcount = view.getUint16(10); | |
| let offset = 12; | |
| function read_dstr() { | |
| let o = []; | |
| let len; | |
| do { | |
| len = view.getUint8(offset); | |
| o.push(new TextDecoder().decode(data.subarray(offset+1, offset+1+len))); | |
| offset += len + 1; | |
| } while(len > 0); | |
| return o; | |
| } | |
| for(let i = 0; i < qdcount; i++) { | |
| dns.questions.push({ | |
| name: read_dstr(), | |
| type: view.getInt16(offset), | |
| class: view.getInt16(offset + 2) | |
| }); | |
| offset += 4; | |
| } | |
| for(let i = 0; i < ancount; i++) { | |
| let ans = { | |
| name: read_dstr(), | |
| type: view.getInt16(offset), | |
| class: view.getUint16(offset + 2), | |
| ttl: view.getUint32(offset + 4) | |
| }; | |
| offset += 8; | |
| let rdlen = view.getUint16(offset); | |
| offset += 2; | |
| ans.data = data.subarray(offset, offset+rdlen); | |
| offset += rdlen; | |
| dns.answers.push(ans); | |
| } | |
| o.dns = dns; | |
| } | |
| function write_dns(spec, out) { | |
| const view = out.udp_payload_view; | |
| view.setUint16(0, spec.dns.id); | |
| view.setUint16(2, spec.dns.flags); | |
| view.setUint16(4, spec.dns.questions.length); | |
| view.setUint16(6, spec.dns.answers.length); | |
| let offset = 12; | |
| for(let i = 0; i < spec.dns.questions.length; ++i) { | |
| let q = spec.dns.questions[i]; | |
| for(let s of q.name) { | |
| const n_written = view_set_string(offset + 1, s, view, out); | |
| view.setUint8(offset, n_written); | |
| offset += 1 + n_written; | |
| } | |
| view.setUint16(offset, q.type); | |
| offset += 2; | |
| view.setUint16(offset, q.class); | |
| offset += 2; | |
| } | |
| function write_reply(a) { | |
| for(let s of a.name) { | |
| const n_written = view_set_string(offset + 1, s, view, out); | |
| view.setUint8(offset, n_written); | |
| offset += 1 + n_written; | |
| } | |
| view.setUint16(offset, a.type); | |
| offset += 2; | |
| view.setUint16(offset, a.class); | |
| offset += 2; | |
| view.setUint32(offset, a.ttl); | |
| offset += 4; | |
| view.setUint16(offset, a.data.length); | |
| offset += 2; | |
| offset += view_set_array(offset, a.data, view, out); | |
| } | |
| for(let i = 0; i < spec.dns.answers.length; ++i) { | |
| let a = spec.dns.answers[i]; | |
| write_reply(a); | |
| } | |
| return offset; | |
| } | |
| function parse_dhcp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let bootpo = data.subarray(44,44+192); | |
| let dhcp = { | |
| op: view.getUint8(0), | |
| htype: view.getUint8(1), | |
| hlen: view.getUint8(2), | |
| hops: view.getUint8(3), | |
| xid: view.getUint32(4), | |
| secs: view.getUint16(8), | |
| flags: view.getUint16(10), | |
| ciaddr: view.getUint32(12), | |
| yiaddr: view.getUint32(16), | |
| siaddr: view.getUint32(20), | |
| giaddr: view.getUint32(24), | |
| chaddr: data.subarray(28,28+16), | |
| magic: view.getUint32(236), | |
| options: [], | |
| }; | |
| let options = data.subarray(240); | |
| for(let i = 0; i < options.length; ++i) { | |
| let start = i; | |
| let op = options[i]; | |
| if(op === 0) continue; | |
| ++i; | |
| let len = options[i]; | |
| i += len; | |
| dhcp.options.push(options.subarray(start, start + len + 2)); | |
| } | |
| o.dhcp = dhcp; | |
| o.dhcp_options = dhcp.options; | |
| } | |
| function write_dhcp(spec, out) { | |
| const view = out.udp_payload_view; | |
| view.setUint8(0, spec.dhcp.op); | |
| view.setUint8(1, spec.dhcp.htype); | |
| view.setUint8(2, spec.dhcp.hlen); | |
| view.setUint8(3, spec.dhcp.hops); | |
| view.setUint32(4, spec.dhcp.xid); | |
| view.setUint16(8, spec.dhcp.secs); | |
| view.setUint16(10, spec.dhcp.flags); | |
| view.setUint32(12, spec.dhcp.ciaddr); | |
| view.setUint32(16, spec.dhcp.yiaddr); | |
| view.setUint32(20, spec.dhcp.siaddr); | |
| view.setUint32(24, spec.dhcp.giaddr); | |
| view_set_array(28, spec.dhcp.chaddr, view, out); | |
| view.setUint32(236, DHCP_MAGIC_COOKIE); | |
| let offset = 240; | |
| for(let o of spec.dhcp.options) { | |
| offset += view_set_array(offset, o, view, out); | |
| } | |
| return offset; | |
| } | |
| function parse_ntp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| o.ntp = { | |
| flags: view.getUint8(0), | |
| stratum: view.getUint8(1), | |
| poll: view.getUint8(2), | |
| precision: view.getUint8(3), | |
| root_delay: view.getUint32(4), | |
| root_disp: view.getUint32(8), | |
| ref_id: view.getUint32(12), | |
| ref_ts_i: view.getUint32(16), | |
| ref_ts_f: view.getUint32(20), | |
| ori_ts_i: view.getUint32(24), | |
| ori_ts_f: view.getUint32(28), | |
| rec_ts_i: view.getUint32(32), | |
| rec_ts_f: view.getUint32(36), | |
| trans_ts_i: view.getUint32(40), | |
| trans_ts_f: view.getUint32(44), | |
| }; | |
| } | |
| function write_ntp(spec, out) { | |
| const view = out.udp_payload_view; | |
| view.setUint8(0, spec.ntp.flags); | |
| view.setUint8(1, spec.ntp.stratum); | |
| view.setUint8(2, spec.ntp.poll); | |
| view.setUint8(3, spec.ntp.precision); | |
| view.setUint32(4, spec.ntp.root_delay); | |
| view.setUint32(8, spec.ntp.root_disp); | |
| view.setUint32(12, spec.ntp.ref_id); | |
| view.setUint32(16, spec.ntp.ref_ts_i); | |
| view.setUint32(20, spec.ntp.ref_ts_f); | |
| view.setUint32(24, spec.ntp.ori_ts_i); | |
| view.setUint32(28, spec.ntp.ori_ts_f); | |
| view.setUint32(32, spec.ntp.rec_ts_i); | |
| view.setUint32(36, spec.ntp.rec_ts_f); | |
| view.setUint32(40, spec.ntp.trans_ts_i); | |
| view.setUint32(44, spec.ntp.trans_ts_f); | |
| return 48; | |
| } | |
| function parse_tcp(data, o) { | |
| let view = new DataView(data.buffer, data.byteOffset, data.byteLength); | |
| let tcp = { | |
| sport: view.getUint16(0), | |
| dport: view.getUint16(2), | |
| seq: view.getUint32(4), | |
| ackn: view.getUint32(8), | |
| doff: view.getUint8(12) >> 4, | |
| winsize: view.getUint16(14), | |
| checksum: view.getUint16(16), | |
| urgent: view.getUint16(18), | |
| }; | |
| let flags = view.getUint8(13); | |
| tcp.fin = !!(flags & 0x01); | |
| tcp.syn = !!(flags & 0x02); | |
| tcp.rst = !!(flags & 0x04); | |
| tcp.psh = !!(flags & 0x08); | |
| tcp.ack = !!(flags & 0x10); | |
| tcp.urg = !!(flags & 0x20); | |
| tcp.ece = !!(flags & 0x40); | |
| tcp.cwr = !!(flags & 0x80); | |
| o.tcp = tcp; | |
| let offset = tcp.doff * 4; | |
| o.tcp_data = data.subarray(offset); | |
| } | |
| function write_tcp(spec, out) { | |
| const view = out.ipv4_payload_view; | |
| let flags = 0; | |
| let tcp = spec.tcp; | |
| if(tcp.fin) flags |= 0x01; | |
| if(tcp.syn) flags |= 0x02; | |
| if(tcp.rst) flags |= 0x04; | |
| if(tcp.psh) flags |= 0x08; | |
| if(tcp.ack) flags |= 0x10; | |
| if(tcp.urg) flags |= 0x20; | |
| if(tcp.ece) flags |= 0x40; | |
| if(tcp.cwr) flags |= 0x80; | |
| const doff = TCP_HEADER_SIZE >> 2; // header length in 32-bit words | |
| 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(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); | |
| } | |
| const pseudo_header = | |
| (spec.ipv4.src[0] << 8 | spec.ipv4.src[1]) + | |
| (spec.ipv4.src[2] << 8 | spec.ipv4.src[3]) + | |
| (spec.ipv4.dest[0] << 8 | spec.ipv4.dest[1]) + | |
| (spec.ipv4.dest[2] << 8 | spec.ipv4.dest[3]) + | |
| IPV4_PROTO_TCP + | |
| total_length; | |
| view.setUint16(16, calc_inet_checksum(total_length, pseudo_header, view, out)); | |
| return total_length; | |
| } | |
| export function fake_tcp_connect(dport, adapter) | |
| { | |
| const vm_ip_str = adapter.vm_ip.join("."); | |
| const router_ip_str = adapter.router_ip.join("."); | |
| const sport_0 = (Math.random() * TCP_DYNAMIC_PORT_RANGE) | 0; | |
| let sport, tuple, sport_i = 0; | |
| do { | |
| sport = TCP_DYNAMIC_PORT_START + ((sport_0 + sport_i) % TCP_DYNAMIC_PORT_RANGE); | |
| tuple = `${vm_ip_str}:${dport}:${router_ip_str}:${sport}`; | |
| } while(++sport_i < TCP_DYNAMIC_PORT_RANGE && adapter.tcp_conn[tuple]); | |
| if(adapter.tcp_conn[tuple]) { | |
| throw new Error("pool of dynamic TCP port numbers exhausted, connection aborted"); | |
| } | |
| let conn = new TCPConnection(); | |
| conn.tuple = tuple; | |
| conn.hsrc = adapter.router_mac; | |
| conn.psrc = adapter.router_ip; | |
| conn.sport = sport; | |
| 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; | |
| } | |
| export function fake_tcp_probe(dport, adapter) { | |
| return new Promise((res, rej) => { | |
| let handle = fake_tcp_connect(dport, adapter); | |
| handle.state = TCP_STATE_SYN_PROBE; | |
| handle.on("probe", res); | |
| }); | |
| } | |
| /** | |
| * @constructor | |
| */ | |
| export function TCPConnection() | |
| { | |
| this.state = TCP_STATE_CLOSED; | |
| this.net = null; // 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; | |
| this.delayed_send_fin = false; | |
| this.delayed_state = undefined; | |
| this.events_handlers = {}; | |
| } | |
| TCPConnection.prototype.on = function(event, handler) { | |
| this.events_handlers[event] = handler; | |
| }; | |
| TCPConnection.prototype.emit = function(event, ...args) { | |
| if(!this.events_handlers[event]) return; | |
| this.events_handlers[event].apply(this, args); | |
| }; | |
| TCPConnection.prototype.ipv4_reply = function() { | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: this.hsrc, dest: this.hdest }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_TCP, | |
| src: this.psrc, | |
| dest: this.pdest | |
| }; | |
| reply.tcp = { | |
| sport: this.sport, | |
| dport: this.dport, | |
| winsize: this.winsize, | |
| ackn: this.ack, | |
| seq: this.seq, | |
| ack: true | |
| }; | |
| return reply; | |
| }; | |
| TCPConnection.prototype.packet_reply = function(packet, tcp_options) { | |
| const reply_tcp = { | |
| sport: packet.tcp.dport, | |
| dport: packet.tcp.sport, | |
| winsize: packet.tcp.winsize, | |
| ackn: this.ack, | |
| seq: this.seq | |
| }; | |
| if(tcp_options) { | |
| for(const opt in tcp_options) { | |
| reply_tcp[opt] = tcp_options[opt]; | |
| } | |
| } | |
| const reply = this.ipv4_reply(); | |
| reply.tcp = reply_tcp; | |
| return reply; | |
| }; | |
| TCPConnection.prototype.connect = function() { | |
| // dbg_log(`TCP[${this.tuple}]: connect(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_SYN_SENT}"`, LOG_FETCH); | |
| this.seq = 1338; | |
| this.ack = 1; | |
| this.start_seq = 0; | |
| this.winsize = 64240; | |
| this.state = TCP_STATE_SYN_SENT; | |
| let reply = this.ipv4_reply(); | |
| reply.ipv4.id = 2345; | |
| reply.tcp = { | |
| sport: this.sport, | |
| dport: this.dport, | |
| seq: 1337, | |
| ackn: 0, | |
| winsize: 0, | |
| syn: true, | |
| }; | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| }; | |
| TCPConnection.prototype.accept = function(packet) { | |
| this.seq = 1338; | |
| this.ack = packet.tcp.seq + 1; | |
| this.start_seq = packet.tcp.seq; | |
| this.hsrc = this.net.router_mac; | |
| this.psrc = packet.ipv4.dest; | |
| this.sport = packet.tcp.dport; | |
| this.hdest = packet.eth.src; | |
| this.dport = packet.tcp.sport; | |
| this.pdest = packet.ipv4.src; | |
| this.winsize = packet.tcp.winsize; | |
| let reply = this.ipv4_reply(); | |
| reply.tcp = { | |
| sport: this.sport, | |
| dport: this.dport, | |
| seq: 1337, | |
| ackn: this.ack, | |
| winsize: packet.tcp.winsize, | |
| syn: true, | |
| ack: true | |
| }; | |
| // dbg_log(`TCP[${this.tuple}]: accept(): sending SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH); | |
| this.state = TCP_STATE_ESTABLISHED; | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| }; | |
| TCPConnection.prototype.process = function(packet) { | |
| if(this.state === TCP_STATE_CLOSED) { | |
| // dbg_log(`TCP[${this.tuple}]: WARNING: connection already closed, packet dropped`, LOG_FETCH); | |
| const reply = this.packet_reply(packet, {rst: true}); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| return; | |
| } | |
| else if(packet.tcp.rst) { | |
| if(this.state === TCP_STATE_SYN_PROBE) { | |
| this.emit("probe", false); | |
| this.release(); | |
| return; | |
| } | |
| // dbg_log(`TCP[${this.tuple}]: received RST in state "${this.state}"`, LOG_FETCH); | |
| this.on_close(); | |
| this.release(); | |
| return; | |
| } | |
| else if(packet.tcp.syn) { | |
| if(this.state === TCP_STATE_SYN_SENT && packet.tcp.ack) { | |
| this.ack = packet.tcp.seq + 1; | |
| this.start_seq = packet.tcp.seq; | |
| this.last_received_ackn = packet.tcp.ackn; | |
| const reply = this.ipv4_reply(); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| // dbg_log(`TCP[${this.tuple}]: received SYN+ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH); | |
| this.state = TCP_STATE_ESTABLISHED; | |
| this.emit("connect"); | |
| } | |
| else if(this.state === TCP_STATE_SYN_PROBE && packet.tcp.ack) { | |
| this.emit("probe", true); | |
| const reply = this.packet_reply(packet, {rst: true}); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| this.release(); | |
| } | |
| else { | |
| dbg_log(`TCP[${this.tuple}]: WARNING: unexpected SYN packet dropped`, LOG_FETCH); | |
| } | |
| if(packet.tcp_data.length) { | |
| dbg_log(`TCP[${this.tuple}]: WARNING: ${packet.tcp_data.length} bytes of unexpected SYN packet payload dropped`, LOG_FETCH); | |
| } | |
| return; | |
| } | |
| if(packet.tcp.ack) { | |
| if(this.state === TCP_STATE_SYN_RECEIVED) { | |
| // dbg_log(`TCP[${this.tuple}]: received ACK in state "${this.state}", next "${TCP_STATE_ESTABLISHED}"`, LOG_FETCH); | |
| this.state = TCP_STATE_ESTABLISHED; | |
| } | |
| else if(this.state === TCP_STATE_FIN_WAIT_1) { | |
| if(!packet.tcp.fin) { // handle FIN+ACK in FIN_WAIT_1 separately further down below | |
| // dbg_log(`TCP[${this.tuple}]: received ACK in state "${this.state}", next "${TCP_STATE_FIN_WAIT_2}"`, LOG_FETCH); | |
| this.state = TCP_STATE_FIN_WAIT_2; | |
| } | |
| } | |
| else if(this.state === TCP_STATE_CLOSING || this.state === TCP_STATE_LAST_ACK) { | |
| // dbg_log(`TCP[${this.tuple}]: received ACK in state "${this.state}"`, LOG_FETCH); | |
| this.release(); | |
| return; | |
| } | |
| } | |
| if(this.last_received_ackn === undefined) { | |
| this.last_received_ackn = packet.tcp.ackn; | |
| } | |
| else { | |
| const n_ack = packet.tcp.ackn - this.last_received_ackn; | |
| //console.log("Read ", n_ack, "(", this.last_received_ackn, ") ", packet.tcp.ackn, packet.tcp.winsize) | |
| if(n_ack > 0) { | |
| this.last_received_ackn = packet.tcp.ackn; | |
| this.send_buffer.remove(n_ack); | |
| this.seq += n_ack; | |
| this.pending = false; | |
| if(this.delayed_send_fin && !this.send_buffer.length) { | |
| // dbg_log(`TCP[${this.tuple}]: sending delayed FIN from active close in state "${this.state}", next "${this.delayed_state}"`, LOG_FETCH); | |
| this.delayed_send_fin = false; | |
| this.state = this.delayed_state; | |
| const reply = this.ipv4_reply(); | |
| reply.tcp.fin = true; | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| return; | |
| } | |
| } | |
| else if(n_ack < 0) { // TODO: could this just be a 32-bit sequence number overflow? | |
| dbg_log(`TCP[${this.tuple}]: ERROR: ack underflow (pkt=${packet.tcp.ackn} last=${this.last_received_ackn}), resetting`, LOG_FETCH); | |
| const reply = this.packet_reply(packet, {rst: true}); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| this.on_close(); | |
| this.release(); | |
| return; | |
| } | |
| } | |
| if(packet.tcp.fin) { | |
| if(this.ack !== packet.tcp.seq) { | |
| dbg_log(`TCP[${this.tuple}]: WARNING: closing connection in state "${this.state}" with invalid seq (${this.ack} != ${packet.tcp.seq})`, LOG_FETCH); | |
| } | |
| ++this.ack; // FIN increases seqnr | |
| const reply = this.packet_reply(packet, {}); | |
| if(this.state === TCP_STATE_ESTABLISHED) { | |
| // dbg_log(`TCP[${this.tuple}]: received FIN in state "${this.state}, next "${TCP_STATE_CLOSE_WAIT}""`, LOG_FETCH); | |
| reply.tcp.ack = true; | |
| this.state = TCP_STATE_CLOSE_WAIT; | |
| this.on_shutdown(); | |
| } | |
| else if(this.state === TCP_STATE_FIN_WAIT_1) { | |
| if(packet.tcp.ack) { | |
| // dbg_log(`TCP[${this.tuple}]: received ACK+FIN in state "${this.state}"`, LOG_FETCH); | |
| this.release(); | |
| } | |
| else { | |
| // dbg_log(`TCP[${this.tuple}]: received ACK in state "${this.state}", next "${TCP_STATE_CLOSING}"`, LOG_FETCH); | |
| this.state = TCP_STATE_CLOSING; | |
| } | |
| reply.tcp.ack = true; | |
| } | |
| else if(this.state === TCP_STATE_FIN_WAIT_2) { | |
| // dbg_log(`TCP[${this.tuple}]: received FIN in state "${this.state}"`, LOG_FETCH); | |
| this.release(); | |
| reply.tcp.ack = true; | |
| } | |
| else { | |
| // dbg_log(`TCP[${this.tuple}]: ERROR: received FIN in unexpected TCP state "${this.state}", resetting`, LOG_FETCH); | |
| this.release(); | |
| this.on_close(); | |
| reply.tcp.rst = true; | |
| } | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| } | |
| else if(this.ack !== packet.tcp.seq) { | |
| // Handle TCP Keep-Alives silently. | |
| // Excerpt from RFC 9293, 3.8.4. TCP Keep-Alives: | |
| // To confirm that an idle connection is still active, these | |
| // implementations send a probe segment designed to elicit a response | |
| // from the TCP peer. Such a segment generally contains SEG.SEQ = | |
| // SND.NXT-1 and may or may not contain one garbage octet of data. | |
| if(this.ack !== packet.tcp.seq + 1) { | |
| dbg_log(`Packet seq was wrong ex: ${this.ack} ~${this.ack - this.start_seq} ` + | |
| `pk: ${packet.tcp.seq} ~${this.start_seq - packet.tcp.seq} ` + | |
| `(${this.ack - packet.tcp.seq}) = ${this.name}`, LOG_FETCH); | |
| } | |
| const reply = this.packet_reply(packet, {ack: true}); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| } | |
| else if(packet.tcp.ack && packet.tcp_data.length > 0) { | |
| this.ack += packet.tcp_data.length; | |
| const reply = this.ipv4_reply(); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| this.emit("data", packet.tcp_data); | |
| } | |
| this.pump(); | |
| }; | |
| /** | |
| * @param {Uint8Array} data | |
| */ | |
| TCPConnection.prototype.write = function(data) { | |
| if(!this.in_active_close) { | |
| this.send_buffer.write(data); | |
| } | |
| this.pump(); | |
| }; | |
| /** | |
| * @param {!Array<Uint8Array>} data_array | |
| */ | |
| TCPConnection.prototype.writev = function(data_array) { | |
| if(!this.in_active_close) { | |
| for(const data of data_array) { | |
| this.send_buffer.write(data); | |
| } | |
| } | |
| this.pump(); | |
| }; | |
| TCPConnection.prototype.close = function() { | |
| if(!this.in_active_close) { | |
| this.in_active_close = true; | |
| let next_state; | |
| if(this.state === TCP_STATE_ESTABLISHED || this.state === TCP_STATE_SYN_RECEIVED) { | |
| next_state = TCP_STATE_FIN_WAIT_1; | |
| } | |
| else if(this.state === TCP_STATE_CLOSE_WAIT) { | |
| next_state = TCP_STATE_LAST_ACK; | |
| } | |
| else { | |
| if(this.state !== TCP_STATE_SYN_SENT) { | |
| dbg_log(`TCP[${this.tuple}]: active close in unexpected state "${this.state}"`, LOG_FETCH); | |
| } | |
| this.release(); | |
| return; | |
| } | |
| if(this.send_buffer.length || this.pending) { | |
| // dbg_log(`TCP[${this.tuple}]: active close, delaying FIN in state "${this.state}", delayed next "${next_state}"`, LOG_FETCH); | |
| this.delayed_send_fin = true; | |
| this.delayed_state = next_state; | |
| } | |
| else { | |
| // dbg_log(`TCP[${this.tuple}]: active close, sending FIN in state "${this.state}", next "${next_state}"`, LOG_FETCH); | |
| this.state = next_state; | |
| const reply = this.ipv4_reply(); | |
| reply.tcp.fin = true; | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| } | |
| } | |
| this.pump(); | |
| }; | |
| TCPConnection.prototype.on_shutdown = function() { | |
| this.emit("shutdown"); | |
| // forward FIN event from guest device to network adapter | |
| }; | |
| TCPConnection.prototype.on_close = function() { | |
| this.emit("close"); | |
| // forward RST event from guest device to network adapter | |
| }; | |
| TCPConnection.prototype.release = function() { | |
| if(this.net.tcp_conn[this.tuple]) { | |
| // dbg_log(`TCP[${this.tuple}]: connection closed in state "${this.state}"`, LOG_FETCH); | |
| this.state = TCP_STATE_CLOSED; | |
| delete this.net.tcp_conn[this.tuple]; | |
| } | |
| }; | |
| TCPConnection.prototype.pump = function() { | |
| if(this.send_buffer.length && !this.pending) { | |
| const data = this.send_chunk_buf; | |
| const n_ready = this.send_buffer.peek(data); | |
| const reply = this.ipv4_reply(); | |
| reply.tcp.psh = true; | |
| reply.tcp_data = data.subarray(0, n_ready); | |
| this.net.receive(make_packet(this.net.eth_encoder_buf, reply)); | |
| this.pending = true; | |
| } | |
| }; | |
| function arp_whohas(packet, adapter) { | |
| let packet_subnet = iptolong(packet.arp.tpa) & 0xFFFFFF00; | |
| let router_subnet = iptolong(adapter.router_ip) & 0xFFFFFF00; | |
| if(!adapter.masquerade) { | |
| if(packet_subnet !== router_subnet) { | |
| return; | |
| } | |
| } | |
| if(packet_subnet === router_subnet) { | |
| // Ignore the DHCP client area | |
| if(packet.arp.tpa[3] > 99) return; | |
| } | |
| // Reply to ARP Whohas | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_ARP, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.arp = { | |
| htype: 1, | |
| ptype: ETHERTYPE_IPV4, | |
| oper: 2, | |
| sha: adapter.router_mac, | |
| spa: packet.arp.tpa, | |
| tha: packet.eth.src, | |
| tpa: packet.arp.spa | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| } | |
| function handle_fake_ping(packet, adapter) { | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_ICMP, | |
| src: packet.ipv4.dest, | |
| dest: packet.ipv4.src, | |
| }; | |
| reply.icmp = { | |
| type: 0, | |
| code: packet.icmp.code, | |
| data: packet.icmp.data | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| } | |
| function handle_udp_echo(packet, adapter) { | |
| // UDP Echo Server | |
| let reply = {}; | |
| reply.eth = { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src }; | |
| reply.ipv4 = { | |
| proto: IPV4_PROTO_UDP, | |
| src: packet.ipv4.dest, | |
| dest: packet.ipv4.src, | |
| }; | |
| reply.udp = { | |
| sport: packet.udp.dport, | |
| dport: packet.udp.sport, | |
| data: new TextEncoder().encode(packet.udp.data_s) | |
| }; | |
| adapter.receive(make_packet(adapter.eth_encoder_buf, reply)); | |
| } | |