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  }