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