dhcpsrv.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. --[[
  2. @module dhcpsrv
  3. @summary DHCP服务器端
  4. @version 1.0.0
  5. @date 2025.04.15
  6. @author wendal
  7. @usage
  8. -- 参考dhcpsrv.create函数
  9. ]]
  10. local dhcpsrv = {}
  11. local udpsrv = require("udpsrv")
  12. local TAG = "dhcpsrv"
  13. ----
  14. -- 参考地址
  15. -- https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
  16. local function dhcp_decode(buff)
  17. -- buff:seek(0)
  18. local dst = {}
  19. -- 开始解析dhcp
  20. dst.op = buff[0]
  21. dst.htype = buff[1]
  22. dst.hlen = buff[2]
  23. dst.hops = buff[3]
  24. buff:seek(4)
  25. dst.xid = buff:read(4)
  26. _, dst.secs = buff:unpack(">H")
  27. _, dst.flags = buff:unpack(">H")
  28. dst.ciaddr = buff:read(4)
  29. dst.yiaddr = buff:read(4)
  30. dst.siaddr = buff:read(4)
  31. dst.giaddr = buff:read(4)
  32. dst.chaddr = buff:read(16)
  33. -- 跳过192字节
  34. buff:seek(192, zbuff.SEEK_CUR)
  35. -- 解析magic
  36. _, dst.magic = buff:unpack(">I")
  37. -- 解析option
  38. local opt = {}
  39. while buff:len() > buff:used() do
  40. local tag = buff:read(1):byte()
  41. if tag ~= 0 then
  42. local len = buff:read(1):byte()
  43. if tag == 0xFF or len == 0 then
  44. break
  45. end
  46. local data = buff:read(len)
  47. if tag == 53 then
  48. -- 53: DHCP Message Type
  49. dst.msgtype = data:byte()
  50. end
  51. table.insert(opt, {tag, data})
  52. -- log.info(TAG, "tag", tag, "data", data:toHex())
  53. end
  54. end
  55. if dst.msgtype == nil then
  56. return -- 没有解析到msgtype,直接返回
  57. end
  58. dst.opts = opt
  59. return dst
  60. end
  61. local function dhcp_buff2ip(buff)
  62. return string.format("%d.%d.%d.%d", buff:byte(1), buff:byte(2), buff:byte(3), buff:byte(4))
  63. end
  64. local function dhcp_print_pkg(pkg)
  65. log.info(TAG, "XID", pkg.xid:toHex())
  66. log.info(TAG, "secs", pkg.secs)
  67. log.info(TAG, "flags", pkg.flags)
  68. log.info(TAG, "chaddr", pkg.chaddr:sub(1, pkg.hlen):toHex())
  69. log.info(TAG, "yiaddr", dhcp_buff2ip(pkg.yiaddr))
  70. log.info(TAG, "siaddr", dhcp_buff2ip(pkg.siaddr))
  71. log.info(TAG, "giaddr", dhcp_buff2ip(pkg.giaddr))
  72. log.info(TAG, "ciaddr", dhcp_buff2ip(pkg.ciaddr))
  73. log.info(TAG, "magic", string.format("%08X", pkg.magic))
  74. for _, opt in pairs(pkg.opts) do
  75. if opt[1] == 53 then
  76. log.info(TAG, "msgtype", opt[2]:byte())
  77. elseif opt[1] == 60 then
  78. log.info(TAG, "auth", opt[2])
  79. elseif opt[1] == 57 then
  80. log.info(TAG, "Maximum DHCP message size", opt[2]:byte() * 256 + opt[2]:byte(2))
  81. elseif opt[1] == 61 then
  82. log.info(TAG, "Client-identifier", opt[2]:toHex())
  83. elseif opt[1] == 55 then
  84. log.info(TAG, "Parameter request list", opt[2]:toHex())
  85. elseif opt[1] == 12 then
  86. log.info(TAG, "Host name", opt[2])
  87. -- elseif opt[1] == 58 then
  88. -- log.info(TAG, "Renewal (T1) time value", opt[2]:unpack(">I"))
  89. end
  90. end
  91. end
  92. local function dhcp_encode(pkg, buff)
  93. -- 合成DHCP包
  94. buff:seek(0)
  95. buff[0] = pkg.op
  96. buff[1] = pkg.htype
  97. buff[2] = pkg.hlen
  98. buff[3] = pkg.hops
  99. buff:seek(4)
  100. -- 写入XID
  101. buff:write(pkg.xid)
  102. -- 几个重要的参数
  103. buff:pack(">H", pkg.secs)
  104. buff:pack(">H", pkg.flags)
  105. buff:write(pkg.ciaddr)
  106. buff:write(pkg.yiaddr)
  107. buff:write(pkg.siaddr)
  108. buff:write(pkg.giaddr)
  109. -- 写入MAC地址
  110. buff:write(pkg.chaddr)
  111. -- 跳过192字节
  112. buff:seek(192, zbuff.SEEK_CUR)
  113. -- 写入magic
  114. buff:pack(">I", pkg.magic)
  115. -- 写入option
  116. for _, opt in pairs(pkg.opts) do
  117. buff:write(opt[1])
  118. buff:write(#opt[2])
  119. buff:write(opt[2])
  120. end
  121. buff:write(0xFF, 0x00)
  122. end
  123. ----
  124. local function dhcp_send_x(srv, pkg, client, msgtype)
  125. local buff = zbuff.create(300)
  126. pkg.op = 2
  127. pkg.ciaddr = "\0\0\0\0"
  128. pkg.yiaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
  129. pkg.siaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])
  130. pkg.giaddr = "\0\0\0\0"
  131. pkg.secs = 0
  132. pkg.opts = {} -- 复位option
  133. table.insert(pkg.opts, {53, string.char(msgtype)})
  134. table.insert(pkg.opts, {1, string.char(srv.opts.mark[1], srv.opts.mark[2], srv.opts.mark[3], srv.opts.mark[4])})
  135. table.insert(pkg.opts, {3, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
  136. table.insert(pkg.opts, {51, "\x00\x00\x1E\x00"}) -- 7200秒, 大概
  137. table.insert(pkg.opts, {54, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
  138. table.insert(pkg.opts, {6, string.char(223, 5, 5, 5)})
  139. table.insert(pkg.opts, {6, string.char(119, 29, 29, 29)})
  140. table.insert(pkg.opts, {6, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
  141. dhcp_encode(pkg, buff)
  142. local dst = "255.255.255.255"
  143. if 4 == msgtype then
  144. dst = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
  145. end
  146. -- log.info(TAG, "发送", msgtype, dst, buff:query():toHex())
  147. srv.udp:send(buff, dst, 68)
  148. end
  149. local function dhcp_send_offer(srv, pkg, client)
  150. dhcp_send_x(srv, pkg, client, 2)
  151. end
  152. local function dhcp_send_ack(srv, pkg, client)
  153. dhcp_send_x(srv, pkg, client, 5)
  154. end
  155. local function dhcp_send_nack(srv, pkg, client)
  156. dhcp_send_x(srv, pkg, client, 6)
  157. end
  158. local function dhcp_handle_discover(srv, pkg)
  159. local mac = pkg.chaddr:sub(1, pkg.hlen)
  160. -- 看看是不是已经分配了ip
  161. for _, client in pairs(srv.clients) do
  162. if client.mac == mac then
  163. log.info(TAG, "发现已经分配的mac地址, send offer")
  164. dhcp_send_offer(srv, pkg, client)
  165. return
  166. end
  167. end
  168. -- TODO 清理已经过期的IP分配记录
  169. -- 分配一个新的ip
  170. if #srv.clients >= (srv.opts.ip_end - srv.opts.ip_start) then
  171. log.info(TAG, "没有可分配的ip了")
  172. return
  173. end
  174. local ip = nil
  175. for i = srv.opts.ip_start, srv.opts.ip_end, 1 do
  176. if srv.clients[i] == nil then
  177. ip = i
  178. break
  179. end
  180. end
  181. if ip == nil then
  182. log.info(TAG, "没有可分配的ip了")
  183. return
  184. end
  185. 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))
  186. local client = {
  187. mac = mac,
  188. ip = ip,
  189. tm = mcu.ticks() // mcu.hz(),
  190. stat = 1
  191. }
  192. srv.clients[ip] = client
  193. log.info(TAG, "send offer")
  194. dhcp_send_offer(srv, pkg, client)
  195. end
  196. local function dhcp_handle_request(srv, pkg)
  197. local mac = pkg.chaddr:sub(1, pkg.hlen)
  198. -- 看看是不是已经分配了ip
  199. for _, client in pairs(srv.clients) do
  200. if client.mac == mac then
  201. log.info(TAG, "request,发现已经分配的mac地址, send ack", mac:toHex())
  202. client.tm = mcu.ticks() // mcu.hz()
  203. stat = 3
  204. dhcp_send_ack(srv, pkg, client)
  205. if srv.opts.ack_cb then
  206. local cip = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
  207. srv.opts.ack_cb(cip, mac:toHex())
  208. end
  209. return
  210. end
  211. end
  212. -- 没有找到, 那应该返回NACK
  213. log.info(TAG, "request,对应mac地址没有分配ip, send nack")
  214. dhcp_send_nack(srv, pkg, {ip=pkg.yiaddr:byte(1)})
  215. end
  216. local function dhcp_pkg_handle(srv, pkg)
  217. -- 进行基本的检查
  218. if pkg.magic ~= 0x63825363 then
  219. log.warn(TAG, "dhcp数据包的magic不对劲,忽略该数据包", pkg.magic)
  220. return
  221. end
  222. if pkg.op ~= 1 then
  223. log.info(TAG, "op不对,忽略该数据包", pkg.op)
  224. return
  225. end
  226. if pkg.htype ~= 1 or pkg.hlen ~= 6 then
  227. log.warn(TAG, "htype/hlen 不认识, 忽略该数据包")
  228. return
  229. end
  230. -- 看看是不是能处理的类型, 当前只处理discover/request
  231. if pkg.msgtype == 1 or pkg.msgtype == 3 then
  232. else
  233. log.warn(TAG, "msgtype不是discover/request, 忽略该数据包", pkg.msgtype)
  234. return
  235. end
  236. -- 检查一下mac地址是否合法
  237. local mac = pkg.chaddr:sub(1, pkg.hlen)
  238. if mac == "\0\0\0\0\0\0" or mac == "\xFF\xFF\xFF\xFF\xFF\xFF" then
  239. log.warn(TAG, "mac地址为空, 忽略该数据包")
  240. return
  241. end
  242. -- 处理discover包
  243. if pkg.msgtype == 1 then
  244. log.info(TAG, "是discover包", mac:toHex())
  245. dhcp_handle_discover(srv, pkg)
  246. elseif pkg.msgtype == 3 then
  247. log.info(TAG, "是request包", mac:toHex())
  248. dhcp_handle_request(srv, pkg)
  249. end
  250. -- TODO 处理结束, 打印一下客户的列表?
  251. end
  252. local function dhcp_task(srv)
  253. while 1 do
  254. -- log.info("ulwip", "等待DHCP数据")
  255. local result, data = sys.waitUntil(srv.udp_topic, 1000)
  256. if result then
  257. -- log.info("ulwip", "收到dhcp数据包", data:toHex())
  258. -- 解析DHCP数据包
  259. local pkg = dhcp_decode(zbuff.create(#data, data))
  260. if pkg then
  261. -- dhcp_print_pkg(pkg)
  262. dhcp_pkg_handle(srv, pkg)
  263. end
  264. end
  265. end
  266. end
  267. --[[
  268. 创建一个dhcp服务器
  269. @api dhcpsrv.create(opts)
  270. @table 选项,参考库的说明, 及demo的用法
  271. @return 服务器对象
  272. @usage
  273. -- 创建一个dhcp服务器, 最简介的版本
  274. dhcpsrv.create({adapter=socket.LWIP_AP})
  275. -- 详细的版本
  276. -- 创建一个dhcp服务器
  277. local dhcpsrv_opts = {
  278. adapter=socket.LWIP_AP, -- 监听哪个网卡, 必须填写
  279. mark = {255, 255, 255, 0}, -- 网络掩码, 默认 255.255.255.0
  280. gw = {192, 168, 4, 1}, -- 网关, 默认 192.168.4.1
  281. ip_start = 100, -- ip起始地址, 默认100
  282. ip_end = 200, -- ip结束地址, 默认200
  283. ack_cb = function(ip, mac) end, -- ack回调, 有客户端连接上来时触发, ip和mac地址会传进来
  284. }
  285. dhcpsrv.create(dhcpsrv_opts)
  286. ]]
  287. function dhcpsrv.create(opts)
  288. local srv = {}
  289. if not opts then
  290. opts = {}
  291. end
  292. srv.udp_topic = "dhcpd_inc"
  293. -- 补充参数
  294. if not opts.mark then
  295. opts.mark = {255, 255, 255, 0}
  296. end
  297. if not opts.gw then
  298. opts.gw = {192, 168, 4, 1}
  299. end
  300. if not opts.dns then
  301. opts.dns = opts.gw
  302. end
  303. if not opts.ip_start then
  304. opts.ip_start = 100
  305. end
  306. if not opts.ip_end then
  307. opts.ip_end = 200
  308. end
  309. srv.clients = {}
  310. srv.opts = opts
  311. srv.udp = udpsrv.create(67, srv.udp_topic, opts.adapter)
  312. srv.task = sys.taskInit(dhcp_task, srv)
  313. return srv
  314. end
  315. return dhcpsrv