github.com/bluenviron/gomavlib/v2@v2.2.1-0.20240308101627-2c07e3da629c/endpoint_broadcast.go (about) 1 package gomavlib 2 3 import ( 4 "fmt" 5 "net" 6 "reflect" 7 "strconv" 8 "time" 9 ) 10 11 // ipByBroadcastIP returns the ip of an interface associated with given broadcast ip 12 func ipByBroadcastIP(target net.IP) net.IP { 13 intfs, err := net.Interfaces() 14 if err != nil { 15 return nil 16 } 17 18 for _, intf := range intfs { 19 addrs, err := intf.Addrs() 20 if err != nil { 21 continue 22 } 23 24 for _, addr := range addrs { 25 ipn, ok := addr.(*net.IPNet) 26 if !ok { 27 continue 28 } 29 30 ip := ipn.IP.To4() 31 if ip == nil { 32 continue 33 } 34 35 broadcastIP := net.IP(make([]byte, 4)) 36 for i := range ip { 37 broadcastIP[i] = ip[i] | ^ipn.Mask[i] 38 } 39 if reflect.DeepEqual(broadcastIP, target) { 40 return ip 41 } 42 } 43 } 44 return nil 45 } 46 47 // EndpointUDPBroadcast sets up a endpoint that works with UDP broadcast packets. 48 type EndpointUDPBroadcast struct { 49 // the broadcast address to which sending outgoing frames, example: 192.168.5.255:5600 50 BroadcastAddress string 51 52 // (optional) the listening address. if empty, it will be computed 53 // from the broadcast address. 54 LocalAddress string 55 } 56 57 type endpointUDPBroadcast struct { 58 conf EndpointUDPBroadcast 59 pc net.PacketConn 60 writeTimeout time.Duration 61 broadcastAddr net.Addr 62 63 terminate chan struct{} 64 } 65 66 func (conf EndpointUDPBroadcast) init(node *Node) (Endpoint, error) { 67 ipString, port, err := net.SplitHostPort(conf.BroadcastAddress) 68 if err != nil { 69 return nil, fmt.Errorf("invalid broadcast address") 70 } 71 broadcastIP := net.ParseIP(ipString) 72 if broadcastIP == nil { 73 return nil, fmt.Errorf("invalid IP") 74 } 75 broadcastIP = broadcastIP.To4() 76 if broadcastIP == nil { 77 return nil, fmt.Errorf("invalid IP") 78 } 79 80 if conf.LocalAddress == "" { 81 localIP := ipByBroadcastIP(broadcastIP) 82 if localIP == nil { 83 return nil, fmt.Errorf("cannot find local address associated with given broadcast address") 84 } 85 conf.LocalAddress = fmt.Sprintf("%s:%s", localIP, port) 86 } else { 87 _, _, err = net.SplitHostPort(conf.LocalAddress) 88 if err != nil { 89 return nil, fmt.Errorf("invalid local address") 90 } 91 } 92 93 pc, err := net.ListenPacket("udp4", conf.LocalAddress) 94 if err != nil { 95 return nil, err 96 } 97 98 iport, _ := strconv.Atoi(port) 99 100 t := &endpointUDPBroadcast{ 101 conf: conf, 102 pc: pc, 103 writeTimeout: node.conf.WriteTimeout, 104 broadcastAddr: &net.UDPAddr{IP: broadcastIP, Port: iport}, 105 terminate: make(chan struct{}), 106 } 107 return t, nil 108 } 109 110 func (t *endpointUDPBroadcast) isEndpoint() {} 111 112 func (t *endpointUDPBroadcast) Conf() EndpointConf { 113 return t.conf 114 } 115 116 func (t *endpointUDPBroadcast) label() string { 117 return fmt.Sprintf("udp:%s", t.broadcastAddr) 118 } 119 120 func (t *endpointUDPBroadcast) Close() error { 121 close(t.terminate) 122 t.pc.Close() 123 return nil 124 } 125 126 func (t *endpointUDPBroadcast) Read(buf []byte) (int, error) { 127 // read WITHOUT deadline. Long periods without packets are normal since 128 // we're not directly connected to someone. 129 n, _, err := t.pc.ReadFrom(buf) 130 // wait termination, do not report errors 131 if err != nil { 132 <-t.terminate 133 return 0, errTerminated 134 } 135 136 return n, nil 137 } 138 139 func (t *endpointUDPBroadcast) Write(buf []byte) (int, error) { 140 err := t.pc.SetWriteDeadline(time.Now().Add(t.writeTimeout)) 141 if err != nil { 142 return 0, err 143 } 144 return t.pc.WriteTo(buf, t.broadcastAddr) 145 }