pax_global_header00006660000000000000000000000064146226443140014520gustar00rootroot0000000000000052 comment=9772bf2af051b2b1961be3eb78fd43341dac155d peerdiscovery-1.7.3/000077500000000000000000000000001462264431400144135ustar00rootroot00000000000000peerdiscovery-1.7.3/.github/000077500000000000000000000000001462264431400157535ustar00rootroot00000000000000peerdiscovery-1.7.3/.github/FUNDING.yml000066400000000000000000000000771462264431400175740ustar00rootroot00000000000000# These are supported funding model platforms github: schollz peerdiscovery-1.7.3/.gitignore000066400000000000000000000005661462264431400164120ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ examples/examples examples/ipv4/go.sum examples/ipv4/ipv4 examples/ipv6/go.sum examples/ipv6/ipv6 peerdiscovery-1.7.3/Dockerfile000066400000000000000000000002341462264431400164040ustar00rootroot00000000000000FROM golang WORKDIR /peerdiscovery COPY . . RUN go get github.com/schollz/progressbar/v2 RUN go build ./examples/ipv4/main.go CMD ["/peerdiscovery/main"] peerdiscovery-1.7.3/LICENSE000066400000000000000000000020451462264431400154210ustar00rootroot00000000000000MIT License Copyright (c) 2018 Zack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. peerdiscovery-1.7.3/README.md000066400000000000000000000057201462264431400156760ustar00rootroot00000000000000# peerdiscovery Code coverage Go Report Go Doc Pure-go library for cross-platform thread-safe local peer discovery using UDP multicast. I needed to use peer discovery for [croc](https://github.com/schollz/croc) and everything I tried had problems, so I made another one. ## Install Make sure you have Go 1.5+. ``` go get -u github.com/schollz/peerdiscovery ``` ## Usage The following is a code to find the first peer on the local network and print it out. ```golang discoveries, _ := peerdiscovery.Discover(peerdiscovery.Settings{Limit: 1}) for _, d := range discoveries { fmt.Printf("discovered '%s'\n", d.Address) } ``` Here's the output when running on two computers. (*Run these gifs in sync by hitting Ctl + F5*). **Computer 1:** ![computer 1](https://user-images.githubusercontent.com/6550035/39165714-ba7167d8-473a-11e8-82b5-fb7401ce2138.gif) **Computer 2:** ![computer 1](https://user-images.githubusercontent.com/6550035/39165716-ba8db9ec-473a-11e8-96f7-e8c64faac676.gif) For more examples, see the scanning examples ([ipv4](https://github.com/schollz/peerdiscovery/blob/master/examples/ipv4/main.go) and [ipv6](https://github.com/schollz/peerdiscovery/blob/master/examples/ipv6/main.go)) or [the docs](https://pkg.go.dev/github.com/schollz/peerdiscovery). ## Testing To test the peer discovery with just one host, one can launch multiple containers. The provided `Dockerfile` will run the example code. Please make sure to enable [Docker's IPv6 support](https://docs.docker.com/v17.09/engine/userguide/networking/default_network/ipv6/) if you are using IPv6 for peer discovery. ```console # Build the container, named peertest $ docker build -t peertest . # Execute the following command in multiple terminals $ docker run -t --rm peertest Scanning for 10 seconds to find LAN peers 100% |████████████████████████████████████████| [9s:0s]Found 1 other computers 0) '172.17.0.2' with payload 'zqrecHipCO' ``` ## Contributing Pull requests are welcome. Feel free to... - Revise documentation - Add new features - Fix bugs - Suggest improvements ## Thanks Thanks [@geistesk](https://github.com/geistesk) for adding IPv6 support and a `Notify` func, and helping maintain! Thanks [@Kunde21](https://github.com/Kunde21) for providing a bug fix and massively refactoring the code in a much better way. Thanks [@robpre](https://github.com/robpre) for finding and fixing bugs. Thanks [@shvydky](https://github.com/shvydky) for adding dynamic payloads. ## License MIT peerdiscovery-1.7.3/examples/000077500000000000000000000000001462264431400162315ustar00rootroot00000000000000peerdiscovery-1.7.3/examples/ipv4/000077500000000000000000000000001462264431400171135ustar00rootroot00000000000000peerdiscovery-1.7.3/examples/ipv4/go.mod000066400000000000000000000002611462264431400202200ustar00rootroot00000000000000module ipv4 go 1.15 require ( github.com/schollz/peerdiscovery v1.7.1 github.com/schollz/progressbar/v3 v3.14.0 ) replace github.com/schollz/peerdiscovery v1.7.1 => ../../ peerdiscovery-1.7.3/examples/ipv4/go.sum000066400000000000000000000137251462264431400202560ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/schollz/progressbar/v3 v3.14.0 h1:rFEVJhQPeI8aAXu2xOGjTQ1+w+8SSzQf99fO1q3kQxs= github.com/schollz/progressbar/v3 v3.14.0/go.mod h1:l7jf8Ehh0x7Li8QCcEe28x7lf/9HJXLQm67VKd10NqU= 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= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= peerdiscovery-1.7.3/examples/ipv4/main.go000066400000000000000000000040071462264431400203670ustar00rootroot00000000000000package main import ( "fmt" "log" math_rand "math/rand" "time" "github.com/schollz/peerdiscovery" "github.com/schollz/progressbar/v3" ) func main() { fmt.Println("Scanning for 30 seconds to find LAN peers") // show progress bar go func() { bar := progressbar.New(300) for i := 0; i < 300; i++ { bar.Add(1) time.Sleep(100 * time.Millisecond) } fmt.Print("\n") }() // discover peers discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{ Limit: -1, Payload: []byte(randStringBytesMaskImprSrc(10)), Delay: 100 * time.Millisecond, TimeLimit: 30 * time.Second, Notify: func(d peerdiscovery.Discovered) { log.Println(d) }, MulticastAddress: "239.255.255.250", }) // print out results if err != nil { log.Fatal(err) } else { if len(discoveries) > 0 { fmt.Printf("Found %d other computers\n", len(discoveries)) for i, d := range discoveries { fmt.Printf("%d) '%s' with payload '%s'\n", i, d.Address, d.Payload) } } else { fmt.Println("Found no devices. You need to run this on another computer at the same time.") } } } // src is seeds the random generator for generating random strings var src = math_rand.NewSource(time.Now().UnixNano()) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i-- } cache >>= letterIdxBits remain-- } return string(b) } peerdiscovery-1.7.3/examples/ipv6/000077500000000000000000000000001462264431400171155ustar00rootroot00000000000000peerdiscovery-1.7.3/examples/ipv6/Dockerfile000066400000000000000000000001321462264431400211030ustar00rootroot00000000000000FROM golang WORKDIR /peerdiscovery COPY . . RUN go build -v CMD ["/peerdiscovery/ipv6"] peerdiscovery-1.7.3/examples/ipv6/Makefile000066400000000000000000000002621462264431400205550ustar00rootroot00000000000000build: docker build --no-cache -t peertest . run: @echo "\nfollow https://docs.docker.com/config/daemon/ipv6/ to setup docker for ipv6\n\n" docker run --ip6 -t --rm peertest peerdiscovery-1.7.3/examples/ipv6/go.mod000066400000000000000000000000771462264431400202270ustar00rootroot00000000000000module github.com/schollz/peerdiscovery/examples/ipv6 go 1.15 peerdiscovery-1.7.3/examples/ipv6/main.go000066400000000000000000000037741462264431400204030ustar00rootroot00000000000000package main import ( "fmt" "log" math_rand "math/rand" "time" "github.com/schollz/peerdiscovery" "github.com/schollz/progressbar/v3" ) func main() { fmt.Println("Scanning for 10 seconds to find LAN peers") // show progress bar go func() { bar := progressbar.Default(10) for i := 0; i < 10; i++ { bar.Add(1) time.Sleep(1 * time.Second) } fmt.Print("\n") }() // discover peers discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{ Limit: -1, Payload: []byte(randStringBytesMaskImprSrc(10)), Delay: 500 * time.Millisecond, TimeLimit: 10 * time.Second, Notify: func(d peerdiscovery.Discovered) { log.Println(d) }, IPVersion: peerdiscovery.IPv6, }) // print out results if err != nil { log.Fatal(err) } else { if len(discoveries) > 0 { fmt.Printf("Found %d other computers\n", len(discoveries)) for i, d := range discoveries { fmt.Printf("%d) '%s' with payload '%s'\n", i, d.Address, d.Payload) } } else { fmt.Println("Found no devices. You need to run this on another computer at the same time.") } } } // src is seeds the random generator for generating random strings var src = math_rand.NewSource(time.Now().UnixNano()) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i-- } cache >>= letterIdxBits remain-- } return string(b) } peerdiscovery-1.7.3/go.mod000066400000000000000000000001741462264431400155230ustar00rootroot00000000000000module github.com/schollz/peerdiscovery go 1.13 require ( github.com/stretchr/testify v1.6.1 golang.org/x/net v0.25.0 ) peerdiscovery-1.7.3/go.sum000066400000000000000000000121411462264431400155450ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= peerdiscovery-1.7.3/internal.go000066400000000000000000000066261462264431400165700ustar00rootroot00000000000000package peerdiscovery import ( "fmt" "net" "strconv" "time" ) // initialize returns a new peerDiscovery object which can be used to discover peers. // The settings are optional. If any setting is not supplied, then defaults are used. // See the Settings for more information. func initialize(settings Settings) (p *PeerDiscovery, err error) { p = new(PeerDiscovery) p.Lock() defer p.Unlock() // initialize settings p.settings = settings // defaults if p.settings.Port == "" { p.settings.Port = "9999" } if p.settings.IPVersion == 0 { p.settings.IPVersion = IPv4 } if p.settings.MulticastAddress == "" { if p.settings.IPVersion == IPv4 { p.settings.MulticastAddress = "239.255.255.250" } else { p.settings.MulticastAddress = "ff02::c" } } if len(p.settings.Payload) == 0 { p.settings.Payload = []byte("hi") } if p.settings.Delay == 0 { p.settings.Delay = 1 * time.Second } if p.settings.TimeLimit == 0 { p.settings.TimeLimit = 10 * time.Second } if p.settings.StopChan == nil { p.settings.StopChan = make(chan struct{}) } p.received = make(map[string]*PeerState) p.settings.multicastAddressNumbers = net.ParseIP(p.settings.MulticastAddress) if p.settings.multicastAddressNumbers == nil { err = fmt.Errorf( "multicast address %s could not be converted to an IP", p.settings.MulticastAddress, ) return } p.settings.portNum, err = strconv.Atoi(p.settings.Port) if err != nil { return } return } // filterInterfaces returns a list of valid network interfaces func filterInterfaces(useIpv4 bool) (ifaces []net.Interface, err error) { allIfaces, err := net.Interfaces() if err != nil { return } for _, iface := range allIfaces { // Interface must be up and either support multicast or be a loopback interface. if iface.Flags&net.FlagUp == 0 { continue } if iface.Flags&net.FlagLoopback == 0 && iface.Flags&net.FlagMulticast == 0 { continue } addrs, addrsErr := iface.Addrs() if addrsErr != nil { err = addrsErr return } supported := false for j := range addrs { addr, ok := addrs[j].(*net.IPNet) if !ok { continue } if addr == nil || addr.IP == nil { continue } // An IP can either be an IPv4 or an IPv6 address. // Check if the desired familiy is used. familiyMatches := (addr.IP.To4() != nil) == useIpv4 if familiyMatches { supported = true break } } if supported { ifaces = append(ifaces, iface) } } return } // getLocalIPs returns the local ip address func getLocalIPs() (ips map[string]struct{}) { ips = make(map[string]struct{}) ips["localhost"] = struct{}{} ips["127.0.0.1"] = struct{}{} ips["::1"] = struct{}{} ifaces, err := net.Interfaces() if err != nil { return } for _, iface := range ifaces { addrs, err := iface.Addrs() if err != nil { continue } for _, address := range addrs { ip, _, err := net.ParseCIDR(address.String()) if err != nil { // log.Printf("Failed to parse %s: %v", address.String(), err) continue } ips[ip.String()+"%"+iface.Name] = struct{}{} ips[ip.String()] = struct{}{} } } return } func broadcast(p2 NetPacketConn, payload []byte, ifaces []net.Interface, dst net.Addr) { for i := range ifaces { if errMulticast := p2.SetMulticastInterface(&ifaces[i]); errMulticast != nil { continue } p2.SetMulticastTTL(2) if _, errMulticast := p2.WriteTo([]byte(payload), dst); errMulticast != nil { continue } } } peerdiscovery-1.7.3/listener.go000066400000000000000000000070471462264431400165770ustar00rootroot00000000000000package peerdiscovery import ( "net" "sync" "time" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) const ( // https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure maxDatagramSize = 66507 ) // PeerState is the state of a peer that has been discovered. // It contains the address of the peer, the last time it was seen, // the last payload it sent, and the metadata associated with it. // To update the metadata, assign your own metadata to the Metadata.Data field. // The metadata is not protected by a mutex, so you must do this yourself. type PeerState struct { Address string lastSeen time.Time lastPayload []byte metadata *Metadata } type LostPeer struct { Address string LastSeen time.Time LastPayload []byte Metadata *Metadata } func (p *PeerDiscovery) gc() { ticker := time.NewTicker(p.settings.Delay * 2) defer ticker.Stop() for range ticker.C { p.Lock() for ip, peerState := range p.received { if time.Since(peerState.lastSeen) > p.settings.Delay*4 { if p.settings.NotifyLost != nil { p.settings.NotifyLost(LostPeer{ Address: ip, LastSeen: peerState.lastSeen, LastPayload: peerState.lastPayload, Metadata: peerState.metadata, }) } delete(p.received, ip) } } p.Unlock() } } // PeerDiscovery is the object that can do the discovery for finding LAN peers. type PeerDiscovery struct { settings Settings received map[string]*PeerState sync.RWMutex exit bool } func (p *PeerDiscovery) Shutdown() { p.exit = true } func (p *PeerDiscovery) ActivePeers() (peers []*PeerState) { p.RLock() defer p.RUnlock() for _, peerState := range p.received { peers = append(peers, peerState) } return } // Listen binds to the UDP address and port given and writes packets received // from that address to a buffer which is passed to a hander func (p *PeerDiscovery) listen(c net.PacketConn) (recievedBytes []byte, err error) { p.RLock() portNum := p.settings.portNum allowSelf := p.settings.AllowSelf timeLimit := p.settings.TimeLimit notify := p.settings.Notify p.RUnlock() localIPs := getLocalIPs() // get interfaces ifaces, err := net.Interfaces() if err != nil { return nil, err } // log.Println(ifaces) group := p.settings.multicastAddressNumbers var p2 NetPacketConn if p.settings.IPVersion == IPv4 { p2 = PacketConn4{ipv4.NewPacketConn(c)} } else { p2 = PacketConn6{ipv6.NewPacketConn(c)} } for i := range ifaces { p2.JoinGroup(&ifaces[i], &net.UDPAddr{IP: group, Port: portNum}) } start := time.Now() // Loop forever reading from the socket for { buffer := make([]byte, maxDatagramSize) var ( n int src net.Addr errRead error ) n, src, errRead = p2.ReadFrom(buffer) if errRead != nil { err = errRead return } srcHost, _, _ := net.SplitHostPort(src.String()) if _, ok := localIPs[srcHost]; ok && !allowSelf { continue } // log.Println(src, hex.Dump(buffer[:n])) p.Lock() if peer, ok := p.received[srcHost]; ok { peer.lastSeen = time.Now() peer.lastPayload = buffer[:n] } else { p.received[srcHost] = &PeerState{ Address: srcHost, lastPayload: buffer[:n], lastSeen: time.Now(), metadata: &Metadata{}, } } p.Unlock() if notify != nil { notify(Discovered{ Address: srcHost, Payload: buffer[:n], }) } p.RLock() if len(p.received) >= p.settings.Limit && p.settings.Limit > 0 { p.RUnlock() break } if p.exit || timeLimit > 0 && time.Since(start) > timeLimit { p.RUnlock() break } p.RUnlock() } return } peerdiscovery-1.7.3/packetConn.go000066400000000000000000000021101462264431400170210ustar00rootroot00000000000000package peerdiscovery import ( "net" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) type PacketConn4 struct { *ipv4.PacketConn } // ReadFrom wraps the ipv4 ReadFrom without a control message func (pc4 PacketConn4) ReadFrom(buf []byte) (int, net.Addr, error) { n, _, addr, err := pc4.PacketConn.ReadFrom(buf) return n, addr, err } // WriteTo wraps the ipv4 WriteTo without a control message func (pc4 PacketConn4) WriteTo(buf []byte, dst net.Addr) (int, error) { return pc4.PacketConn.WriteTo(buf, nil, dst) } type PacketConn6 struct { *ipv6.PacketConn } // ReadFrom wraps the ipv6 ReadFrom without a control message func (pc6 PacketConn6) ReadFrom(buf []byte) (int, net.Addr, error) { n, _, addr, err := pc6.PacketConn.ReadFrom(buf) return n, addr, err } // WriteTo wraps the ipv6 WriteTo without a control message func (pc6 PacketConn6) WriteTo(buf []byte, dst net.Addr) (int, error) { return pc6.PacketConn.WriteTo(buf, nil, dst) } // SetMulticastTTL wraps the hop limit of ipv6 func (pc6 PacketConn6) SetMulticastTTL(i int) error { return pc6.SetMulticastHopLimit(i) } peerdiscovery-1.7.3/peerdiscovery.go000066400000000000000000000144411462264431400176310ustar00rootroot00000000000000package peerdiscovery import ( "fmt" "net" "time" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) // IPVersion specifies the version of the Internet Protocol to be used. type IPVersion uint const ( IPv4 IPVersion = 4 IPv6 IPVersion = 6 ) // Discovered is the structure of the discovered peers, // which holds their local address (port removed) and // a payload if there is one. type Discovered struct { // Address is the local address of a discovered peer. Address string // Payload is the associated payload from discovered peer. Payload []byte Metadata *Metadata } // Metadata is the metadata associated with a discovered peer. // To update the metadata, assign your own metadata to the Metadata.Data field. // The metadata is not protected by a mutex, so you must do this yourself. // The metadata update happens by pointer, to keep the library backwards compatible. type Metadata struct { Data interface{} } func (d Discovered) String() string { return fmt.Sprintf("address: %s, payload: %s", d.Address, d.Payload) } // Settings are the settings that can be specified for // doing peer discovery. type Settings struct { // Limit is the number of peers to discover, use < 1 for unlimited. Limit int // Port is the port to broadcast on (the peers must also broadcast using the same port). // The default port is 9999. Port string // MulticastAddress specifies the multicast address. // You should be able to use any of 224.0.0.0/4 or ff00::/8. // By default it uses the Simple Service Discovery Protocol // address (239.255.255.250 for IPv4 or ff02::c for IPv6). MulticastAddress string // Payload is the bytes that are sent out with each broadcast. Must be short. Payload []byte // PayloadFunc is the function that will be called to dynamically generate payload // before every broadcast. If this pointer is nil `Payload` field will be broadcasted instead. PayloadFunc func() []byte // Delay is the amount of time between broadcasts. The default delay is 1 second. Delay time.Duration // TimeLimit is the amount of time to spend discovering, if the limit is not reached. // A negative limit indiciates scanning until the limit was reached or, if an // unlimited scanning was requested, no timeout. // The default time limit is 10 seconds. TimeLimit time.Duration // StopChan is a channel to stop the peer discvoery immediatley after reception. StopChan chan struct{} // AllowSelf will allow discovery the local machine (default false) AllowSelf bool // DisableBroadcast will not allow sending out a broadcast DisableBroadcast bool // IPVersion specifies the version of the Internet Protocol (default IPv4) IPVersion IPVersion // Notify will be called each time a new peer was discovered. // The default is nil, which means no notification whatsoever. Notify func(Discovered) // NotifyLost will be called each time a peer was lost. // The default is nil, which means no notification whatsoever. // This function should not take too long to execute, as it is called // from the peer garbage collector. NotifyLost func(LostPeer) portNum int multicastAddressNumbers net.IP } type NetPacketConn interface { JoinGroup(ifi *net.Interface, group net.Addr) error SetMulticastInterface(ini *net.Interface) error SetMulticastTTL(int) error ReadFrom(buf []byte) (int, net.Addr, error) WriteTo(buf []byte, dst net.Addr) (int, error) } // Discover will use the created settings to scan for LAN peers. It will return // an array of the discovered peers and their associate payloads. It will not // return broadcasts sent to itself. func Discover(settings ...Settings) (discoveries []Discovered, err error) { _, discoveries, err = newPeerDiscovery(settings...) if err != nil { return nil, err } return discoveries, nil } func NewPeerDiscovery(settings ...Settings) (pd *PeerDiscovery, err error) { pd, discoveries, err := newPeerDiscovery(settings...) if notify := pd.settings.Notify; notify != nil { for _, d := range discoveries { notify(d) } } return pd, err } func newPeerDiscovery(settings ...Settings) (pd *PeerDiscovery, discoveries []Discovered, err error) { s := Settings{} if len(settings) > 0 { s = settings[0] } p, err := initialize(s) if err != nil { return nil, nil, err } p.RLock() address := net.JoinHostPort(p.settings.MulticastAddress, p.settings.Port) portNum := p.settings.portNum tickerDuration := p.settings.Delay timeLimit := p.settings.TimeLimit p.RUnlock() ifaces, err := filterInterfaces(p.settings.IPVersion == IPv4) if err != nil { return nil, nil, err } if len(ifaces) == 0 { err = fmt.Errorf("no multicast interface found") return nil, nil, err } // Open up a connection c, err := net.ListenPacket(fmt.Sprintf("udp%d", p.settings.IPVersion), address) if err != nil { return nil, nil, err } defer c.Close() group := p.settings.multicastAddressNumbers // ipv{4,6} have an own PacketConn, which does not implement net.PacketConn var p2 NetPacketConn if p.settings.IPVersion == IPv4 { p2 = PacketConn4{ipv4.NewPacketConn(c)} } else { p2 = PacketConn6{ipv6.NewPacketConn(c)} } for i := range ifaces { p2.JoinGroup(&ifaces[i], &net.UDPAddr{IP: group, Port: portNum}) } go p.listen(c) ticker := time.NewTicker(tickerDuration) defer ticker.Stop() start := time.Now() for { p.RLock() if len(p.received) >= p.settings.Limit && p.settings.Limit > 0 { p.exit = true } p.RUnlock() if !s.DisableBroadcast { payload := p.settings.Payload if p.settings.PayloadFunc != nil { payload = p.settings.PayloadFunc() } // write to multicast broadcast(p2, payload, ifaces, &net.UDPAddr{IP: group, Port: portNum}) } select { case <-p.settings.StopChan: p.exit = true case <-ticker.C: } if p.exit || timeLimit > 0 && time.Since(start) > timeLimit { break } } if !s.DisableBroadcast { payload := p.settings.Payload if p.settings.PayloadFunc != nil { payload = p.settings.PayloadFunc() } // send out broadcast that is finished broadcast(p2, payload, ifaces, &net.UDPAddr{IP: group, Port: portNum}) } p.RLock() discoveries = make([]Discovered, len(p.received)) i := 0 for ip, peerState := range p.received { discoveries[i] = Discovered{ Address: ip, Payload: peerState.lastPayload, Metadata: peerState.metadata, } i++ } p.RUnlock() go p.gc() return p, discoveries, nil } peerdiscovery-1.7.3/peerdiscovery_test.go000066400000000000000000000025701462264431400206700ustar00rootroot00000000000000package peerdiscovery import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" ) func TestDiscovery(t *testing.T) { for _, version := range []IPVersion{IPv4, IPv6} { // should not be able to "discover" itself discoveries, err := Discover(Settings{ TimeLimit: 5 * time.Second, Delay: 500 * time.Millisecond, }) assert.Nil(t, err) assert.Zero(t, len(discoveries)) // should be able to "discover" itself discoveries, err = Discover(Settings{ Limit: -1, AllowSelf: true, Payload: []byte("payload"), Delay: 500 * time.Millisecond, TimeLimit: 1 * time.Second, IPVersion: version, }) fmt.Println(discoveries) assert.Nil(t, err) assert.NotZero(t, len(discoveries)) } } func TestDiscoverySelf(t *testing.T) { for _, version := range []IPVersion{IPv4, IPv6} { // broadcast self to self go func() { _, err := Discover(Settings{ Limit: -1, Payload: []byte("payload"), Delay: 10 * time.Millisecond, TimeLimit: 2 * time.Second, IPVersion: version, }) assert.Nil(t, err) }() discoveries, err := Discover(Settings{ Limit: 1, Payload: []byte("payload"), Delay: 500 * time.Millisecond, TimeLimit: 2 * time.Second, DisableBroadcast: true, AllowSelf: true, }) assert.Nil(t, err) assert.NotZero(t, len(discoveries)) } }