156 lines
3.5 KiB
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
|
|
}
|
|
}
|
|
}
|