diff --git a/server/go.mod b/server/go.mod index a006ddf..3a1bfbe 100644 --- a/server/go.mod +++ b/server/go.mod @@ -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 +) diff --git a/server/go.sum b/server/go.sum index 87d897d..70bc844 100644 --- a/server/go.sum +++ b/server/go.sum @@ -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= diff --git a/server/main.go b/server/main.go index 8cf9046..af48460 100644 --- a/server/main.go +++ b/server/main.go @@ -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) } diff --git a/server/mdns.go b/server/mdns.go new file mode 100644 index 0000000..3bdea52 --- /dev/null +++ b/server/mdns.go @@ -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) +} diff --git a/server/musique-bridge.go b/server/musique-bridge.go index 05957be..aa2fc7f 100644 --- a/server/musique-bridge.go +++ b/server/musique-bridge.go @@ -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) } } diff --git a/server/scan/scanner.go b/server/scan/scanner.go deleted file mode 100644 index 8b1e3f0..0000000 --- a/server/scan/scanner.go +++ /dev/null @@ -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 -}