luajitos

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

DHCP.lua (10738B)


      1 -- DHCP Client Implementation
      2 -- Implements DHCP for automatic IP address configuration
      3 
      4 local DHCP = {}
      5 
      6 -- DHCP Message Types
      7 DHCP.MSG_TYPE = {
      8     DISCOVER = 1,
      9     OFFER = 2,
     10     REQUEST = 3,
     11     DECLINE = 4,
     12     ACK = 5,
     13     NAK = 6,
     14     RELEASE = 7,
     15     INFORM = 8,
     16 }
     17 
     18 -- DHCP Options
     19 DHCP.OPTION = {
     20     SUBNET_MASK = 1,
     21     ROUTER = 3,
     22     DNS_SERVER = 6,
     23     REQUESTED_IP = 50,
     24     LEASE_TIME = 51,
     25     MSG_TYPE = 53,
     26     SERVER_ID = 54,
     27     PARAM_REQUEST = 55,
     28     END = 255,
     29 }
     30 
     31 ---Build DHCP packet
     32 ---@param msg_type number DHCP message type
     33 ---@param xid number Transaction ID
     34 ---@param mac table MAC address (6 bytes)
     35 ---@param options table Options to include
     36 ---@return string packet DHCP packet
     37 function DHCP.build_packet(msg_type, xid, mac, options)
     38     local dhcp = {}
     39 
     40     -- Op (1 = Boot Request)
     41     dhcp[#dhcp + 1] = string.char(0x01)
     42 
     43     -- Hardware type (1 = Ethernet)
     44     dhcp[#dhcp + 1] = string.char(0x01)
     45 
     46     -- Hardware address length (6 = MAC)
     47     dhcp[#dhcp + 1] = string.char(0x06)
     48 
     49     -- Hops
     50     dhcp[#dhcp + 1] = string.char(0x00)
     51 
     52     -- Transaction ID (4 bytes)
     53     dhcp[#dhcp + 1] = string.char(
     54         (xid >> 24) & 0xFF,
     55         (xid >> 16) & 0xFF,
     56         (xid >> 8) & 0xFF,
     57         xid & 0xFF
     58     )
     59 
     60     -- Seconds elapsed
     61     dhcp[#dhcp + 1] = string.char(0x00, 0x00)
     62 
     63     -- Flags (broadcast)
     64     dhcp[#dhcp + 1] = string.char(0x80, 0x00)
     65 
     66     -- Client IP address (0.0.0.0)
     67     dhcp[#dhcp + 1] = string.char(0x00, 0x00, 0x00, 0x00)
     68 
     69     -- Your IP address (0.0.0.0)
     70     dhcp[#dhcp + 1] = string.char(0x00, 0x00, 0x00, 0x00)
     71 
     72     -- Server IP address (0.0.0.0)
     73     dhcp[#dhcp + 1] = string.char(0x00, 0x00, 0x00, 0x00)
     74 
     75     -- Gateway IP address (0.0.0.0)
     76     dhcp[#dhcp + 1] = string.char(0x00, 0x00, 0x00, 0x00)
     77 
     78     -- Client MAC address (16 bytes, padded)
     79     for i = 1, 6 do
     80         dhcp[#dhcp + 1] = string.char(mac[i])
     81     end
     82     for i = 7, 16 do
     83         dhcp[#dhcp + 1] = string.char(0x00)
     84     end
     85 
     86     -- Server host name (64 bytes, zeroed)
     87     for i = 1, 64 do
     88         dhcp[#dhcp + 1] = string.char(0x00)
     89     end
     90 
     91     -- Boot file name (128 bytes, zeroed)
     92     for i = 1, 128 do
     93         dhcp[#dhcp + 1] = string.char(0x00)
     94     end
     95 
     96     -- Magic cookie
     97     dhcp[#dhcp + 1] = string.char(0x63, 0x82, 0x53, 0x63)
     98 
     99     -- DHCP Message Type option
    100     dhcp[#dhcp + 1] = string.char(DHCP.OPTION.MSG_TYPE, 0x01, msg_type)
    101 
    102     -- Additional options
    103     if options then
    104         for _, opt in ipairs(options) do
    105             dhcp[#dhcp + 1] = string.char(opt.code, opt.len)
    106             dhcp[#dhcp + 1] = opt.data
    107         end
    108     end
    109 
    110     -- End option
    111     dhcp[#dhcp + 1] = string.char(DHCP.OPTION.END)
    112 
    113     -- Pad to minimum size (300 bytes for DHCP)
    114     local packet = table.concat(dhcp)
    115     if #packet < 300 then
    116         packet = packet .. string.rep(string.char(0x00), 300 - #packet)
    117     end
    118 
    119     return packet
    120 end
    121 
    122 ---Parse DHCP packet
    123 ---@param payload string DHCP payload
    124 ---@return table|nil dhcp Parsed DHCP packet
    125 function DHCP.parse_packet(payload)
    126     if #payload < 236 then
    127         return nil
    128     end
    129 
    130     local dhcp = {}
    131 
    132     -- Op
    133     dhcp.op = string.byte(payload, 1)
    134 
    135     -- Transaction ID
    136     dhcp.xid = (string.byte(payload, 5) << 24) |
    137                (string.byte(payload, 6) << 16) |
    138                (string.byte(payload, 7) << 8) |
    139                string.byte(payload, 8)
    140 
    141     -- Your IP address
    142     dhcp.yiaddr = {}
    143     for i = 1, 4 do
    144         dhcp.yiaddr[i] = string.byte(payload, 16 + i)
    145     end
    146 
    147     -- Server IP address
    148     dhcp.siaddr = {}
    149     for i = 1, 4 do
    150         dhcp.siaddr[i] = string.byte(payload, 20 + i)
    151     end
    152 
    153     -- Gateway IP address
    154     dhcp.giaddr = {}
    155     for i = 1, 4 do
    156         dhcp.giaddr[i] = string.byte(payload, 24 + i)
    157     end
    158 
    159     -- Check magic cookie
    160     local magic = string.byte(payload, 237) * 0x1000000 +
    161                   string.byte(payload, 238) * 0x10000 +
    162                   string.byte(payload, 239) * 0x100 +
    163                   string.byte(payload, 240)
    164 
    165     if magic ~= 0x63825363 then
    166         return nil  -- Invalid DHCP packet
    167     end
    168 
    169     -- Parse options
    170     dhcp.options = {}
    171     local i = 241
    172     while i < #payload do
    173         local code = string.byte(payload, i)
    174 
    175         if code == DHCP.OPTION.END then
    176             break
    177         elseif code == 0 then  -- Pad
    178             i = i + 1
    179         else
    180             local len = string.byte(payload, i + 1)
    181             local data = string.sub(payload, i + 2, i + 1 + len)
    182             dhcp.options[code] = data
    183             i = i + 2 + len
    184         end
    185     end
    186 
    187     -- Extract common options
    188     if dhcp.options[DHCP.OPTION.MSG_TYPE] then
    189         dhcp.msg_type = string.byte(dhcp.options[DHCP.OPTION.MSG_TYPE], 1)
    190     end
    191 
    192     if dhcp.options[DHCP.OPTION.SUBNET_MASK] then
    193         dhcp.subnet_mask = {}
    194         for i = 1, 4 do
    195             dhcp.subnet_mask[i] = string.byte(dhcp.options[DHCP.OPTION.SUBNET_MASK], i)
    196         end
    197     end
    198 
    199     if dhcp.options[DHCP.OPTION.ROUTER] then
    200         dhcp.router = {}
    201         for i = 1, 4 do
    202             dhcp.router[i] = string.byte(dhcp.options[DHCP.OPTION.ROUTER], i)
    203         end
    204     end
    205 
    206     if dhcp.options[DHCP.OPTION.DNS_SERVER] then
    207         dhcp.dns = {}
    208         for i = 1, 4 do
    209             dhcp.dns[i] = string.byte(dhcp.options[DHCP.OPTION.DNS_SERVER], i)
    210         end
    211     end
    212 
    213     if dhcp.options[DHCP.OPTION.SERVER_ID] then
    214         dhcp.server_id = {}
    215         for i = 1, 4 do
    216             dhcp.server_id[i] = string.byte(dhcp.options[DHCP.OPTION.SERVER_ID], i)
    217         end
    218     end
    219 
    220     return dhcp
    221 end
    222 
    223 ---Request IP address via DHCP
    224 ---@param NetworkStack table Network stack instance
    225 ---@param timeout number Timeout in seconds
    226 ---@return table|nil config DHCP configuration (ip, netmask, gateway, dns)
    227 function DHCP.request(NetworkStack, timeout)
    228     if not NetworkStack.RTL8139 then
    229         return nil
    230     end
    231 
    232     local mac = NetworkStack.RTL8139.getMACAddress()
    233     if not mac then
    234         return nil
    235     end
    236 
    237     -- Generate transaction ID
    238     local xid = math.random(1, 0x7FFFFFFF)
    239 
    240     -- Build DHCP DISCOVER
    241     local discover = DHCP.build_packet(DHCP.MSG_TYPE.DISCOVER, xid, mac, {
    242         {
    243             code = DHCP.OPTION.PARAM_REQUEST,
    244             len = 4,
    245             data = string.char(
    246                 DHCP.OPTION.SUBNET_MASK,
    247                 DHCP.OPTION.ROUTER,
    248                 DHCP.OPTION.DNS_SERVER,
    249                 DHCP.OPTION.LEASE_TIME
    250             )
    251         }
    252     })
    253 
    254     -- Build UDP packet (DHCP client port 68 -> server port 67)
    255     local udp = NetworkStack.build_udp(68, 67, discover)
    256 
    257     -- Build IP packet (broadcast from 0.0.0.0 to 255.255.255.255)
    258     local ip = NetworkStack.build_ipv4(
    259         17,
    260         {0, 0, 0, 0},
    261         {255, 255, 255, 255},
    262         udp
    263     )
    264 
    265     -- Build Ethernet frame (broadcast)
    266     local broadcast_mac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
    267     local frame = NetworkStack.RTL8139.buildEthernetFrame(
    268         broadcast_mac, mac, 0x0800, ip
    269     )
    270 
    271     -- Send DHCP DISCOVER
    272     NetworkStack.RTL8139.send(frame)
    273 
    274     -- Wait for DHCP OFFER
    275     local start_time = os.time and os.time() or 0
    276     local offer = nil
    277 
    278     while (os.time and os.time() or 0) - start_time < timeout do
    279         NetworkStack.RTL8139.poll()
    280 
    281         -- Check for DHCP OFFER in UDP packets on port 68
    282         local packet = NetworkStack.RTL8139.receive()
    283         if packet then
    284             local eth = NetworkStack.RTL8139.parseEthernetHeader(packet)
    285             if eth and eth.ethertype == 0x0800 then
    286                 local ip_pkt = NetworkStack.parse_ipv4(eth.payload)
    287                 if ip_pkt and ip_pkt.protocol == 17 then
    288                     local udp_pkt = NetworkStack.parse_udp(ip_pkt.payload)
    289                     if udp_pkt and udp_pkt.dst_port == 68 and udp_pkt.src_port == 67 then
    290                         local dhcp = DHCP.parse_packet(udp_pkt.payload)
    291                         if dhcp and dhcp.xid == xid and dhcp.msg_type == DHCP.MSG_TYPE.OFFER then
    292                             offer = dhcp
    293                             break
    294                         end
    295                     end
    296                 end
    297             end
    298         end
    299     end
    300 
    301     if not offer then
    302         return nil  -- Timeout
    303     end
    304 
    305     -- Send DHCP REQUEST
    306     local request_opts = {
    307         {
    308             code = DHCP.OPTION.REQUESTED_IP,
    309             len = 4,
    310             data = string.char(offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3], offer.yiaddr[4])
    311         },
    312         {
    313             code = DHCP.OPTION.PARAM_REQUEST,
    314             len = 4,
    315             data = string.char(
    316                 DHCP.OPTION.SUBNET_MASK,
    317                 DHCP.OPTION.ROUTER,
    318                 DHCP.OPTION.DNS_SERVER,
    319                 DHCP.OPTION.LEASE_TIME
    320             )
    321         }
    322     }
    323 
    324     if offer.server_id then
    325         table.insert(request_opts, {
    326             code = DHCP.OPTION.SERVER_ID,
    327             len = 4,
    328             data = string.char(
    329                 offer.server_id[1], offer.server_id[2],
    330                 offer.server_id[3], offer.server_id[4]
    331             )
    332         })
    333     end
    334 
    335     local request = DHCP.build_packet(DHCP.MSG_TYPE.REQUEST, xid, mac, request_opts)
    336 
    337     -- Build and send DHCP REQUEST
    338     udp = NetworkStack.build_udp(68, 67, request)
    339     ip = NetworkStack.build_ipv4(17, {0, 0, 0, 0}, {255, 255, 255, 255}, udp)
    340     frame = NetworkStack.RTL8139.buildEthernetFrame(broadcast_mac, mac, 0x0800, ip)
    341     NetworkStack.RTL8139.send(frame)
    342 
    343     -- Wait for DHCP ACK
    344     start_time = os.time and os.time() or 0
    345 
    346     while (os.time and os.time() or 0) - start_time < timeout do
    347         NetworkStack.RTL8139.poll()
    348 
    349         local packet = NetworkStack.RTL8139.receive()
    350         if packet then
    351             local eth = NetworkStack.RTL8139.parseEthernetHeader(packet)
    352             if eth and eth.ethertype == 0x0800 then
    353                 local ip_pkt = NetworkStack.parse_ipv4(eth.payload)
    354                 if ip_pkt and ip_pkt.protocol == 17 then
    355                     local udp_pkt = NetworkStack.parse_udp(ip_pkt.payload)
    356                     if udp_pkt and udp_pkt.dst_port == 68 and udp_pkt.src_port == 67 then
    357                         local dhcp = DHCP.parse_packet(udp_pkt.payload)
    358                         if dhcp and dhcp.xid == xid and dhcp.msg_type == DHCP.MSG_TYPE.ACK then
    359                             -- Success! Return configuration
    360                             return {
    361                                 ip = dhcp.yiaddr,
    362                                 netmask = dhcp.subnet_mask,
    363                                 gateway = dhcp.router,
    364                                 dns = dhcp.dns,
    365                                 server = dhcp.server_id
    366                             }
    367                         end
    368                     end
    369                 end
    370             end
    371         end
    372     end
    373 
    374     return nil  -- Timeout
    375 end
    376 
    377 return DHCP