status-go/vendor/github.com/koron/go-ssdp/multicast.go

129 lines
2.8 KiB
Go

package ssdp
import (
"errors"
"io"
"net"
"strings"
"time"
"golang.org/x/net/ipv4"
)
type multicastConn struct {
laddr *net.UDPAddr
conn *net.UDPConn
pconn *ipv4.PacketConn
iflist []net.Interface
}
func multicastListen(r *udpAddrResolver) (*multicastConn, error) {
// prepare parameters.
laddr, err := r.resolve()
if err != nil {
return nil, err
}
// connect.
conn, err := net.ListenUDP("udp4", laddr)
if err != nil {
return nil, err
}
// configure socket to use with multicast.
pconn, iflist, err := newIPv4MulticastConn(conn)
if err != nil {
conn.Close()
return nil, err
}
return &multicastConn{
laddr: laddr,
conn: conn,
pconn: pconn,
iflist: iflist,
}, nil
}
func newIPv4MulticastConn(conn *net.UDPConn) (*ipv4.PacketConn, []net.Interface, error) {
iflist, err := interfaces()
if err != nil {
return nil, nil, err
}
addr, err := multicastSendAddr()
if err != nil {
return nil, nil, err
}
pconn, err := joinGroupIPv4(conn, iflist, addr)
if err != nil {
return nil, nil, err
}
return pconn, iflist, nil
}
// joinGroupIPv4 makes the connection join to a group on interfaces.
func joinGroupIPv4(conn *net.UDPConn, iflist []net.Interface, gaddr net.Addr) (*ipv4.PacketConn, error) {
wrap := ipv4.NewPacketConn(conn)
wrap.SetMulticastLoopback(true)
// add interfaces to multicast group.
joined := 0
for _, ifi := range iflist {
if err := wrap.JoinGroup(&ifi, gaddr); err != nil {
logf("failed to join group %s on %s: %s", gaddr.String(), ifi.Name, err)
continue
}
joined++
logf("joined group %s on %s", gaddr.String(), ifi.Name)
}
if joined == 0 {
return nil, errors.New("no interfaces had joined to group")
}
return wrap, nil
}
func (mc *multicastConn) Close() error {
if err := mc.pconn.Close(); err != nil {
return err
}
// mc.conn is closed by mc.pconn.Close()
return nil
}
func (mc *multicastConn) WriteTo(data []byte, to net.Addr) (int, error) {
if uaddr, ok := to.(*net.UDPAddr); ok && !uaddr.IP.IsMulticast() {
return mc.conn.WriteTo(data, to)
}
for _, ifi := range mc.iflist {
if err := mc.pconn.SetMulticastInterface(&ifi); err != nil {
return 0, err
}
if _, err := mc.pconn.WriteTo(data, nil, to); err != nil {
return 0, err
}
}
return len(data), nil
}
func (mc *multicastConn) LocalAddr() net.Addr {
return mc.laddr
}
func (mc *multicastConn) readPackets(timeout time.Duration, h packetHandler) error {
buf := make([]byte, 65535)
if timeout > 0 {
mc.pconn.SetReadDeadline(time.Now().Add(timeout))
}
for {
n, _, addr, err := mc.pconn.ReadFrom(buf)
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return nil
}
if strings.Contains(err.Error(), "use of closed network connection") {
return io.EOF
}
return err
}
if err := h(addr, buf[:n]); err != nil {
return err
}
}
}