This makes it possible to run multiple instances on the same box, all receiving local discovery packets. Tested on Mac, Windows, supposed to work on at least Linux too. For Windows, there may be issues with XP and earlier, but meh...
109 lines
2.1 KiB
Go
109 lines
2.1 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package beacon
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
|
|
"golang.org/x/net/ipv6"
|
|
)
|
|
|
|
type Multicast struct {
|
|
conn *ipv6.PacketConn
|
|
addr *net.UDPAddr
|
|
inbox chan []byte
|
|
outbox chan recv
|
|
intfs []net.Interface
|
|
}
|
|
|
|
func NewMulticast(addr string) (*Multicast, error) {
|
|
gaddr, err := net.ResolveUDPAddr("udp6", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := net.ListenPacket("udp6", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
intfs, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := ipv6.NewPacketConn(conn)
|
|
joined := 0
|
|
for _, intf := range intfs {
|
|
err := p.JoinGroup(&intf, &net.UDPAddr{IP: gaddr.IP})
|
|
if debug {
|
|
if err != nil {
|
|
l.Debugln("IPv6 join", intf.Name, "failed:", err)
|
|
} else {
|
|
l.Debugln("IPv6 join", intf.Name, "success")
|
|
}
|
|
}
|
|
joined++
|
|
}
|
|
|
|
if joined == 0 {
|
|
return nil, errors.New("no multicast interfaces available")
|
|
}
|
|
|
|
b := &Multicast{
|
|
conn: p,
|
|
addr: gaddr,
|
|
inbox: make(chan []byte),
|
|
outbox: make(chan recv, 16),
|
|
intfs: intfs,
|
|
}
|
|
|
|
go genericReader(ipv6ReaderAdapter{b.conn}, b.outbox)
|
|
go b.writer()
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *Multicast) Send(data []byte) {
|
|
b.inbox <- data
|
|
}
|
|
|
|
func (b *Multicast) Recv() ([]byte, net.Addr) {
|
|
recv := <-b.outbox
|
|
return recv.data, recv.src
|
|
}
|
|
|
|
func (b *Multicast) writer() {
|
|
wcm := &ipv6.ControlMessage{
|
|
HopLimit: 1,
|
|
}
|
|
|
|
for bs := range b.inbox {
|
|
for _, intf := range b.intfs {
|
|
wcm.IfIndex = intf.Index
|
|
_, err := b.conn.WriteTo(bs, wcm, b.addr)
|
|
if err != nil && debug {
|
|
l.Debugln(err, "on write to", b.addr)
|
|
} else if debug {
|
|
l.Debugf("sent %d bytes to %v on %s", len(bs), b.addr, intf.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This makes ReadFrom on an *ipv6.PacketConn behave like ReadFrom on a
|
|
// net.PacketConn.
|
|
type ipv6ReaderAdapter struct {
|
|
c *ipv6.PacketConn
|
|
}
|
|
|
|
func (i ipv6ReaderAdapter) ReadFrom(bs []byte) (int, net.Addr, error) {
|
|
n, _, src, err := i.c.ReadFrom(bs)
|
|
return n, src, err
|
|
}
|