server.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. package dhcp
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "log"
  6. "net"
  7. "os"
  8. "sync"
  9. "time"
  10. "dhcp-dns-manager/internal/db"
  11. "dhcp-dns-manager/internal/config"
  12. "golang.org/x/sys/unix"
  13. )
  14. // DHCP 消息类型
  15. const (
  16. MsgDiscover = 1
  17. MsgOffer = 2
  18. MsgRequest = 3
  19. MsgDecline = 4
  20. MsgACK = 5
  21. MsgNAK = 6
  22. MsgRelease = 7
  23. )
  24. // DHCP 选项
  25. const (
  26. OptionSubnetMask = 1
  27. OptionRouter = 3
  28. OptionDNS = 6
  29. OptionHostname = 12
  30. OptionLeaseTime = 51
  31. OptionMessageType = 53
  32. OptionServerIdentifier = 54
  33. OptionRequestedIP = 50
  34. OptionEnd = 255
  35. )
  36. type Server struct {
  37. config *config.DHCPConfig
  38. configReloader func() *config.DHCPConfig // optional: reload config from ConfigManager
  39. db *db.DB
  40. leases map[string]*db.DHCPLease
  41. staticBindings map[string]db.DHCPStaticBinding
  42. leaseMutex sync.RWMutex
  43. conn *net.UDPConn
  44. stopChan chan struct{}
  45. serverIP net.IP
  46. usedIPs map[string]string // IP -> MAC
  47. }
  48. func NewServer(cfg *config.DHCPConfig, database *db.DB) *Server {
  49. return &Server{
  50. config: cfg,
  51. db: database,
  52. leases: make(map[string]*db.DHCPLease),
  53. staticBindings: make(map[string]db.DHCPStaticBinding),
  54. usedIPs: make(map[string]string),
  55. stopChan: make(chan struct{}),
  56. serverIP: net.ParseIP(cfg.Gateway).To4(),
  57. }
  58. }
  59. // SetConfigReloader sets a function to reload config dynamically.
  60. // This allows the DHCP server to pick up config changes made via the web UI.
  61. func (s *Server) SetConfigReloader(reloader func() *config.DHCPConfig) {
  62. s.configReloader = reloader
  63. }
  64. // getConfig returns the current config, reloading if a reloader is set.
  65. func (s *Server) getConfig() *config.DHCPConfig {
  66. if s.configReloader != nil {
  67. return s.configReloader()
  68. }
  69. return s.config
  70. }
  71. // getServerIP returns the server IP for DHCP options
  72. func (s *Server) getServerIP() net.IP {
  73. cfg := s.getConfig()
  74. return net.ParseIP(cfg.Gateway).To4()
  75. }
  76. func (s *Server) Start() error {
  77. if !s.config.Enabled {
  78. return nil
  79. }
  80. // Load existing leases
  81. s.loadLeases()
  82. s.loadStaticBindings()
  83. // Start lease cleanup goroutine
  84. go s.cleanupLeases()
  85. // Start DHCP server on UDP port 67
  86. // Use a raw connection to properly handle broadcast responses
  87. conn, err := newBroadcastUDPConn("0.0.0.0", 67)
  88. if err != nil {
  89. return fmt.Errorf("failed to listen on UDP 67: %v", err)
  90. }
  91. s.conn = conn
  92. log.Printf("DHCP server listening on 0.0.0.0:67")
  93. // Handle DHCP requests
  94. go s.handleDHCP()
  95. return nil
  96. }
  97. func (s *Server) Stop() {
  98. if s.conn != nil {
  99. s.conn.Close()
  100. }
  101. close(s.stopChan)
  102. }
  103. func (s *Server) handleDHCP() {
  104. buf := make([]byte, 1024)
  105. for {
  106. select {
  107. case <-s.stopChan:
  108. return
  109. default:
  110. s.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
  111. n, remoteAddr, err := s.conn.ReadFromUDP(buf)
  112. if err != nil {
  113. continue
  114. }
  115. s.processDHCPMessage(buf[:n], remoteAddr)
  116. }
  117. }
  118. }
  119. func (s *Server) processDHCPMessage(data []byte, remoteAddr *net.UDPAddr) {
  120. if len(data) < 240 {
  121. return
  122. }
  123. // Parse DHCP message
  124. msgType := parseMessageType(data)
  125. if msgType == 0 {
  126. return
  127. }
  128. // Get client MAC
  129. clientMAC := formatMAC(data[28:34])
  130. // Get client IP from packet
  131. clientIP := net.IP(data[16:20])
  132. switch msgType {
  133. case MsgDiscover:
  134. s.handleDiscover(data, clientMAC, clientIP, remoteAddr)
  135. case MsgRequest:
  136. s.handleRequest(data, clientMAC, clientIP, remoteAddr)
  137. case MsgRelease:
  138. s.handleRelease(clientMAC)
  139. case MsgDecline:
  140. s.handleDecline(clientMAC)
  141. }
  142. }
  143. func (s *Server) handleDiscover(data []byte, clientMAC string, clientIP net.IP, remoteAddr *net.UDPAddr) {
  144. // Find or assign IP
  145. offeredIP := s.assignIP(clientMAC, data)
  146. if offeredIP == "" {
  147. log.Printf("DHCP: No available IP for %s", clientMAC)
  148. return
  149. }
  150. // Record the offered IP as a provisional lease BEFORE sending the Offer.
  151. // This ensures that when the client sends Request back, verifyAssignment
  152. // will find the lease and return true (instead of NAKing the client).
  153. s.recordLease(clientMAC, offeredIP, data)
  154. // Send DHCP Offer
  155. s.sendOffer(data, clientMAC, offeredIP, remoteAddr)
  156. log.Printf("DHCP: Offered %s to %s", offeredIP, clientMAC)
  157. }
  158. func (s *Server) handleRequest(data []byte, clientMAC string, clientIP net.IP, remoteAddr *net.UDPAddr) {
  159. // Get requested IP
  160. requestedIP := parseRequestedIP(data).String()
  161. if requestedIP == "<nil>" || requestedIP == "" {
  162. // Fallback to ciaddr
  163. requestedIP = clientIP.String()
  164. }
  165. // Verify the requested IP
  166. if s.verifyAssignment(clientMAC, requestedIP) {
  167. // Send DHCP ACK
  168. s.sendACK(data, clientMAC, requestedIP, remoteAddr)
  169. log.Printf("DHCP: ACK %s to %s", requestedIP, clientMAC)
  170. // Record lease
  171. s.recordLease(clientMAC, requestedIP, data)
  172. } else {
  173. // Send DHCP NAK
  174. s.sendNAK(data, remoteAddr)
  175. log.Printf("DHCP: NAK for %s (requested %s)", clientMAC, requestedIP)
  176. }
  177. }
  178. func (s *Server) handleRelease(clientMAC string) {
  179. s.leaseMutex.Lock()
  180. defer s.leaseMutex.Unlock()
  181. if lease, exists := s.leases[clientMAC]; exists {
  182. log.Printf("DHCP: Released %s for %s", lease.IP, clientMAC)
  183. delete(s.leases, clientMAC)
  184. delete(s.usedIPs, lease.IP)
  185. }
  186. }
  187. func (s *Server) handleDecline(clientMAC string) {
  188. s.leaseMutex.Lock()
  189. defer s.leaseMutex.Unlock()
  190. if lease, exists := s.leases[clientMAC]; exists {
  191. log.Printf("DHCP: Declined %s for %s", lease.IP, clientMAC)
  192. delete(s.leases, clientMAC)
  193. delete(s.usedIPs, lease.IP)
  194. }
  195. }
  196. func (s *Server) assignIP(clientMAC string, data []byte) string {
  197. s.leaseMutex.Lock()
  198. defer s.leaseMutex.Unlock()
  199. // Check static binding first
  200. if binding, exists := s.staticBindings[clientMAC]; exists {
  201. return binding.IP
  202. }
  203. // Check if client already has a lease
  204. if lease, exists := s.leases[clientMAC]; exists {
  205. return lease.IP
  206. }
  207. // Find available IP
  208. startIP := net.ParseIP(s.getConfig().IPPoolStart).To4()
  209. endIP := net.ParseIP(s.getConfig().IPPoolEnd).To4()
  210. if startIP == nil || endIP == nil {
  211. return ""
  212. }
  213. startInt := uint32(startIP[0])<<24 | uint32(startIP[1])<<16 | uint32(startIP[2])<<8 | uint32(startIP[3])
  214. endInt := uint32(endIP[0])<<24 | uint32(endIP[1])<<16 | uint32(endIP[2])<<8 | uint32(endIP[3])
  215. // Build set of used IPs
  216. usedIPs := make(map[string]bool)
  217. for _, lease := range s.leases {
  218. usedIPs[lease.IP] = true
  219. }
  220. // Find first available IP
  221. for ip := startInt; ip <= endInt; ip++ {
  222. ipBytes := []byte{
  223. byte(ip >> 24),
  224. byte(ip >> 16),
  225. byte(ip >> 8),
  226. byte(ip),
  227. }
  228. ipStr := fmt.Sprintf("%d.%d.%d.%d", ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3])
  229. // Skip gateway and excluded IPs
  230. if ipStr == s.getConfig().Gateway {
  231. continue
  232. }
  233. excluded := false
  234. for _, excl := range s.getConfig().ExcludedIPs {
  235. if ipStr == excl {
  236. excluded = true
  237. break
  238. }
  239. }
  240. if excluded {
  241. continue
  242. }
  243. if !usedIPs[ipStr] {
  244. return ipStr
  245. }
  246. }
  247. return ""
  248. }
  249. func (s *Server) verifyAssignment(clientMAC, ip string) bool {
  250. s.leaseMutex.RLock()
  251. defer s.leaseMutex.RUnlock()
  252. // Check static binding
  253. if binding, exists := s.staticBindings[clientMAC]; exists {
  254. return binding.IP == ip
  255. }
  256. // Check if IP is assigned to this client
  257. if lease, exists := s.leases[clientMAC]; exists {
  258. return lease.IP == ip
  259. }
  260. // IP not in lease map yet — this is a new client that just got an Offer.
  261. // Verify the IP is within the configured pool range and not already taken.
  262. startIP := net.ParseIP(s.getConfig().IPPoolStart).To4()
  263. endIP := net.ParseIP(s.getConfig().IPPoolEnd).To4()
  264. if startIP == nil || endIP == nil {
  265. return false
  266. }
  267. clientIP := net.ParseIP(ip).To4()
  268. if clientIP == nil {
  269. return false
  270. }
  271. clientInt := uint32(clientIP[0])<<24 | uint32(clientIP[1])<<16 | uint32(clientIP[2])<<8 | uint32(clientIP[3])
  272. startInt := uint32(startIP[0])<<24 | uint32(startIP[1])<<16 | uint32(startIP[2])<<8 | uint32(startIP[3])
  273. endInt := uint32(endIP[0])<<24 | uint32(endIP[1])<<16 | uint32(endIP[2])<<8 | uint32(endIP[3])
  274. if clientInt < startInt || clientInt > endInt {
  275. return false
  276. }
  277. // Make sure no other client already has this IP
  278. for mac, lease := range s.leases {
  279. if lease.IP == ip && mac != clientMAC {
  280. return false
  281. }
  282. }
  283. return true
  284. }
  285. func (s *Server) recordLease(clientMAC, ip string, data []byte) {
  286. lease := &db.DHCPLease{
  287. MAC: clientMAC,
  288. IP: ip,
  289. ExpiresAt: time.Now().Add(time.Duration(s.getConfig().LeaseTime) * time.Second).Unix(),
  290. }
  291. // Try to get hostname from DHCP options
  292. if hostname := parseHostname(data); hostname != "" {
  293. lease.Hostname = hostname
  294. }
  295. s.leaseMutex.Lock()
  296. // Check if lease already exists (e.g. from Offer phase)
  297. existingLease, alreadyExists := s.leases[clientMAC]
  298. s.leases[clientMAC] = lease
  299. s.usedIPs[ip] = clientMAC
  300. s.leaseMutex.Unlock()
  301. // Save to database — update if already exists to avoid duplicates
  302. if alreadyExists && existingLease.ID > 0 {
  303. lease.ID = existingLease.ID
  304. s.db.Save(lease)
  305. } else {
  306. s.db.Create(lease)
  307. }
  308. }
  309. func (s *Server) sendOffer(data []byte, clientMAC, offeredIP string, remoteAddr *net.UDPAddr) {
  310. // Build DHCP OFFER message
  311. response := buildDHCPMessage(MsgOffer, data, offeredIP, s.config)
  312. // Add options
  313. response = appendOption(response, OptionSubnetMask, []byte(net.ParseIP(s.getConfig().Netmask).To4()))
  314. response = appendOption(response, OptionRouter, []byte(net.ParseIP(s.getConfig().Gateway).To4()))
  315. // Add DNS servers
  316. if len(s.getConfig().DNSServers) > 0 {
  317. var dnsBytes []byte
  318. for _, dns := range s.getConfig().DNSServers {
  319. dnsBytes = append(dnsBytes, net.ParseIP(dns).To4()...)
  320. }
  321. response = appendOption(response, OptionDNS, dnsBytes)
  322. }
  323. // Add lease time
  324. leaseTime := make([]byte, 4)
  325. binary.BigEndian.PutUint32(leaseTime, uint32(s.getConfig().LeaseTime))
  326. response = appendOption(response, OptionLeaseTime, leaseTime)
  327. // Add server identifier
  328. response = appendOption(response, OptionServerIdentifier, []byte(s.getServerIP().To4()))
  329. // Add end option
  330. response = append(response, OptionEnd)
  331. // Send response — use broadcast if client has no IP yet (ciaddr == 0.0.0.0)
  332. targetAddr := s.getResponseAddr(data, remoteAddr)
  333. s.conn.WriteToUDP(response, targetAddr)
  334. }
  335. func (s *Server) sendACK(data []byte, clientMAC, ip string, remoteAddr *net.UDPAddr) {
  336. response := buildDHCPMessage(MsgACK, data, ip, s.config)
  337. // Add options
  338. response = appendOption(response, OptionSubnetMask, []byte(net.ParseIP(s.getConfig().Netmask).To4()))
  339. response = appendOption(response, OptionRouter, []byte(net.ParseIP(s.getConfig().Gateway).To4()))
  340. if len(s.getConfig().DNSServers) > 0 {
  341. var dnsBytes []byte
  342. for _, dns := range s.getConfig().DNSServers {
  343. dnsBytes = append(dnsBytes, net.ParseIP(dns).To4()...)
  344. }
  345. response = appendOption(response, OptionDNS, dnsBytes)
  346. }
  347. leaseTime := make([]byte, 4)
  348. binary.BigEndian.PutUint32(leaseTime, uint32(s.getConfig().LeaseTime))
  349. response = appendOption(response, OptionLeaseTime, leaseTime)
  350. response = appendOption(response, OptionServerIdentifier, []byte(s.getServerIP().To4()))
  351. response = append(response, OptionEnd)
  352. targetAddr := s.getResponseAddr(data, remoteAddr)
  353. s.conn.WriteToUDP(response, targetAddr)
  354. }
  355. func (s *Server) sendNAK(data []byte, remoteAddr *net.UDPAddr) {
  356. response := buildDHCPMessage(MsgNAK, data, "0.0.0.0", s.config)
  357. response = appendOption(response, OptionServerIdentifier, []byte(s.getServerIP().To4()))
  358. response = append(response, OptionEnd)
  359. // NAK must always be broadcast
  360. broadcastAddr := &net.UDPAddr{IP: net.IPv4bcast, Port: 68}
  361. s.conn.WriteToUDP(response, broadcastAddr)
  362. }
  363. // getResponseAddr determines where to send the DHCP response.
  364. // Per RFC 2131: if ciaddr is 0.0.0.0, broadcast to 255.255.255.255:68.
  365. // Otherwise unicast to ciaddr:68.
  366. func (s *Server) getResponseAddr(data []byte, remoteAddr *net.UDPAddr) *net.UDPAddr {
  367. if len(data) < 28 {
  368. return remoteAddr
  369. }
  370. // ciaddr is at bytes 24-27
  371. ciaddr := net.IP(data[24:28])
  372. if ciaddr.Equal(net.IPv4zero) || ciaddr.IsUnspecified() {
  373. // Client has no IP yet — broadcast
  374. return &net.UDPAddr{IP: net.IPv4bcast, Port: 68}
  375. }
  376. // Client already has an IP — unicast
  377. return &net.UDPAddr{IP: ciaddr, Port: 68}
  378. }
  379. func (s *Server) loadLeases() {
  380. leases, err := s.db.GetActiveLeases()
  381. if err != nil {
  382. return
  383. }
  384. s.leaseMutex.Lock()
  385. defer s.leaseMutex.Unlock()
  386. for _, lease := range leases {
  387. s.leases[lease.MAC] = &lease
  388. s.usedIPs[lease.IP] = lease.MAC
  389. }
  390. }
  391. func (s *Server) loadStaticBindings() {
  392. bindings, err := s.db.GetStaticBindings()
  393. if err != nil {
  394. return
  395. }
  396. for _, binding := range bindings {
  397. s.staticBindings[binding.MAC] = binding
  398. }
  399. }
  400. func (s *Server) cleanupLeases() {
  401. ticker := time.NewTicker(1 * time.Minute)
  402. defer ticker.Stop()
  403. for {
  404. select {
  405. case <-ticker.C:
  406. s.leaseMutex.Lock()
  407. now := time.Now().Unix()
  408. for mac, lease := range s.leases {
  409. if lease.ExpiresAt < now {
  410. delete(s.leases, mac)
  411. delete(s.usedIPs, lease.IP)
  412. log.Printf("DHCP: Lease expired for %s (%s)", mac, lease.IP)
  413. }
  414. }
  415. s.leaseMutex.Unlock()
  416. case <-s.stopChan:
  417. return
  418. }
  419. }
  420. }
  421. func (s *Server) GetLeases() []db.DHCPLease {
  422. s.leaseMutex.RLock()
  423. defer s.leaseMutex.RUnlock()
  424. leases := make([]db.DHCPLease, 0, len(s.leases))
  425. for _, lease := range s.leases {
  426. leases = append(leases, *lease)
  427. }
  428. return leases
  429. }
  430. func (s *Server) CreateStaticBinding(mac, ip, hostname, description string) error {
  431. binding := db.DHCPStaticBinding{
  432. MAC: mac,
  433. IP: ip,
  434. Hostname: hostname,
  435. Description: description,
  436. Enabled: true,
  437. }
  438. err := s.db.Create(&binding).Error
  439. if err == nil {
  440. s.staticBindings[mac] = binding
  441. }
  442. return err
  443. }
  444. func (s *Server) DeleteStaticBinding(id uint) error {
  445. // Get binding first to remove from cache
  446. var binding db.DHCPStaticBinding
  447. s.db.First(&binding, id)
  448. delete(s.staticBindings, binding.MAC)
  449. return s.db.Delete(&db.DHCPStaticBinding{}, id).Error
  450. }
  451. func (s *Server) GetStaticBindings() ([]db.DHCPStaticBinding, error) {
  452. return s.db.GetStaticBindings()
  453. }
  454. // Helper functions
  455. func parseMessageType(data []byte) byte {
  456. // DHCP message type is option 53
  457. for i := 240; i < len(data)-1; i++ {
  458. if data[i] == OptionMessageType && i+2 < len(data) {
  459. return data[i+2]
  460. }
  461. if data[i] == OptionEnd {
  462. break
  463. }
  464. }
  465. return 0
  466. }
  467. func parseRequestedIP(data []byte) net.IP {
  468. // DHCP requested IP is option 50
  469. for i := 240; i < len(data)-1; i++ {
  470. if data[i] == OptionRequestedIP && i+5 < len(data) && data[i+1] == 4 {
  471. return net.IP(data[i+2 : i+6])
  472. }
  473. if data[i] == OptionEnd {
  474. break
  475. }
  476. }
  477. return nil
  478. }
  479. func parseHostname(data []byte) string {
  480. // DHCP hostname is option 12
  481. for i := 240; i < len(data)-1; i++ {
  482. if data[i] == OptionHostname && i+2 < len(data) {
  483. length := int(data[i+1])
  484. if i+2+length <= len(data) {
  485. return string(data[i+2 : i+2+length])
  486. }
  487. }
  488. if data[i] == OptionEnd {
  489. break
  490. }
  491. }
  492. return ""
  493. }
  494. func formatMAC(mac []byte) string {
  495. return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
  496. mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
  497. }
  498. func buildDHCPMessage(msgType byte, request []byte, ip string, cfg *config.DHCPConfig) []byte {
  499. response := make([]byte, 240)
  500. // Copy operation, hardware type, hardware address length, hops
  501. response[0] = 2 // BOOTREPLY
  502. response[1] = request[1] // hardware type
  503. response[2] = request[2] // hardware address length
  504. // Copy transaction ID
  505. copy(response[4:8], request[4:8])
  506. // Set offered IP
  507. ipBytes := net.ParseIP(ip).To4()
  508. copy(response[16:20], ipBytes)
  509. // Set server IP
  510. serverIP := net.ParseIP(cfg.Gateway).To4()
  511. copy(response[128:132], serverIP)
  512. // Copy client MAC
  513. copy(response[28:34], request[28:34])
  514. // DHCP magic cookie
  515. response[236] = 99
  516. response[237] = 130
  517. response[238] = 83
  518. response[239] = 99
  519. // Add message type option
  520. response = append(response, OptionMessageType)
  521. response = append(response, 1) // length
  522. response = append(response, msgType)
  523. return response
  524. }
  525. func appendOption(data []byte, option byte, value []byte) []byte {
  526. data = append(data, option)
  527. data = append(data, byte(len(value)))
  528. data = append(data, value...)
  529. return data
  530. }
  531. // IP 地址管理工具函数
  532. func IPInRange(ip, start, end string) bool {
  533. ipAddr := net.ParseIP(ip)
  534. startAddr := net.ParseIP(start)
  535. endAddr := net.ParseIP(end)
  536. if ipAddr == nil || startAddr == nil || endAddr == nil {
  537. return false
  538. }
  539. ipBytes := ipAddr.To4()
  540. startBytes := startAddr.To4()
  541. endBytes := endAddr.To4()
  542. if ipBytes == nil || startBytes == nil || endBytes == nil {
  543. return false
  544. }
  545. ipInt := uint32(ipBytes[0])<<24 | uint32(ipBytes[1])<<16 | uint32(ipBytes[2])<<8 | uint32(ipBytes[3])
  546. startInt := uint32(startBytes[0])<<24 | uint32(startBytes[1])<<16 | uint32(startBytes[2])<<8 | uint32(startBytes[3])
  547. endInt := uint32(endBytes[0])<<24 | uint32(endBytes[1])<<16 | uint32(endBytes[2])<<8 | uint32(endBytes[3])
  548. return ipInt >= startInt && ipInt <= endInt
  549. }
  550. // newBroadcastUDPConn creates a UDP listener on the given host:port with SO_BROADCAST enabled.
  551. // This is required for DHCP because responses to clients without an IP must be sent to
  552. // the broadcast address (255.255.255.255:68).
  553. func newBroadcastUDPConn(host string, port int) (*net.UDPConn, error) {
  554. // Create a raw UDP socket so we can set SO_BROADCAST
  555. sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
  556. if err != nil {
  557. return nil, fmt.Errorf("failed to create socket: %v", err)
  558. }
  559. // Enable SO_BROADCAST so we can send to 255.255.255.255
  560. if err := unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_BROADCAST, 1); err != nil {
  561. unix.Close(sock)
  562. return nil, fmt.Errorf("failed to set SO_BROADCAST: %v", err)
  563. }
  564. // Bind to the address
  565. sa := &unix.SockaddrInet4{Port: port}
  566. copy(sa.Addr[:], net.ParseIP(host).To4())
  567. if err := unix.Bind(sock, sa); err != nil {
  568. unix.Close(sock)
  569. return nil, fmt.Errorf("failed to bind: %v", err)
  570. }
  571. // Wrap the raw socket in a net.UDPConn
  572. file := os.NewFile(uintptr(sock), fmt.Sprintf("udp-%s-%d", host, port))
  573. conn, err := net.FileConn(file)
  574. if err != nil {
  575. unix.Close(sock)
  576. return nil, fmt.Errorf("failed to wrap socket: %v", err)
  577. }
  578. udpConn, ok := conn.(*net.UDPConn)
  579. if !ok {
  580. conn.Close()
  581. return nil, fmt.Errorf("not a UDP connection")
  582. }
  583. return udpConn, nil
  584. }
  585. // EvictClient removes a client's lease, forcing them to get a new IP
  586. func (s *Server) EvictClient(mac string) error {
  587. s.leaseMutex.Lock()
  588. defer s.leaseMutex.Unlock()
  589. lease, exists := s.leases[mac]
  590. if !exists {
  591. return fmt.Errorf("lease not found for MAC %s", mac)
  592. }
  593. // Remove from in-memory lease map
  594. delete(s.leases, mac)
  595. delete(s.usedIPs, lease.IP)
  596. // Delete from database
  597. if err := s.db.Where("mac = ?", mac).Delete(&db.DHCPLease{}).Error; err != nil {
  598. return fmt.Errorf("failed to delete lease from database: %v", err)
  599. }
  600. log.Printf("DHCP: Evicted client %s (%s)", mac, lease.IP)
  601. return nil
  602. }