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

156 lines
3.5 KiB
Go

package multicast
import (
"errors"
"io"
"net"
"strings"
"time"
"github.com/koron/go-ssdp/internal/ssdplog"
"golang.org/x/net/ipv4"
)
// Conn is multicast connection.
type Conn struct {
laddr *net.UDPAddr
conn *net.UDPConn
pconn *ipv4.PacketConn
iflist []net.Interface
}
// Listen starts to receiving multicast messages.
func Listen(r *AddrResolver) (*Conn, 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 &Conn{
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 := SendAddr()
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 {
ssdplog.Printf("failed to join group %s on %s: %s", gaddr.String(), ifi.Name, err)
continue
}
joined++
ssdplog.Printf("joined group %s on %s (#%d)", gaddr.String(), ifi.Name, ifi.Index)
}
if joined == 0 {
return nil, errors.New("no interfaces had joined to group")
}
return wrap, nil
}
// Close closes a multicast connection.
func (mc *Conn) Close() error {
if err := mc.pconn.Close(); err != nil {
return err
}
// mc.conn is closed by mc.pconn.Close()
return nil
}
// DataProvider provides a body of multicast message to send.
type DataProvider interface {
Bytes(*net.Interface) []byte
}
//type multicastDataProviderFunc func(*net.Interface) []byte
//
//func (f multicastDataProviderFunc) Bytes(ifi *net.Interface) []byte {
// return f(ifi)
//}
type BytesDataProvider []byte
func (b BytesDataProvider) Bytes(ifi *net.Interface) []byte {
return []byte(b)
}
// WriteTo sends a multicast message to interfaces.
func (mc *Conn) WriteTo(dataProv DataProvider, to net.Addr) (int, error) {
if uaddr, ok := to.(*net.UDPAddr); ok && !uaddr.IP.IsMulticast() {
return mc.conn.WriteTo(dataProv.Bytes(nil), to)
}
sum := 0
for _, ifi := range mc.iflist {
if err := mc.pconn.SetMulticastInterface(&ifi); err != nil {
return 0, err
}
n, err := mc.pconn.WriteTo(dataProv.Bytes(&ifi), nil, to)
if err != nil {
return 0, err
}
sum += n
}
return sum, nil
}
// LocalAddr returns local address to listen multicast packets.
func (mc *Conn) LocalAddr() net.Addr {
return mc.laddr
}
// ReadPackets reads multicast packets.
func (mc *Conn) 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
}
}
}