Using mDNS to discover remotes
This commit is contained in:
parent
dd50882b20
commit
95a28723fd
@ -1,3 +1,13 @@
|
||||
module musique/server
|
||||
|
||||
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
|
||||
)
|
||||
|
@ -1,23 +1,45 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM=
|
||||
github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs=
|
||||
github.com/schollz/progressbar/v3 v3.12.2 h1:yLqqqpQNMxGxHY8uEshRihaHWwa0rf0yb7/Zrpgq2C0=
|
||||
github.com/schollz/progressbar/v3 v3.12.2/go.mod h1:HFJYIYQQJX32UJdyoigUl19xoV6aMwZt6iX/C30RWfg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/RobertBendun/zeroconf/v2 v2.0.0-20230102034354-649340f2f3b6 h1:u4H25RhCTadMtZmrvcS5ze8qUOBQ20gAoTJ4qzvp8hs=
|
||||
github.com/RobertBendun/zeroconf/v2 v2.0.0-20230102034354-649340f2f3b6/go.mod h1:KcFfULkjW8Z9cUQxr3MlM8aZ/SKMB5IRPUiX75nfe88=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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=
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"log"
|
||||
"musique/server/proto"
|
||||
"musique/server/router"
|
||||
"musique/server/scan"
|
||||
"net"
|
||||
"os"
|
||||
"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() {
|
||||
var (
|
||||
logsPath string
|
||||
@ -320,6 +297,12 @@ func main() {
|
||||
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{}
|
||||
registerRoutes(&r)
|
||||
exit, err := r.Run(baseIP, uint16(port))
|
||||
@ -327,7 +310,7 @@ func main() {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err := registerRemotes(); err != nil {
|
||||
if err := registerRemotes(5); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
|
88
server/mdns.go
Normal file
88
server/mdns.go
Normal 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)
|
||||
}
|
@ -9,6 +9,11 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
initialWaitingTime = 3
|
||||
userRequestedWatingingTime = 5
|
||||
)
|
||||
|
||||
//export ServerInit
|
||||
func ServerInit(inputNick string, inputPort int) {
|
||||
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)
|
||||
}
|
||||
|
||||
if err := registerRemotes(); err != nil {
|
||||
if err := registerRemotes(initialWaitingTime); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
@ -51,7 +56,7 @@ func ServerBeginProtocol() {
|
||||
//export Discover
|
||||
func Discover() {
|
||||
if len(remotes) == 0 {
|
||||
if err := registerRemotes(); err != nil {
|
||||
if err := registerRemotes(userRequestedWatingingTime); err != nil {
|
||||
log.Println("discover:", err)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user