mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-30 07:05:06 +03:00
Initial pass at mDNS support (receiving does not work yet)
This commit is contained in:
parent
dd548fc0fa
commit
8d00461cf8
3 changed files with 350 additions and 2 deletions
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/mdns"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/module"
|
"github.com/yggdrasil-network/yggdrasil-go/src/module"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
||||||
|
@ -37,6 +38,7 @@ type node struct {
|
||||||
state *config.NodeState
|
state *config.NodeState
|
||||||
tuntap module.Module // tuntap.TunAdapter
|
tuntap module.Module // tuntap.TunAdapter
|
||||||
multicast module.Module // multicast.Multicast
|
multicast module.Module // multicast.Multicast
|
||||||
|
mdns module.Module // mdns.MDNS
|
||||||
admin module.Module // admin.AdminSocket
|
admin module.Module // admin.AdminSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,6 +285,7 @@ func main() {
|
||||||
// Allocate our modules
|
// Allocate our modules
|
||||||
n.admin = &admin.AdminSocket{}
|
n.admin = &admin.AdminSocket{}
|
||||||
n.multicast = &multicast.Multicast{}
|
n.multicast = &multicast.Multicast{}
|
||||||
|
n.mdns = &mdns.MDNS{}
|
||||||
n.tuntap = &tuntap.TunAdapter{}
|
n.tuntap = &tuntap.TunAdapter{}
|
||||||
// Start the admin socket
|
// Start the admin socket
|
||||||
n.admin.Init(&n.core, n.state, logger, nil)
|
n.admin.Init(&n.core, n.state, logger, nil)
|
||||||
|
@ -296,6 +299,12 @@ func main() {
|
||||||
logger.Errorln("An error occurred starting multicast:", err)
|
logger.Errorln("An error occurred starting multicast:", err)
|
||||||
}
|
}
|
||||||
n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
||||||
|
// Start the mDNS interface
|
||||||
|
n.mdns.Init(&n.core, n.state, logger, nil)
|
||||||
|
if err := n.mdns.Start(); err != nil {
|
||||||
|
logger.Errorln("An error occurred starting mDNS:", err)
|
||||||
|
}
|
||||||
|
n.mdns.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
||||||
// Start the TUN/TAP interface
|
// Start the TUN/TAP interface
|
||||||
if listener, err := n.core.ConnListen(); err == nil {
|
if listener, err := n.core.ConnListen(); err == nil {
|
||||||
if dialer, err := n.core.ConnDialer(); err == nil {
|
if dialer, err := n.core.ConnDialer(); err == nil {
|
||||||
|
@ -337,6 +346,7 @@ func main() {
|
||||||
n.core.UpdateConfig(cfg)
|
n.core.UpdateConfig(cfg)
|
||||||
n.tuntap.UpdateConfig(cfg)
|
n.tuntap.UpdateConfig(cfg)
|
||||||
n.multicast.UpdateConfig(cfg)
|
n.multicast.UpdateConfig(cfg)
|
||||||
|
n.mdns.UpdateConfig(cfg)
|
||||||
} else {
|
} else {
|
||||||
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
|
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
|
||||||
}
|
}
|
||||||
|
|
333
src/mdns/mdns.go
Normal file
333
src/mdns/mdns.go
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
package mdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
|
"github.com/gologme/log"
|
||||||
|
"github.com/grandcat/zeroconf"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MDNSService = "_yggdrasil._tcp"
|
||||||
|
MDNSDomain = "yggdrasil.local."
|
||||||
|
)
|
||||||
|
|
||||||
|
type MDNS struct {
|
||||||
|
phony.Inbox
|
||||||
|
core *yggdrasil.Core //
|
||||||
|
config *config.NodeState //
|
||||||
|
log *log.Logger //
|
||||||
|
info []string //
|
||||||
|
instance string //
|
||||||
|
_running bool //
|
||||||
|
_exprs []*regexp.Regexp //
|
||||||
|
_servers map[string]map[string]*mDNSServer // intf -> ip -> *mDNSServer
|
||||||
|
_resolver *zeroconf.Resolver //
|
||||||
|
_context context.Context // used by _resolver
|
||||||
|
_cancel context.CancelFunc // used by _resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type mDNSServer struct {
|
||||||
|
intf net.Interface
|
||||||
|
server *zeroconf.Server
|
||||||
|
listener *yggdrasil.TcpListener
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
|
||||||
|
m.core = core
|
||||||
|
m.config = state
|
||||||
|
m.log = log
|
||||||
|
m.info = []string{
|
||||||
|
fmt.Sprintf("ed25519=%s", core.SigningPublicKey()),
|
||||||
|
fmt.Sprintf("curve25519=%s", core.EncryptionPublicKey()),
|
||||||
|
fmt.Sprintf("versionmajor=%d", yggdrasil.ProtocolMajorVersion),
|
||||||
|
fmt.Sprintf("versionminor=%d", yggdrasil.ProtocolMinorVersion),
|
||||||
|
}
|
||||||
|
if nodeid := core.NodeID(); nodeid != nil {
|
||||||
|
m.instance = hex.EncodeToString((*nodeid)[:])[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
current := m.config.GetCurrent()
|
||||||
|
m._updateConfig(¤t)
|
||||||
|
|
||||||
|
m._context, m._cancel = context.WithCancel(context.Background())
|
||||||
|
m._servers = make(map[string]map[string]*mDNSServer)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) listen(results <-chan *zeroconf.ServiceEntry) {
|
||||||
|
m.log.Info("Listening for other Yggdrasil nodes via mDNS")
|
||||||
|
for entry := range results {
|
||||||
|
log.Info("Received mDNS entry:", entry)
|
||||||
|
}
|
||||||
|
m.log.Info("No longer listening for other Yggdrasil nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) Start() error {
|
||||||
|
var err error
|
||||||
|
phony.Block(m, func() {
|
||||||
|
err = m._start()
|
||||||
|
})
|
||||||
|
m.log.Infoln("Started mDNS module")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _start() error {
|
||||||
|
var err error
|
||||||
|
if m._running {
|
||||||
|
return errors.New("mDNS module is already running")
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver, err := zeroconf.NewResolver(nil)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Fatalln("Failed to initialize resolver:", err.Error())
|
||||||
|
}
|
||||||
|
m._resolver = resolver
|
||||||
|
incoming := make(chan *zeroconf.ServiceEntry)
|
||||||
|
go m.listen(incoming)
|
||||||
|
|
||||||
|
err = m._resolver.Browse(m._context, MDNSService, MDNSDomain, incoming)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Fatalln("Failed to browse:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
m._running = true
|
||||||
|
m.Act(m, m._updateInterfaces)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) Stop() error {
|
||||||
|
var err error
|
||||||
|
phony.Block(m, func() {
|
||||||
|
err = m._stop()
|
||||||
|
})
|
||||||
|
m.log.Infoln("Stopped mDNS module")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _stop() error {
|
||||||
|
m._cancel()
|
||||||
|
for _, intf := range m._servers {
|
||||||
|
for _, ip := range intf {
|
||||||
|
ip.server.Shutdown()
|
||||||
|
ip.listener.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) UpdateConfig(config *config.NodeConfig) {
|
||||||
|
var err error
|
||||||
|
phony.Block(m, func() {
|
||||||
|
err = m._updateConfig(config)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
m.log.Warnf("Failed to update mDNS config: %s", err)
|
||||||
|
} else {
|
||||||
|
m.log.Infof("mDNS configuration updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _updateConfig(config *config.NodeConfig) error {
|
||||||
|
// Now get a list of interface expressions from the
|
||||||
|
// config. This will dictate which interfaces we are
|
||||||
|
// allowed to use.
|
||||||
|
var exprs []*regexp.Regexp
|
||||||
|
// Compile each regular expression
|
||||||
|
for _, exstr := range config.MulticastInterfaces {
|
||||||
|
e, err := regexp.Compile(exstr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exprs = append(exprs, e)
|
||||||
|
}
|
||||||
|
// Update our expression list.
|
||||||
|
m._exprs = exprs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) SetupAdminHandlers(a *admin.AdminSocket) {}
|
||||||
|
|
||||||
|
func (m *MDNS) IsStarted() bool {
|
||||||
|
var running bool
|
||||||
|
phony.Block(m, func() {
|
||||||
|
running = m._running
|
||||||
|
})
|
||||||
|
return running
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _updateInterfaces() {
|
||||||
|
// Start by getting a list of interfaces from the
|
||||||
|
// operating system.
|
||||||
|
interfaces := make(map[string]map[string]net.Interface)
|
||||||
|
osInterfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Now work through the OS interfaces and work out
|
||||||
|
// which are suitable.
|
||||||
|
for _, intf := range osInterfaces {
|
||||||
|
if intf.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if intf.Flags&net.FlagMulticast == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if intf.Flags&net.FlagPointToPoint != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, expr := range m._exprs {
|
||||||
|
// Does the interface match the regular expression? Store it if so
|
||||||
|
if expr.MatchString(intf.Name) {
|
||||||
|
// We should now work out if there are any good candidate
|
||||||
|
// IP addresses on this interface. We will only store it if
|
||||||
|
// there are.
|
||||||
|
addrs, err := intf.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip, _, err := net.ParseCIDR(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip.IsLinkLocalUnicast() {
|
||||||
|
if _, ok := interfaces[intf.Name]; !ok {
|
||||||
|
interfaces[intf.Name] = make(map[string]net.Interface)
|
||||||
|
}
|
||||||
|
interfaces[intf.Name][ip.String()] = intf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work out which interfaces are new.
|
||||||
|
for n, addrs := range interfaces {
|
||||||
|
if _, ok := m._servers[n]; !ok {
|
||||||
|
for addr, intf := range addrs {
|
||||||
|
if err := m._startInterface(intf, addr); err != nil {
|
||||||
|
m.log.Errorf("Failed to start mDNS interface %s on address %s: %s", n, addr, err)
|
||||||
|
} else {
|
||||||
|
m.log.Infof("mDNS started on interface %s address %s", n, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work out which interfaces have disappeared.
|
||||||
|
for n, _ := range m._servers {
|
||||||
|
if addrs, ok := interfaces[n]; !ok {
|
||||||
|
for addr, server := range m._servers[n] {
|
||||||
|
if err := m._stopInterface(server.intf, addr); err != nil {
|
||||||
|
m.log.Errorf("Failed to stop mDNS interface %s on address %s: %s", n, addr, err)
|
||||||
|
} else {
|
||||||
|
m.log.Infof("mDNS stopped on interface %s address %s", n, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for addr := range addrs {
|
||||||
|
if intf, ok := interfaces[n][addr]; !ok {
|
||||||
|
if err := m._stopInterface(intf, addr); err != nil {
|
||||||
|
m.log.Errorf("Failed to stop mDNS interface %s on address %s: %s", n, addr, err)
|
||||||
|
} else {
|
||||||
|
m.log.Infof("mDNS stopped on interface %s address %s", n, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.AfterFunc(time.Second, func() {
|
||||||
|
m.Act(m, m._updateInterfaces)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _startInterface(intf net.Interface, addr string) error {
|
||||||
|
// Construct a listener on this address.
|
||||||
|
// Work out what the listen address of the new TCP listener should be.
|
||||||
|
ip := net.ParseIP(addr)
|
||||||
|
listenaddr := fmt.Sprintf("[%s%%%s]:%d", ip, intf.Name, 0) // TODO: linklocalport
|
||||||
|
listener, err := m.core.ListenTCP(listenaddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("m.core.ListenTCP: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve it as a TCP endpoint so that we can get the IP address and
|
||||||
|
// port separately.
|
||||||
|
tcpaddr, err := net.ResolveTCPAddr(
|
||||||
|
listener.Listener.Addr().Network(),
|
||||||
|
listener.Listener.Addr().String(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("net.ResolveTCPAddr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a proxy service. This allows us to specify the hostname and
|
||||||
|
// IP addresses to put into the DNS SRV record.
|
||||||
|
server, err := zeroconf.RegisterProxy(
|
||||||
|
m.instance, // instance name
|
||||||
|
MDNSService, // service name
|
||||||
|
MDNSDomain, // service domain
|
||||||
|
tcpaddr.Port, // TCP listener port
|
||||||
|
m.instance, // our hostname
|
||||||
|
[]string{tcpaddr.IP.String()}, // our IP address
|
||||||
|
m.info, // TXT record contents
|
||||||
|
[]net.Interface{intf}, // interfaces to use
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zeroconf.RegisterProxy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now store information about our new listener and server.
|
||||||
|
if _, ok := m._servers[intf.Name]; !ok {
|
||||||
|
m._servers[intf.Name] = make(map[string]*mDNSServer)
|
||||||
|
}
|
||||||
|
m._servers[intf.Name][addr] = &mDNSServer{
|
||||||
|
intf: intf,
|
||||||
|
server: server,
|
||||||
|
listener: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MDNS) _stopInterface(intf net.Interface, addr string) error {
|
||||||
|
// Check if we know about the interface.
|
||||||
|
addrs, aok := m._servers[intf.Name]
|
||||||
|
if !aok {
|
||||||
|
return fmt.Errorf("interface %q not found", intf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we know about the address on that interface.
|
||||||
|
server, sok := addrs[addr]
|
||||||
|
if !sok {
|
||||||
|
return fmt.Errorf("address %q not found", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shut down the mDNS server and the TCP listener.
|
||||||
|
server.server.Shutdown()
|
||||||
|
server.listener.Stop()
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
delete(m._servers[intf.Name], addr)
|
||||||
|
if len(m._servers[intf.Name]) == 0 {
|
||||||
|
delete(m._servers, intf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,6 +6,11 @@ package yggdrasil
|
||||||
|
|
||||||
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolMajorVersion uint64 = 0 // Major version number of the protocol.
|
||||||
|
ProtocolMinorVersion uint64 = 2 // Minor version number of the protocol.
|
||||||
|
)
|
||||||
|
|
||||||
// This is the version-specific metadata exchanged at the start of a connection.
|
// This is the version-specific metadata exchanged at the start of a connection.
|
||||||
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
|
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
|
||||||
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
|
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
|
||||||
|
@ -23,8 +28,8 @@ type version_metadata struct {
|
||||||
func version_getBaseMetadata() version_metadata {
|
func version_getBaseMetadata() version_metadata {
|
||||||
return version_metadata{
|
return version_metadata{
|
||||||
meta: [4]byte{'m', 'e', 't', 'a'},
|
meta: [4]byte{'m', 'e', 't', 'a'},
|
||||||
ver: 0,
|
ver: ProtocolMajorVersion,
|
||||||
minorVer: 2,
|
minorVer: ProtocolMinorVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue