server.go 18 KB

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