NetworkStack.lua (19556B)
1 #!/usr/bin/env luajit 2 -- Network Stack Library 3 -- Implements ARP, IPv4, ICMP protocols on top of RTL8139 4 5 local NetworkStack = {} 6 7 -- IP configuration 8 NetworkStack.config = { 9 ip = {10, 0, 2, 15}, -- Our IP (QEMU user network default guest IP) 10 netmask = {255, 255, 255, 0}, 11 gateway = {10, 0, 2, 2}, -- QEMU user network gateway 12 dns = {10, 0, 2, 3} -- QEMU user network DNS 13 } 14 15 -- ARP cache 16 NetworkStack.arp_cache = {} 17 18 -- Pending ICMP requests 19 NetworkStack.icmp_requests = {} 20 21 -- TCP instance (loaded on demand) 22 NetworkStack.TCP = nil 23 24 ---Convert IP address array to string 25 ---@param ip table Array of 4 bytes 26 ---@return string ip "A.B.C.D" 27 function NetworkStack.ip_to_string(ip) 28 return string.format("%d.%d.%d.%d", ip[1], ip[2], ip[3], ip[4]) 29 end 30 31 ---Parse IP address string 32 ---@param str string "A.B.C.D" format 33 ---@return table|nil ip Array of 4 bytes 34 function NetworkStack.parse_ip(str) 35 local parts = {} 36 for part in string.gmatch(str, "[^%.]+") do 37 local num = tonumber(part) 38 if not num or num < 0 or num > 255 then 39 return nil 40 end 41 parts[#parts + 1] = num 42 end 43 if #parts ~= 4 then 44 return nil 45 end 46 return parts 47 end 48 49 ---Calculate IP checksum 50 ---@param data string Data to checksum 51 ---@return number checksum 16-bit checksum 52 function NetworkStack.ip_checksum(data) 53 local sum = 0 54 local i = 1 55 56 -- Sum 16-bit words 57 while i < #data do 58 local word = string.byte(data, i) * 256 + string.byte(data, i + 1) 59 sum = sum + word 60 i = i + 2 61 end 62 63 -- Add remaining byte if odd length 64 if i == #data then 65 sum = sum + string.byte(data, i) * 256 66 end 67 68 -- Fold 32-bit sum to 16 bits 69 while sum > 0xFFFF do 70 sum = (sum & 0xFFFF) + (sum >> 16) 71 end 72 73 return ~sum & 0xFFFF 74 end 75 76 ---Build ARP request packet 77 ---@param target_ip table Target IP address (4 bytes) 78 ---@param our_mac table Our MAC address (6 bytes) 79 ---@param our_ip table Our IP address (4 bytes) 80 ---@return string packet ARP request packet 81 function NetworkStack.build_arp_request(target_ip, our_mac, our_ip) 82 local arp = {} 83 84 -- Hardware type (Ethernet = 1) 85 arp[#arp + 1] = string.char(0x00, 0x01) 86 87 -- Protocol type (IPv4 = 0x0800) 88 arp[#arp + 1] = string.char(0x08, 0x00) 89 90 -- Hardware size (6 bytes for MAC) 91 arp[#arp + 1] = string.char(0x06) 92 93 -- Protocol size (4 bytes for IPv4) 94 arp[#arp + 1] = string.char(0x04) 95 96 -- Opcode (Request = 1) 97 arp[#arp + 1] = string.char(0x00, 0x01) 98 99 -- Sender MAC 100 for i = 1, 6 do 101 arp[#arp + 1] = string.char(our_mac[i]) 102 end 103 104 -- Sender IP 105 for i = 1, 4 do 106 arp[#arp + 1] = string.char(our_ip[i]) 107 end 108 109 -- Target MAC (unknown, all zeros) 110 arp[#arp + 1] = string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00) 111 112 -- Target IP 113 for i = 1, 4 do 114 arp[#arp + 1] = string.char(target_ip[i]) 115 end 116 117 return table.concat(arp) 118 end 119 120 ---Parse ARP packet 121 ---@param payload string ARP payload 122 ---@return table|nil arp Parsed ARP packet 123 function NetworkStack.parse_arp(payload) 124 if #payload < 28 then 125 return nil 126 end 127 128 local arp = {} 129 130 -- Hardware type 131 arp.hw_type = string.byte(payload, 1) * 256 + string.byte(payload, 2) 132 133 -- Protocol type 134 arp.proto_type = string.byte(payload, 3) * 256 + string.byte(payload, 4) 135 136 -- Opcode 137 arp.opcode = string.byte(payload, 7) * 256 + string.byte(payload, 8) 138 139 -- Sender MAC 140 arp.sender_mac = {} 141 for i = 1, 6 do 142 arp.sender_mac[i] = string.byte(payload, 8 + i) 143 end 144 145 -- Sender IP 146 arp.sender_ip = {} 147 for i = 1, 4 do 148 arp.sender_ip[i] = string.byte(payload, 14 + i) 149 end 150 151 -- Target MAC 152 arp.target_mac = {} 153 for i = 1, 6 do 154 arp.target_mac[i] = string.byte(payload, 18 + i) 155 end 156 157 -- Target IP 158 arp.target_ip = {} 159 for i = 1, 4 do 160 arp.target_ip[i] = string.byte(payload, 24 + i) 161 end 162 163 return arp 164 end 165 166 ---Build IPv4 packet 167 ---@param protocol number Protocol (1=ICMP, 6=TCP, 17=UDP) 168 ---@param src_ip table Source IP (4 bytes) 169 ---@param dst_ip table Destination IP (4 bytes) 170 ---@param payload string IP payload 171 ---@return string packet IPv4 packet 172 function NetworkStack.build_ipv4(protocol, src_ip, dst_ip, payload) 173 local ip = {} 174 175 -- Version (4) and IHL (5 = 20 bytes) 176 ip[#ip + 1] = string.char(0x45) 177 178 -- DSCP and ECN 179 ip[#ip + 1] = string.char(0x00) 180 181 -- Total length 182 local total_len = 20 + #payload 183 ip[#ip + 1] = string.char(math.floor(total_len / 256), total_len % 256) 184 185 -- Identification 186 ip[#ip + 1] = string.char(0x00, 0x01) 187 188 -- Flags and fragment offset 189 ip[#ip + 1] = string.char(0x00, 0x00) 190 191 -- TTL 192 ip[#ip + 1] = string.char(64) 193 194 -- Protocol 195 ip[#ip + 1] = string.char(protocol) 196 197 -- Checksum (will calculate later) 198 local checksum_pos = #ip + 1 199 ip[#ip + 1] = string.char(0x00, 0x00) 200 201 -- Source IP 202 for i = 1, 4 do 203 ip[#ip + 1] = string.char(src_ip[i]) 204 end 205 206 -- Destination IP 207 for i = 1, 4 do 208 ip[#ip + 1] = string.char(dst_ip[i]) 209 end 210 211 -- Calculate checksum 212 local header = table.concat(ip) 213 local checksum = NetworkStack.ip_checksum(header) 214 215 -- Insert checksum 216 ip[checksum_pos] = string.char(math.floor(checksum / 256), checksum % 256) 217 218 -- Rebuild header with correct checksum and add payload 219 return table.concat(ip) .. payload 220 end 221 222 ---Parse IPv4 packet 223 ---@param payload string IPv4 payload 224 ---@return table|nil ip Parsed IPv4 packet 225 function NetworkStack.parse_ipv4(payload) 226 if #payload < 20 then 227 return nil 228 end 229 230 local ip = {} 231 232 -- Version and IHL 233 local ver_ihl = string.byte(payload, 1) 234 ip.version = ver_ihl >> 4 235 ip.ihl = ver_ihl & 0x0F 236 237 if ip.version ~= 4 then 238 return nil 239 end 240 241 -- Header length in bytes 242 local header_len = ip.ihl * 4 243 244 -- Protocol 245 ip.protocol = string.byte(payload, 10) 246 247 -- Source IP 248 ip.src_ip = {} 249 for i = 1, 4 do 250 ip.src_ip[i] = string.byte(payload, 12 + i) 251 end 252 253 -- Destination IP 254 ip.dst_ip = {} 255 for i = 1, 4 do 256 ip.dst_ip[i] = string.byte(payload, 16 + i) 257 end 258 259 -- Payload 260 ip.payload = string.sub(payload, header_len + 1) 261 262 return ip 263 end 264 265 ---Build ICMP Echo Request 266 ---@param id number Identifier 267 ---@param seq number Sequence number 268 ---@param data string Payload data 269 ---@return string packet ICMP packet 270 function NetworkStack.build_icmp_echo(id, seq, data) 271 local icmp = {} 272 273 -- Type (8 = Echo Request) 274 icmp[#icmp + 1] = string.char(0x08) 275 276 -- Code (0) 277 icmp[#icmp + 1] = string.char(0x00) 278 279 -- Checksum (will calculate later) 280 local checksum_pos = #icmp + 1 281 icmp[#icmp + 1] = string.char(0x00, 0x00) 282 283 -- Identifier 284 icmp[#icmp + 1] = string.char(math.floor(id / 256), id % 256) 285 286 -- Sequence number 287 icmp[#icmp + 1] = string.char(math.floor(seq / 256), seq % 256) 288 289 -- Data 290 icmp[#icmp + 1] = data 291 292 -- Calculate checksum 293 local packet = table.concat(icmp) 294 local checksum = NetworkStack.ip_checksum(packet) 295 296 -- Insert checksum 297 icmp[checksum_pos] = string.char(math.floor(checksum / 256), checksum % 256) 298 299 return table.concat(icmp) 300 end 301 302 ---Parse ICMP packet 303 ---@param payload string ICMP payload 304 ---@return table|nil icmp Parsed ICMP packet 305 function NetworkStack.parse_icmp(payload) 306 if #payload < 8 then 307 return nil 308 end 309 310 local icmp = {} 311 312 -- Type 313 icmp.type = string.byte(payload, 1) 314 315 -- Code 316 icmp.code = string.byte(payload, 2) 317 318 -- Identifier 319 icmp.id = string.byte(payload, 5) * 256 + string.byte(payload, 6) 320 321 -- Sequence 322 icmp.seq = string.byte(payload, 7) * 256 + string.byte(payload, 8) 323 324 -- Data 325 icmp.data = string.sub(payload, 9) 326 327 return icmp 328 end 329 330 ---Send ARP request and wait for reply 331 ---@param RTL8139 table RTL8139 driver 332 ---@param target_ip table Target IP address 333 ---@param timeout number Timeout in seconds 334 ---@return table|nil mac Target MAC address 335 function NetworkStack.arp_resolve(RTL8139, target_ip, timeout) 336 local ip_str = NetworkStack.ip_to_string(target_ip) 337 338 -- Check cache first 339 if NetworkStack.arp_cache[ip_str] then 340 return NetworkStack.arp_cache[ip_str] 341 end 342 343 local our_mac = RTL8139.getMACAddress() 344 local arp_request = NetworkStack.build_arp_request(target_ip, our_mac, NetworkStack.config.ip) 345 346 -- Send ARP request (broadcast) 347 RTL8139.sendBroadcast(0x0806, arp_request) 348 349 -- Wait for reply 350 local start_time = os.time or function() return 0 end 351 local start = start_time() 352 353 while (start_time() - start) < timeout do 354 local packet = RTL8139.receive() 355 if packet then 356 local eth = RTL8139.parseEthernetHeader(packet) 357 if eth and eth.ethertype == 0x0806 then 358 local arp = NetworkStack.parse_arp(eth.payload) 359 if arp and arp.opcode == 2 then -- ARP Reply 360 -- Cache the result 361 local sender_ip_str = NetworkStack.ip_to_string(arp.sender_ip) 362 NetworkStack.arp_cache[sender_ip_str] = arp.sender_mac 363 364 -- Check if this is the reply we're waiting for 365 if sender_ip_str == ip_str then 366 return arp.sender_mac 367 end 368 end 369 end 370 end 371 end 372 373 return nil 374 end 375 376 ---Build UDP packet 377 ---@param src_port number Source port 378 ---@param dst_port number Destination port 379 ---@param payload string UDP payload 380 ---@return string packet UDP packet 381 function NetworkStack.build_udp(src_port, dst_port, payload) 382 local udp = {} 383 384 -- Source port 385 udp[#udp + 1] = string.char(math.floor(src_port / 256), src_port % 256) 386 387 -- Destination port 388 udp[#udp + 1] = string.char(math.floor(dst_port / 256), dst_port % 256) 389 390 -- Length (header + payload) 391 local length = 8 + #payload 392 udp[#udp + 1] = string.char(math.floor(length / 256), length % 256) 393 394 -- Checksum (optional for IPv4, set to 0) 395 udp[#udp + 1] = string.char(0x00, 0x00) 396 397 -- Payload 398 udp[#udp + 1] = payload 399 400 return table.concat(udp) 401 end 402 403 ---Parse UDP packet 404 ---@param payload string UDP payload 405 ---@return table|nil udp Parsed UDP packet 406 function NetworkStack.parse_udp(payload) 407 if #payload < 8 then 408 return nil 409 end 410 411 local udp = {} 412 413 -- Source port 414 udp.src_port = string.byte(payload, 1) * 256 + string.byte(payload, 2) 415 416 -- Destination port 417 udp.dst_port = string.byte(payload, 3) * 256 + string.byte(payload, 4) 418 419 -- Length 420 udp.length = string.byte(payload, 5) * 256 + string.byte(payload, 6) 421 422 -- Checksum 423 udp.checksum = string.byte(payload, 7) * 256 + string.byte(payload, 8) 424 425 -- Payload 426 udp.payload = string.sub(payload, 9) 427 428 return udp 429 end 430 431 ---Send UDP packet 432 ---@param dst_ip table Destination IP address 433 ---@param src_port number Source port 434 ---@param dst_port number Destination port 435 ---@param payload string Payload data 436 ---@return boolean success True if sent successfully 437 function NetworkStack.send_udp(dst_ip, src_port, dst_port, payload) 438 if not NetworkStack.RTL8139 then 439 return false 440 end 441 442 -- Build UDP packet 443 local udp_packet = NetworkStack.build_udp(src_port, dst_port, payload) 444 445 -- Build IP packet (protocol 17 = UDP) 446 local ip_packet = NetworkStack.build_ipv4(17, NetworkStack.config.ip, dst_ip, udp_packet) 447 448 -- Resolve MAC address 449 local dst_mac = NetworkStack.arp_resolve(NetworkStack.RTL8139, dst_ip, 5) 450 if not dst_mac then 451 return false 452 end 453 454 -- Build and send Ethernet frame 455 local our_mac = NetworkStack.RTL8139.getMACAddress() 456 local frame = NetworkStack.RTL8139.buildEthernetFrame(dst_mac, our_mac, 0x0800, ip_packet) 457 458 local result = NetworkStack.RTL8139.send(frame) 459 return result > 0 460 end 461 462 -- UDP socket tracking 463 NetworkStack.udp_sockets = {} 464 NetworkStack.next_ephemeral_port = 49152 -- Start of ephemeral port range 465 466 ---Bind a UDP socket to a port 467 ---@param port number Port to bind to 468 ---@param callback function Callback(src_ip, src_port, data) when packet received 469 ---@return number|nil socket_id Socket ID, or nil on error 470 function NetworkStack.udp_bind(port, callback) 471 if NetworkStack.udp_sockets[port] then 472 return nil -- Port already in use 473 end 474 475 NetworkStack.udp_sockets[port] = { 476 port = port, 477 callback = callback 478 } 479 480 return port 481 end 482 483 ---Unbind a UDP socket 484 ---@param port number Port to unbind 485 function NetworkStack.udp_unbind(port) 486 NetworkStack.udp_sockets[port] = nil 487 end 488 489 ---Get next available ephemeral port 490 ---@return number port Next ephemeral port 491 function NetworkStack.get_ephemeral_port() 492 local port = NetworkStack.next_ephemeral_port 493 NetworkStack.next_ephemeral_port = NetworkStack.next_ephemeral_port + 1 494 if NetworkStack.next_ephemeral_port > 65535 then 495 NetworkStack.next_ephemeral_port = 49152 496 end 497 return port 498 end 499 500 ---Initialize network stack with RTL8139 driver 501 ---@param RTL8139 table RTL8139 driver instance 502 function NetworkStack.init(RTL8139) 503 NetworkStack.RTL8139 = RTL8139 504 505 -- Register packet handler for IP packets 506 RTL8139.onReceive(function(packet, eth) 507 if eth.ethertype == 0x0800 then 508 -- IPv4 packet 509 local ip = NetworkStack.parse_ipv4(eth.payload) 510 if not ip then 511 return 512 end 513 514 -- Check if packet is for us 515 local is_for_us = true 516 for i = 1, 4 do 517 if ip.dst_ip[i] ~= NetworkStack.config.ip[i] then 518 is_for_us = false 519 break 520 end 521 end 522 523 if not is_for_us then 524 return -- Not for us 525 end 526 527 if ip.protocol == 1 then 528 -- ICMP packet 529 local icmp = NetworkStack.parse_icmp(ip.payload) 530 if icmp then 531 if icmp.type == 0 then 532 -- Echo Reply 533 local key = icmp.id .. "_" .. icmp.seq 534 if NetworkStack.icmp_requests[key] then 535 NetworkStack.icmp_requests[key].reply = { 536 src_ip = ip.src_ip, 537 icmp = icmp, 538 time = os.time and os.time() or 0 539 } 540 end 541 elseif icmp.type == 8 then 542 -- Echo Request - respond to ping 543 NetworkStack.handle_ping_request(ip.src_ip, icmp) 544 end 545 end 546 elseif ip.protocol == 6 then 547 -- TCP packet 548 if NetworkStack.TCP then 549 local tcp = NetworkStack.TCP.parse_packet(ip.payload) 550 if tcp then 551 NetworkStack.TCP.handle_packet(NetworkStack, ip.src_ip, ip.dst_ip, tcp) 552 end 553 end 554 elseif ip.protocol == 17 then 555 -- UDP packet 556 local udp = NetworkStack.parse_udp(ip.payload) 557 if udp then 558 -- Check if we have a socket bound to this port 559 local socket = NetworkStack.udp_sockets[udp.dst_port] 560 if socket and socket.callback then 561 socket.callback(ip.src_ip, udp.src_port, udp.payload) 562 end 563 end 564 end 565 end 566 end, {ethertype = 0x0800}) 567 end 568 569 ---Handle ping request (respond to ICMP echo) 570 ---@param src_ip table Source IP address 571 ---@param icmp table Parsed ICMP packet 572 function NetworkStack.handle_ping_request(src_ip, icmp) 573 if not NetworkStack.RTL8139 then 574 return 575 end 576 577 -- Build ICMP Echo Reply 578 local reply = {} 579 580 -- Type (0 = Echo Reply) 581 reply[#reply + 1] = string.char(0x00) 582 583 -- Code (0) 584 reply[#reply + 1] = string.char(0x00) 585 586 -- Checksum (will calculate later) 587 local checksum_pos = #reply + 1 588 reply[#reply + 1] = string.char(0x00, 0x00) 589 590 -- Identifier 591 reply[#reply + 1] = string.char(math.floor(icmp.id / 256), icmp.id % 256) 592 593 -- Sequence number 594 reply[#reply + 1] = string.char(math.floor(icmp.seq / 256), icmp.seq % 256) 595 596 -- Data (echo back the same data) 597 reply[#reply + 1] = icmp.data 598 599 -- Calculate checksum 600 local packet = table.concat(reply) 601 local checksum = NetworkStack.ip_checksum(packet) 602 603 -- Insert checksum 604 reply[checksum_pos] = string.char(math.floor(checksum / 256), checksum % 256) 605 606 -- Build IP packet 607 local icmp_packet = table.concat(reply) 608 local ip_packet = NetworkStack.build_ipv4(1, NetworkStack.config.ip, src_ip, icmp_packet) 609 610 -- Resolve MAC address 611 local dst_mac = NetworkStack.arp_resolve(NetworkStack.RTL8139, src_ip, 5) 612 if not dst_mac then 613 return 614 end 615 616 -- Send response 617 local our_mac = NetworkStack.RTL8139.getMACAddress() 618 local frame = NetworkStack.RTL8139.buildEthernetFrame(dst_mac, our_mac, 0x0800, ip_packet) 619 NetworkStack.RTL8139.send(frame) 620 end 621 622 ---Load TCP module 623 ---@return boolean success True if loaded successfully 624 function NetworkStack.load_tcp() 625 if NetworkStack.TCP then 626 return true -- Already loaded 627 end 628 629 -- Try to load TCP module 630 if CRamdiskOpen then 631 local tcp_handle = CRamdiskOpen("/os/libs/TCP.lua", "r") 632 if tcp_handle then 633 local tcp_code = CRamdiskRead(tcp_handle) 634 CRamdiskClose(tcp_handle) 635 636 if tcp_code then 637 local tcp_func, err = load(tcp_code, "/os/libs/TCP.lua", "t") 638 if tcp_func then 639 NetworkStack.TCP = tcp_func() 640 return true 641 end 642 end 643 end 644 end 645 646 return false 647 end 648 649 ---Send ping (ICMP Echo Request) 650 ---@param dst_ip table Destination IP address 651 ---@param timeout number Timeout in seconds 652 ---@return table|nil reply Reply data or nil if timeout 653 function NetworkStack.ping(dst_ip, timeout) 654 if not NetworkStack.RTL8139 then 655 return nil 656 end 657 658 local id = math.random(1, 65535) 659 local seq = 1 660 local data = "LuajitOS ping" 661 662 -- Build ICMP echo request 663 local icmp_packet = NetworkStack.build_icmp_echo(id, seq, data) 664 665 -- Build IP packet 666 local ip_packet = NetworkStack.build_ipv4(1, NetworkStack.config.ip, dst_ip, icmp_packet) 667 668 -- Resolve MAC address 669 local dst_mac = NetworkStack.arp_resolve(NetworkStack.RTL8139, dst_ip, 5) 670 if not dst_mac then 671 return nil 672 end 673 674 -- Register request 675 local key = id .. "_" .. seq 676 NetworkStack.icmp_requests[key] = { 677 sent_time = os.time and os.time() or 0, 678 reply = nil 679 } 680 681 -- Send packet 682 local our_mac = NetworkStack.RTL8139.getMACAddress() 683 local frame = NetworkStack.RTL8139.buildEthernetFrame(dst_mac, our_mac, 0x0800, ip_packet) 684 NetworkStack.RTL8139.send(frame) 685 686 -- Wait for reply 687 local start_time = os.time and os.time() or 0 688 while (os.time and os.time() or 0) - start_time < timeout do 689 NetworkStack.RTL8139.poll() 690 if NetworkStack.icmp_requests[key].reply then 691 local reply = NetworkStack.icmp_requests[key].reply 692 NetworkStack.icmp_requests[key] = nil 693 return reply 694 end 695 end 696 697 NetworkStack.icmp_requests[key] = nil 698 return nil 699 end 700 701 return NetworkStack