luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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