Using mDNS to discover remotes

This commit is contained in:
Robert Bendun 2023-01-02 05:47:27 +01:00
parent dd50882b20
commit 95a28723fd
6 changed files with 155 additions and 139 deletions

View File

@ -1,3 +1,13 @@
module musique/server module musique/server
go 1.19 go 1.19
require github.com/RobertBendun/zeroconf/v2 v2.0.0-20230102034354-649340f2f3b6
require (
github.com/miekg/dns v1.1.50 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/tools v0.4.0 // indirect
)

View File

@ -1,23 +1,45 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/RobertBendun/zeroconf/v2 v2.0.0-20230102034354-649340f2f3b6 h1:u4H25RhCTadMtZmrvcS5ze8qUOBQ20gAoTJ4qzvp8hs=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/RobertBendun/zeroconf/v2 v2.0.0-20230102034354-649340f2f3b6/go.mod h1:KcFfULkjW8Z9cUQxr3MlM8aZ/SKMB5IRPUiX75nfe88=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
github.com/schollz/progressbar/v3 v3.12.2 h1:yLqqqpQNMxGxHY8uEshRihaHWwa0rf0yb7/Zrpgq2C0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
github.com/schollz/progressbar/v3 v3.12.2/go.mod h1:HFJYIYQQJX32UJdyoigUl19xoV6aMwZt6iX/C30RWfg= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -7,7 +7,6 @@ import (
"log" "log"
"musique/server/proto" "musique/server/proto"
"musique/server/router" "musique/server/router"
"musique/server/scan"
"net" "net"
"os" "os"
"strings" "strings"
@ -273,28 +272,6 @@ func synchronizeHostsWithRemotes() {
} }
} }
func registerRemotes() error {
networks, err := scan.AvailableNetworks()
if err != nil {
return err
}
hosts := scan.TCPHosts(networks, []uint16{8081, 8082, 8083, 8084})
remotes = make(map[string]*Remote)
for host := range hosts {
if !isThisMyAddress(host.Address) {
remotes[host.Address] = &Remote{
Address: host.Address,
Nick: host.Nick,
Version: host.Version,
}
}
}
return nil
}
func main() { func main() {
var ( var (
logsPath string logsPath string
@ -320,6 +297,12 @@ func main() {
log.Fatalln("Please provide nick via --nick flag") log.Fatalln("Please provide nick via --nick flag")
} }
server, err := registerDNS()
if err != nil {
log.Fatalln("Failed to register DNS:", err)
}
defer server.Shutdown()
r := router.Router{} r := router.Router{}
registerRoutes(&r) registerRoutes(&r)
exit, err := r.Run(baseIP, uint16(port)) exit, err := r.Run(baseIP, uint16(port))
@ -327,7 +310,7 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
if err := registerRemotes(); err != nil { if err := registerRemotes(5); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

88
server/mdns.go Normal file
View File

@ -0,0 +1,88 @@
package main
import (
"context"
"fmt"
"log"
"musique/server/proto"
"net"
"sync"
"time"
"github.com/RobertBendun/zeroconf/v2"
)
func doHandshake(wg *sync.WaitGroup, service *zeroconf.ServiceEntry, remotes chan<- Remote, timeout time.Duration) {
for _, ip := range service.AddrIPv4 {
wg.Add(1)
go func(ip net.IP) {
defer wg.Done()
target := fmt.Sprintf("%s:%d", ip, service.Port)
if isThisMyAddress(target) {
return
}
var hs proto.HandshakeResponse
err := proto.CommandTimeout(target, proto.Handshake(), &hs, timeout)
if err == nil {
log.Println("Received handshake response", target, hs)
remotes <- Remote{
Address: target,
Nick: hs.Nick,
Version: hs.Version,
}
}
}(ip)
}
}
// Register all remotes that cane be found in `waitTime` seconds
func registerRemotes(waitTime int) error {
wg := sync.WaitGroup{}
done := make(chan error, 1)
incomingRemotes := make(chan Remote, 32)
entries := make(chan *zeroconf.ServiceEntry, 12)
timeout := time.Second*time.Duration(waitTime)
wg.Add(1)
go func(results <-chan *zeroconf.ServiceEntry) {
for entry := range results {
log.Println("Found service at", entry.HostName, "at addrs", entry.AddrIPv4)
doHandshake(&wg, entry, incomingRemotes, timeout)
}
wg.Done()
done <- nil
}(entries)
go func() {
wg.Wait()
close(incomingRemotes)
}()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
go func() {
err := zeroconf.Browse(ctx, "_musique._tcp", "local", entries)
if err != nil {
done <- fmt.Errorf("failed to browse: %v", err)
}
<-ctx.Done()
}()
remotes = make(map[string]*Remote)
for remote := range incomingRemotes {
remote := remote
remotes[remote.Address] = &remote
}
return <-done
}
type dnsServer interface {
Shutdown()
}
func registerDNS() (dnsServer, error) {
return zeroconf.Register("Musique", "_musique._tcp", "local", port, []string{}, nil)
}

View File

@ -9,6 +9,11 @@ import (
"sort" "sort"
) )
const (
initialWaitingTime = 3
userRequestedWatingingTime = 5
)
//export ServerInit //export ServerInit
func ServerInit(inputNick string, inputPort int) { func ServerInit(inputNick string, inputPort int) {
logFile, err := os.OpenFile("musique.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o640) logFile, err := os.OpenFile("musique.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o640)
@ -27,7 +32,7 @@ func ServerInit(inputNick string, inputPort int) {
log.Fatalln(err) log.Fatalln(err)
} }
if err := registerRemotes(); err != nil { if err := registerRemotes(initialWaitingTime); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@ -51,7 +56,7 @@ func ServerBeginProtocol() {
//export Discover //export Discover
func Discover() { func Discover() {
if len(remotes) == 0 { if len(remotes) == 0 {
if err := registerRemotes(); err != nil { if err := registerRemotes(userRequestedWatingingTime); err != nil {
log.Println("discover:", err) log.Println("discover:", err)
} }
} }

View File

@ -1,92 +0,0 @@
package scan
import (
"fmt"
"log"
"musique/server/proto"
"net"
"sync"
"time"
)
type Network struct {
FirstAddress string
MaxHostsCount int
}
const timeoutTCPHosts = time.Duration(1) * time.Second
func nextIP(ip net.IP) (next net.IP) {
// FIXME Proper next IP address in network calculation
next = make([]byte, 4)
bytes := []byte(ip)
bytes = bytes[len(bytes)-4:]
next[0], next[1], next[2], next[3] = bytes[0], bytes[1], bytes[2], bytes[3]+1
return
}
// AvailableNetworks returns all IPv4 networks that are available to the host
func AvailableNetworks() ([]Network, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, fmt.Errorf("getting interfaces info: %v", err)
}
networks := []Network{}
for _, addr := range addrs {
_, ipNet, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if ipNet.IP.IsGlobalUnicast() {
if ip := ipNet.IP.To4(); ip != nil {
// FIXME We assume mask /24. This is a reasonable assumption performance wise
// but may lead to inability to recognize some of the host in network
networks = append(networks, Network{nextIP(ipNet.IP).String(), 253})
}
}
}
return networks, nil
}
type Response struct {
proto.HandshakeResponse
Address string
}
// TCPHosts returns all TCP hosts that are in given networks on one of given ports
func TCPHosts(networks []Network, ports []uint16) <-chan Response {
ips := make(chan Response, 256)
log.Printf("tcphosts: %+v\n", networks)
wg := sync.WaitGroup{}
for _, network := range networks {
ip := net.ParseIP(network.FirstAddress)
for i := 0; i < network.MaxHostsCount; i++ {
for _, port := range ports {
wg.Add(1)
go func(ip net.IP, port uint16) {
defer wg.Done()
target := fmt.Sprintf("%s:%d", ip, port)
var hs proto.HandshakeResponse
err := proto.CommandTimeout(target, proto.Handshake(), &hs, timeoutTCPHosts)
if err == nil {
ips <- Response{hs, target}
}
}(ip, port)
}
ip = nextIP(ip)
}
}
go func() {
wg.Wait()
close(ips)
}()
return ips
}