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 MTU_DEFAULT = 1500 const ETH_TRAILER_SIZE = 4 const IPV4_HEADER_SIZE = 20 const IPV4_PAYLOAD_OFFSET = ETH_PAYLOAD_OFFSET + IPV4_HEADER_SIZE const UDP_HEADER_SIZE = 8 const UDP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_SIZE const TCP_HEADER_SIZE = 20 const TCP_PAYLOAD_OFFSET = IPV4_PAYLOAD_OFFSET + TCP_HEADER_SIZE const ICMP_HEADER_SIZE = 4 const DEFAULT_DOH_SERVER = 'cloudflare-dns.com' // Suppress unused variable warnings for protocol offset constants void TCP_PAYLOAD_OFFSET export interface EthEncoderBuf { eth_frame: Uint8Array eth_frame_view: DataView eth_payload_view: DataView ipv4_payload_view: DataView udp_payload_view: DataView text_encoder: TextEncoder } interface EthHeader { ethertype: number src: Uint8Array dest: Uint8Array dest_s?: string src_s?: string } interface ArpHeader { htype: number ptype: number oper: number sha: Uint8Array spa: Uint8Array tha: Uint8Array tpa: Uint8Array } interface Ipv4Header { version?: number ihl?: number tos?: number len?: number id?: number ttl?: number proto: number ip_checksum?: number src: Uint8Array dest: Uint8Array } interface IcmpHeader { type: number code: number checksum?: number data: Uint8Array } interface TcpHeader { sport: number dport: number seq: number ackn: number doff?: number winsize: number checksum?: number urgent?: number fin?: boolean syn?: boolean rst?: boolean psh?: boolean ack?: boolean urg?: boolean ece?: boolean cwr?: boolean options?: { mss?: number } } interface UdpHeader { sport: number dport: number len?: number checksum?: number data?: Uint8Array data_s?: string } interface DnsQuestion { name: string[] type: number class: number } interface DnsAnswer { name: string[] type: number class: number ttl: number data: Uint8Array | number[] } interface DnsHeader { id: number flags: number questions: DnsQuestion[] answers: DnsAnswer[] } interface DhcpHeader { op: number htype: number hlen: number hops: number xid: number secs: number flags: number ciaddr: number yiaddr: number siaddr: number giaddr: number chaddr: Uint8Array magic?: number options: Uint8Array[] } interface NtpHeader { flags: number stratum: number poll: number precision: number root_delay: number root_disp: number ref_id: number ref_ts_i: number ref_ts_f: number ori_ts_i: number ori_ts_f: number rec_ts_i: number rec_ts_f: number trans_ts_i: number trans_ts_f: number } export interface PacketSpec { eth: EthHeader arp?: ArpHeader ipv4?: Ipv4Header icmp?: IcmpHeader tcp?: TcpHeader tcp_data?: Uint8Array udp?: UdpHeader dns?: DnsHeader dhcp?: DhcpHeader ntp?: NtpHeader dhcp_options?: Uint8Array[] } export interface NetworkAdapterLike { bus: { pair?: { send(name: string, value: unknown): void } } router_mac: Uint8Array router_ip: Uint8Array vm_ip: Uint8Array vm_mac: Uint8Array masquerade: boolean dns_method: string doh_server?: string tcp_conn: Record mtu?: number eth_encoder_buf: EthEncoderBuf receive(data: Uint8Array): void on_tcp_connection?(conn: TCPConnection, packet: PacketSpec): boolean | void } function a2ethaddr(bytes: Uint8Array): string { 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: Uint8Array): number { return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3] } class GrowableRingbuffer { maximum_capacity: number tail: number head: number length: number buffer: Uint8Array constructor(initial_capacity: number, maximum_capacity: number) { 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) } write(src_array: Uint8Array): void { 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 } peek(dst_array: Uint8Array): number { 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 } remove(length: number): number { 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( mtu: number = MTU_DEFAULT, ): EthEncoderBuf { 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, 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(), } } /** * Copy given data array into view starting at offset, return number of bytes written. */ function view_set_array( offset: number, data: Uint8Array | number[], view: DataView, out: EthEncoderBuf, ): number { out.eth_frame.set(data, view.byteOffset + offset) return data.length } /** * Write zeros into the view starting at offset */ function view_set_zeros( offset: number, length: number, view: DataView, out: EthEncoderBuf, ): void { 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. */ function view_set_string( offset: number, str: string, view: DataView, out: EthEncoderBuf, ): number { 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). */ function calc_inet_checksum( length: number, checksum: number, view: DataView, out: EthEncoderBuf, ): number { 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 } function make_packet(out: EthEncoderBuf, spec: PacketSpec): Uint8Array { dbg_assert(!!spec.eth) out.eth_frame.fill(0) return out.eth_frame.subarray(0, write_eth(spec, out)) } function handle_fake_tcp( packet: PacketSpec, adapter: NetworkAdapterLike, ): boolean | undefined { const tuple = `${packet.ipv4!.src.join('.')}:${packet.tcp!.sport}:${packet.ipv4!.dest.join('.')}:${packet.tcp!.dport}` if (packet.tcp!.syn && !packet.tcp!.ack) { if (adapter.tcp_conn[tuple]) { dbg_log('SYN to already opened port', LOG_FETCH) delete adapter.tcp_conn[tuple] } const conn = new TCPConnection(adapter) conn.state = TCP_STATE_SYN_RECEIVED conn.tuple = tuple conn.last = packet conn.hsrc = packet.eth.dest conn.psrc = packet.ipv4!.dest conn.sport = packet.tcp!.dport conn.hdest = packet.eth.src conn.dport = packet.tcp!.sport conn.pdest = packet.ipv4!.src adapter.bus.pair?.send('tcp-connection', conn) if (adapter.on_tcp_connection) { adapter.on_tcp_connection(conn, packet) } if (adapter.tcp_conn[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 const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src, }, ipv4: { proto: IPV4_PROTO_TCP, src: packet.ipv4!.dest, dest: packet.ipv4!.src, }, 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) return undefined } function handle_fake_dns_static( packet: PacketSpec, adapter: NetworkAdapterLike, ): boolean { const reply: PacketSpec = { 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 }, } const answers: DnsAnswer[] = [] let flags = 0x8000 //Response, flags |= 0x0180 // Recursion // flags |= 0x0400; Authoritative for (let i = 0; i < packet.dns!.questions.length; ++i) { const 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: PacketSpec, adapter: NetworkAdapterLike, ): boolean { const fetch_url = `https://${adapter.doh_server || DEFAULT_DOH_SERVER}/dns-query` const udp_data = packet.udp!.data const fetch_opts: RequestInit = { method: 'POST', headers: [['content-type', 'application/dns-message']], body: udp_data ? udp_data.slice() : undefined, } fetch(fetch_url, fetch_opts).then(async (resp) => { const reply: PacketSpec = { 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: PacketSpec, adapter: NetworkAdapterLike, ): boolean { 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: PacketSpec, adapter: NetworkAdapterLike, ): boolean { const now = Date.now() // - 1000 * 60 * 60 * 24 * 7; const now_n = now + NTP_EPOC_DIFF const now_n_f = TWO_TO_32 * ((now_n % 1000) / 1000) const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src, }, ipv4: { proto: IPV4_PROTO_UDP, src: packet.ipv4!.dest, dest: packet.ipv4!.src, }, udp: { sport: 123, dport: packet.udp!.sport }, } const 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: PacketSpec, adapter: NetworkAdapterLike, ): void { const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src, }, ipv4: { proto: IPV4_PROTO_UDP, src: adapter.router_ip, dest: adapter.vm_ip, }, 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, op: 0, options: [], } const options: Uint8Array[] = [] // idk, it seems like op should be 3, but udhcpc sends 1 const fix = packet.dhcp!.options.find(function (x: Uint8Array) { 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 } const router_ip: number[] = [ 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: Uint8Array, adapter: NetworkAdapterLike, ): void { const packet: PacketSpec = { eth: { ethertype: 0, src: new Uint8Array(6), dest: new Uint8Array(6) }, } 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const ethertype = view.getUint16(12) const eth: EthHeader = { 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? const 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: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const arp: ArpHeader = { 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: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const version = (data[0] >> 4) & 0x0f const ihl = data[0] & 0x0f const tos = view.getUint8(1) const len = view.getUint16(2) const ttl = view.getUint8(8) const proto = view.getUint8(9) const ip_checksum = view.getUint16(10) const ipv4: Ipv4Header = { 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 const 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: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const icmp: IcmpHeader = { type: view.getUint8(0), code: view.getUint8(1), checksum: view.getUint16(2), data: data.subarray(4), } o.icmp = icmp } function write_icmp(spec: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const udp: UdpHeader = { 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: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const dns: DnsHeader = { id: view.getUint16(0), flags: view.getUint16(2), questions: [], answers: [], } const qdcount = view.getUint16(4) const ancount = view.getUint16(6) let offset = 12 function read_dstr(): string[] { const o: string[] = [] let len: number 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++) { const ans: DnsAnswer = { name: read_dstr(), type: view.getInt16(offset), class: view.getUint16(offset + 2), ttl: view.getUint32(offset + 4), data: new Uint8Array(0), } offset += 8 const 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: PacketSpec, out: EthEncoderBuf): number { 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) { const q = spec.dns!.questions[i] for (const 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: DnsAnswer): void { for (const 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) { const a = spec.dns!.answers[i] write_reply(a) } return offset } function parse_dhcp(data: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const dhcp: DhcpHeader = { 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: [], } const options = data.subarray(240) for (let i = 0; i < options.length; ++i) { const start = i const op = options[i] if (op === 0) continue ++i const 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: PacketSpec, out: EthEncoderBuf): number { 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 (const o of spec.dhcp!.options) { offset += view_set_array(offset, o, view, out) } return offset } function parse_ntp(data: Uint8Array, o: PacketSpec): void { const 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: PacketSpec, out: EthEncoderBuf): number { 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: Uint8Array, o: PacketSpec): void { const view = new DataView(data.buffer, data.byteOffset, data.byteLength) const tcp: TcpHeader = { 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), } const 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 const offset = tcp.doff! * 4 o.tcp_data = data.subarray(offset) } function write_tcp(spec: PacketSpec, out: EthEncoderBuf): number { const view = out.ipv4_payload_view let flags = 0 const 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 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, (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) 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: number, adapter: NetworkAdapterLike, ): TCPConnection { 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: number, tuple: string, 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', ) } const conn = new TCPConnection(adapter) 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 adapter.tcp_conn[tuple] = conn conn.connect() return conn } export function fake_tcp_probe( dport: number, adapter: NetworkAdapterLike, ): Promise { return new Promise((res, _rej) => { const handle = fake_tcp_connect(dport, adapter) handle.state = TCP_STATE_SYN_PROBE handle.on('probe', res) }) } export class TCPConnection { mtu: number state: string net: NetworkAdapterLike send_buffer: GrowableRingbuffer send_chunk_buf: Uint8Array in_active_close: boolean delayed_send_fin: boolean delayed_state: string | undefined events_handlers: Record void> tuple: string = '' last: PacketSpec | undefined = undefined hsrc: Uint8Array = new Uint8Array(6) psrc: Uint8Array = new Uint8Array(4) sport: number = 0 hdest: Uint8Array = new Uint8Array(6) dport: number = 0 pdest: Uint8Array = new Uint8Array(4) seq: number = 0 ack: number = 0 start_seq: number = 0 winsize: number = 0 last_received_ackn: number | undefined = undefined pending: boolean = false name: string = '' read: any = undefined stream_id: number = 0 on_close: () => void = () => { this.emit('close') } on_shutdown: () => void = () => { this.emit('shutdown') } constructor(adapter: NetworkAdapterLike) { 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 = adapter 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 = {} } on(event: string, handler: (...args: any[]) => void): void { this.events_handlers[event] = handler } emit(event: string, ...args: any[]): void { if (!this.events_handlers[event]) return this.events_handlers[event].apply(this, args) } ipv4_reply(): PacketSpec { const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: this.hsrc, dest: this.hdest, }, ipv4: { proto: IPV4_PROTO_TCP, src: this.psrc, dest: this.pdest, }, tcp: { sport: this.sport, dport: this.dport, winsize: this.winsize, ackn: this.ack, seq: this.seq, ack: true, }, } return reply } packet_reply( packet: PacketSpec, tcp_options: Partial, ): PacketSpec { const reply_tcp: TcpHeader = { sport: packet.tcp!.dport, dport: packet.tcp!.sport, winsize: packet.tcp!.winsize, ackn: this.ack, seq: this.seq, } for (const opt in tcp_options) { ;(reply_tcp as Record)[opt] = ( tcp_options as Record )[opt] } const reply = this.ipv4_reply() reply.tcp = reply_tcp return reply } connect(): void { // 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 if (this.state !== TCP_STATE_SYN_PROBE) { this.state = TCP_STATE_SYN_SENT } const 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)) } accept(packet?: PacketSpec): void { packet = packet || this.last! this.net.tcp_conn[this.tuple] = this this.seq = 1338 this.ack = packet.tcp!.seq + 1 this.start_seq = packet.tcp!.seq this.winsize = packet.tcp!.winsize const 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, 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 this.net.receive(make_packet(this.net.eth_encoder_buf, reply)) } process(packet: PacketSpec): void { this.last = 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() } write(data: Uint8Array): void { if (!this.in_active_close) { this.send_buffer.write(data) } this.pump() } writev(data_array: Uint8Array[]): void { if (!this.in_active_close) { for (const data of data_array) { this.send_buffer.write(data) } } this.pump() } close(): void { if (!this.in_active_close) { this.in_active_close = true let next_state: string 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() } release(): void { 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] } } pump(): void { 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: PacketSpec, adapter: NetworkAdapterLike): void { const packet_subnet = iptolong(packet.arp!.tpa) & 0xffffff00 const 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 const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_ARP, src: adapter.router_mac, dest: packet.eth.src, }, 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: PacketSpec, adapter: NetworkAdapterLike, ): void { const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src, }, ipv4: { proto: IPV4_PROTO_ICMP, src: packet.ipv4!.dest, dest: packet.ipv4!.src, }, 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: PacketSpec, adapter: NetworkAdapterLike, ): void { // UDP Echo Server const reply: PacketSpec = { eth: { ethertype: ETHERTYPE_IPV4, src: adapter.router_mac, dest: packet.eth.src, }, ipv4: { proto: IPV4_PROTO_UDP, src: packet.ipv4!.dest, dest: packet.ipv4!.src, }, 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)) }