代码拉取完成,页面将自动刷新
--[[
@module dhcpsrv
@summary DHCP服务器端
@version 1.0.0
@date 2025.04.15
@author wendal
@usage
-- 参考dhcpsrv.create函数
]]
local dhcpsrv = {}
local udpsrv = require("udpsrv")
local TAG = "dhcpsrv"
----
-- 参考地址
-- https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
local function dhcp_decode(buff)
-- buff:seek(0)
local dst = {}
-- 开始解析dhcp
dst.op = buff[0]
dst.htype = buff[1]
dst.hlen = buff[2]
dst.hops = buff[3]
buff:seek(4)
dst.xid = buff:read(4)
_, dst.secs = buff:unpack(">H")
_, dst.flags = buff:unpack(">H")
dst.ciaddr = buff:read(4)
dst.yiaddr = buff:read(4)
dst.siaddr = buff:read(4)
dst.giaddr = buff:read(4)
dst.chaddr = buff:read(16)
-- 跳过192字节
buff:seek(192, zbuff.SEEK_CUR)
-- 解析magic
_, dst.magic = buff:unpack(">I")
-- 解析option
local opt = {}
while buff:len() > buff:used() do
local tag = buff:read(1):byte()
if tag ~= 0 then
local len = buff:read(1):byte()
if tag == 0xFF or len == 0 then
break
end
local data = buff:read(len)
if tag == 53 then
-- 53: DHCP Message Type
dst.msgtype = data:byte()
end
table.insert(opt, {tag, data})
-- log.info(TAG, "tag", tag, "data", data:toHex())
end
end
if dst.msgtype == nil then
return -- 没有解析到msgtype,直接返回
end
dst.opts = opt
return dst
end
local function dhcp_buff2ip(buff)
return string.format("%d.%d.%d.%d", buff:byte(1), buff:byte(2), buff:byte(3), buff:byte(4))
end
local function dhcp_print_pkg(pkg)
log.info(TAG, "XID", pkg.xid:toHex())
log.info(TAG, "secs", pkg.secs)
log.info(TAG, "flags", pkg.flags)
log.info(TAG, "chaddr", pkg.chaddr:sub(1, pkg.hlen):toHex())
log.info(TAG, "yiaddr", dhcp_buff2ip(pkg.yiaddr))
log.info(TAG, "siaddr", dhcp_buff2ip(pkg.siaddr))
log.info(TAG, "giaddr", dhcp_buff2ip(pkg.giaddr))
log.info(TAG, "ciaddr", dhcp_buff2ip(pkg.ciaddr))
log.info(TAG, "magic", string.format("%08X", pkg.magic))
for _, opt in pairs(pkg.opts) do
if opt[1] == 53 then
log.info(TAG, "msgtype", opt[2]:byte())
elseif opt[1] == 60 then
log.info(TAG, "auth", opt[2])
elseif opt[1] == 57 then
log.info(TAG, "Maximum DHCP message size", opt[2]:byte() * 256 + opt[2]:byte(2))
elseif opt[1] == 61 then
log.info(TAG, "Client-identifier", opt[2]:toHex())
elseif opt[1] == 55 then
log.info(TAG, "Parameter request list", opt[2]:toHex())
elseif opt[1] == 12 then
log.info(TAG, "Host name", opt[2])
-- elseif opt[1] == 58 then
-- log.info(TAG, "Renewal (T1) time value", opt[2]:unpack(">I"))
end
end
end
local function dhcp_encode(pkg, buff)
-- 合成DHCP包
buff:seek(0)
buff[0] = pkg.op
buff[1] = pkg.htype
buff[2] = pkg.hlen
buff[3] = pkg.hops
buff:seek(4)
-- 写入XID
buff:write(pkg.xid)
-- 几个重要的参数
buff:pack(">H", pkg.secs)
buff:pack(">H", pkg.flags)
buff:write(pkg.ciaddr)
buff:write(pkg.yiaddr)
buff:write(pkg.siaddr)
buff:write(pkg.giaddr)
-- 写入MAC地址
buff:write(pkg.chaddr)
-- 跳过192字节
buff:seek(192, zbuff.SEEK_CUR)
-- 写入magic
buff:pack(">I", pkg.magic)
-- 写入option
for _, opt in pairs(pkg.opts) do
buff:write(opt[1])
buff:write(#opt[2])
buff:write(opt[2])
end
buff:write(0xFF, 0x00)
end
----
local function dhcp_send_x(srv, pkg, client, msgtype)
local buff = zbuff.create(300)
pkg.op = 2
pkg.ciaddr = "\0\0\0\0"
pkg.yiaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
pkg.siaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])
pkg.giaddr = "\0\0\0\0"
pkg.secs = 0
pkg.opts = {} -- 复位option
table.insert(pkg.opts, {53, string.char(msgtype)})
table.insert(pkg.opts, {1, string.char(srv.opts.mark[1], srv.opts.mark[2], srv.opts.mark[3], srv.opts.mark[4])})
table.insert(pkg.opts, {3, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
table.insert(pkg.opts, {51, "\x00\x00\x1E\x00"}) -- 7200秒, 大概
table.insert(pkg.opts, {54, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
table.insert(pkg.opts, {6, string.char(223, 5, 5, 5)})
table.insert(pkg.opts, {6, string.char(119, 29, 29, 29)})
table.insert(pkg.opts, {6, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
dhcp_encode(pkg, buff)
local dst = "255.255.255.255"
if 4 == msgtype then
dst = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
end
-- log.info(TAG, "发送", msgtype, dst, buff:query():toHex())
srv.udp:send(buff, dst, 68)
end
local function dhcp_send_offer(srv, pkg, client)
dhcp_send_x(srv, pkg, client, 2)
end
local function dhcp_send_ack(srv, pkg, client)
dhcp_send_x(srv, pkg, client, 5)
end
local function dhcp_send_nack(srv, pkg, client)
dhcp_send_x(srv, pkg, client, 6)
end
local function dhcp_handle_discover(srv, pkg)
local mac = pkg.chaddr:sub(1, pkg.hlen)
-- 看看是不是已经分配了ip
for _, client in pairs(srv.clients) do
if client.mac == mac then
log.info(TAG, "发现已经分配的mac地址, send offer")
dhcp_send_offer(srv, pkg, client)
return
end
end
-- TODO 清理已经过期的IP分配记录
-- 分配一个新的ip
if #srv.clients >= (srv.opts.ip_end - srv.opts.ip_start) then
log.info(TAG, "没有可分配的ip了")
return
end
local ip = nil
for i = srv.opts.ip_start, srv.opts.ip_end, 1 do
if srv.clients[i] == nil then
ip = i
break
end
end
if ip == nil then
log.info(TAG, "没有可分配的ip了")
return
end
log.info(TAG, "分配ip", mac:toHex(), string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], ip))
local client = {
mac = mac,
ip = ip,
tm = mcu.ticks() // mcu.hz(),
stat = 1
}
srv.clients[ip] = client
log.info(TAG, "send offer")
dhcp_send_offer(srv, pkg, client)
end
local function dhcp_handle_request(srv, pkg)
local mac = pkg.chaddr:sub(1, pkg.hlen)
-- 看看是不是已经分配了ip
for _, client in pairs(srv.clients) do
if client.mac == mac then
log.info(TAG, "request,发现已经分配的mac地址, send ack", mac:toHex())
client.tm = mcu.ticks() // mcu.hz()
stat = 3
dhcp_send_ack(srv, pkg, client)
if srv.opts.ack_cb then
local cip = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
srv.opts.ack_cb(cip, mac:toHex())
end
return
end
end
-- 没有找到, 那应该返回NACK
log.info(TAG, "request,对应mac地址没有分配ip, send nack")
dhcp_send_nack(srv, pkg, {ip=pkg.yiaddr:byte(1)})
end
local function dhcp_pkg_handle(srv, pkg)
-- 进行基本的检查
if pkg.magic ~= 0x63825363 then
log.warn(TAG, "dhcp数据包的magic不对劲,忽略该数据包", pkg.magic)
return
end
if pkg.op ~= 1 then
log.info(TAG, "op不对,忽略该数据包", pkg.op)
return
end
if pkg.htype ~= 1 or pkg.hlen ~= 6 then
log.warn(TAG, "htype/hlen 不认识, 忽略该数据包")
return
end
-- 看看是不是能处理的类型, 当前只处理discover/request
if pkg.msgtype == 1 or pkg.msgtype == 3 then
else
log.warn(TAG, "msgtype不是discover/request, 忽略该数据包", pkg.msgtype)
return
end
-- 检查一下mac地址是否合法
local mac = pkg.chaddr:sub(1, pkg.hlen)
if mac == "\0\0\0\0\0\0" or mac == "\xFF\xFF\xFF\xFF\xFF\xFF" then
log.warn(TAG, "mac地址为空, 忽略该数据包")
return
end
-- 处理discover包
if pkg.msgtype == 1 then
log.info(TAG, "是discover包", mac:toHex())
dhcp_handle_discover(srv, pkg)
elseif pkg.msgtype == 3 then
log.info(TAG, "是request包", mac:toHex())
dhcp_handle_request(srv, pkg)
end
-- TODO 处理结束, 打印一下客户的列表?
end
local function dhcp_task(srv)
while 1 do
-- log.info("ulwip", "等待DHCP数据")
local result, data = sys.waitUntil(srv.udp_topic, 1000)
if result then
-- log.info("ulwip", "收到dhcp数据包", data:toHex())
-- 解析DHCP数据包
local pkg = dhcp_decode(zbuff.create(#data, data))
if pkg then
-- dhcp_print_pkg(pkg)
dhcp_pkg_handle(srv, pkg)
end
end
end
end
--[[
创建一个dhcp服务器
@api dhcpsrv.create(opts)
@table 选项,参考库的说明, 及demo的用法
@return table 服务器对象
@usage
-- 创建一个dhcp服务器, 最简介的版本
dhcpsrv.create({adapter=socket.LWIP_AP})
-- 详细的版本
-- 创建一个dhcp服务器
local dhcpsrv_opts = {
adapter=socket.LWIP_AP, -- 监听哪个网卡, 必须填写
mark = {255, 255, 255, 0}, -- 网络掩码, 默认 255.255.255.0
gw = {192, 168, 4, 1}, -- 网关, 默认自动获取网卡IP,如果获取失败则使用 192.168.4.1
ip_start = 100, -- ip起始地址, 默认100
ip_end = 200, -- ip结束地址, 默认200
ack_cb = function(ip, mac) end, -- ack回调, 有客户端连接上来时触发, ip和mac地址会传进来
}
local mydhcpsrv = dhcpsrv.create(dhcpsrv_opts)
-- 以下是一个打印客户端列表的例子, 非必选, 仅供参考
-- clients是一个table, 包含MAC和IP的对应关系, 注意, IP只记录了最后一段数字, 非完整IP
-- 注意, clients是动态变化的过程, mydhcpsrv对象的其他属性切勿修改, 仅提供clients的只读功能
sys.taskInit(function()
while true do
sys.wait(10000)
-- 这里可以打印一下当前的客户端列表
for ip, client in pairs(mydhcpsrv.clients) do
log.info(TAG, "client", ip, client.mac:toHex(), client.tm, client.stat)
end
end
end)
-- 自动分配网段功能说明:
-- 如果不指定gw参数,系统会自动获取网卡IP作为网关地址
-- 这样可以确保DHCP分配的IP与网卡IP在同一网段
]]
function dhcpsrv.create(opts)
local srv = {}
if not opts then
opts = {}
end
srv.udp_topic = "dhcpd_inc"
-- 自动获取网卡IP地址的函数
local function get_adapter_ip()
if not opts.adapter then
return nil
end
-- 获取网卡IP地址
local ip = netdrv.ipv4(opts.adapter)
if not ip or ip == "0.0.0.0" then
return nil
end
-- 简单解析IP地址:192.168.4.1 -> {192, 168, 4, 1}
local a, b, c, d = ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
if a and b and c and d then
return {tonumber(a), tonumber(b), tonumber(c), tonumber(d)}
end
return nil
end
-- 补充参数
if not opts.mark then
opts.mark = {255, 255, 255, 0}
end
-- 如果没有指定网关,则自动获取网卡IP作为网关
if not opts.gw then
local adapter_ip = get_adapter_ip()
if adapter_ip then
opts.gw = adapter_ip
log.info(TAG, "自动获取网卡IP作为网关", string.format("%d.%d.%d.%d", adapter_ip[1], adapter_ip[2], adapter_ip[3], adapter_ip[4]))
else
opts.gw = {192, 168, 4, 1}
log.warn(TAG, "无法获取网卡IP,使用默认网关", string.format("%d.%d.%d.%d", opts.gw[1], opts.gw[2], opts.gw[3], opts.gw[4]))
end
end
if not opts.dns then
opts.dns = opts.gw
end
-- 根据网关IP自动设置IP分配范围
if not opts.ip_start then
opts.ip_start = 100
end
if not opts.ip_end then
opts.ip_end = 200
end
srv.clients = {}
srv.opts = opts
srv.udp = udpsrv.create(67, srv.udp_topic, opts.adapter)
srv.task = sys.taskInit(dhcp_task, srv)
return srv
end
return dhcpsrv
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。