Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
3
vendor/github.com/koron/go-ssdp/.gitignore
generated
vendored
Normal file
3
vendor/github.com/koron/go-ssdp/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.exe
|
||||
tags
|
||||
tmp/
|
||||
21
vendor/github.com/koron/go-ssdp/LICENSE
generated
vendored
Normal file
21
vendor/github.com/koron/go-ssdp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 MURAOKA Taro <koron.kaoriya@gmail.com>
|
||||
|
||||
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.
|
||||
71
vendor/github.com/koron/go-ssdp/Makefile
generated
vendored
Normal file
71
vendor/github.com/koron/go-ssdp/Makefile
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
EXAMPLES = advertise alive bye monitor search
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -gcflags '-e'
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -gcflags '-e' ./...
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
go test -bench ./...
|
||||
|
||||
.PHONY: tags
|
||||
tags:
|
||||
gotags -f tags -R .
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
mkdir -p tmp
|
||||
go test -coverprofile tmp/_cover.out . ./internal/...
|
||||
go tool cover -html tmp/_cover.out -o tmp/cover.html
|
||||
|
||||
.PHONY: checkall
|
||||
checkall: vet staticcheck
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
.PHONY: staticcheck
|
||||
staticcheck:
|
||||
staticcheck ./...
|
||||
|
||||
.PHONY: clean
|
||||
clean: examples-clean
|
||||
go clean
|
||||
rm -f tags
|
||||
rm -f tmp/_cover.out tmp/cover.html
|
||||
|
||||
# based on: github.com/koron-go/_skeleton/Makefile
|
||||
|
||||
.PHONY: test-race
|
||||
test-race:
|
||||
go test -race .
|
||||
|
||||
.PHONY: examples
|
||||
examples: examples-build
|
||||
|
||||
.PHONY: examples-build
|
||||
examples-build: $(EXAMPLES)
|
||||
|
||||
.PHONY: examples-clean
|
||||
examples-clean:
|
||||
rm -f $(EXAMPLES)
|
||||
|
||||
advertise: examples/advertise/*.go *.go
|
||||
go build ./examples/advertise
|
||||
|
||||
alive: examples/alive/*.go *.go
|
||||
go build ./examples/alive
|
||||
|
||||
bye: examples/bye/*.go *.go
|
||||
go build ./examples/bye
|
||||
|
||||
monitor: examples/monitor/*.go *.go
|
||||
go build ./examples/monitor
|
||||
|
||||
search: examples/search/*.go *.go
|
||||
go build ./examples/search
|
||||
95
vendor/github.com/koron/go-ssdp/README.md
generated
vendored
Normal file
95
vendor/github.com/koron/go-ssdp/README.md
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# SSDP library
|
||||
|
||||
[](https://godoc.org/github.com/koron/go-ssdp)
|
||||
[](https://github.com/koron/go-ssdp/actions?query=workflow%3AGo)
|
||||
[](https://goreportcard.com/report/github.com/koron/go-ssdp)
|
||||
|
||||
Based on <https://tools.ietf.org/html/draft-cai-ssdp-v1-03>.
|
||||
|
||||
## Examples
|
||||
|
||||
There are tiny snippets for example. See also examples/ directory for working
|
||||
examples.
|
||||
|
||||
### Respond to search
|
||||
|
||||
```go
|
||||
import "github.com/koron/go-ssdp"
|
||||
|
||||
ad, err := ssdp.Advertise(
|
||||
"my:device", // send as "ST"
|
||||
"unique:id", // send as "USN"
|
||||
"http://192.168.0.1:57086/foo.xml", // send as "LOCATION"
|
||||
"go-ssdp sample", // send as "SERVER"
|
||||
1800) // send as "maxAge" in "CACHE-CONTROL"
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// run Advertiser infinitely.
|
||||
quit := make(chan bool)
|
||||
<-quit
|
||||
```
|
||||
|
||||
### Send alive periodically
|
||||
|
||||
```go
|
||||
import "time"
|
||||
|
||||
aliveTick := time.Tick(300 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-aliveTick:
|
||||
ad.Alive()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Send bye when quiting
|
||||
|
||||
```go
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
// to detect CTRL-C is pressed.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-aliveTick:
|
||||
ad.Alive()
|
||||
case <-quit:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// send/multicast "byebye" message.
|
||||
ad.Bye()
|
||||
// teminate Advertiser.
|
||||
ad.Close()
|
||||
```
|
||||
|
||||
### Limitate interfaces to multicast
|
||||
|
||||
go-ssdp will send multicast messages to all IPv4 interfaces as default.
|
||||
When you want to limitate interfaces, see below snippet.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/koron/go-ssdp"
|
||||
"net"
|
||||
)
|
||||
|
||||
en0, err := net.InterfaceByName("en0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ssdp.Interfaces = []net.Interface{*en0}
|
||||
```
|
||||
|
||||
go-ssdp will send multicast message only "en0" after this.
|
||||
185
vendor/github.com/koron/go-ssdp/advertise.go
generated
vendored
Normal file
185
vendor/github.com/koron/go-ssdp/advertise.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/multicast"
|
||||
"github.com/koron/go-ssdp/internal/ssdplog"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
to net.Addr
|
||||
data multicast.DataProvider
|
||||
}
|
||||
|
||||
// Advertiser is a server to advertise a service.
|
||||
type Advertiser struct {
|
||||
st string
|
||||
usn string
|
||||
locProv LocationProvider
|
||||
server string
|
||||
maxAge int
|
||||
|
||||
conn *multicast.Conn
|
||||
ch chan *message
|
||||
wg sync.WaitGroup
|
||||
wgS sync.WaitGroup
|
||||
}
|
||||
|
||||
// Advertise starts advertisement of service.
|
||||
// location should be a string or a ssdp.LocationProvider.
|
||||
func Advertise(st, usn string, location interface{}, server string, maxAge int) (*Advertiser, error) {
|
||||
locProv, err := toLocationProvider(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := multicast.Listen(multicast.RecvAddrResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ssdplog.Printf("SSDP advertise on: %s", conn.LocalAddr().String())
|
||||
a := &Advertiser{
|
||||
st: st,
|
||||
usn: usn,
|
||||
locProv: locProv,
|
||||
server: server,
|
||||
maxAge: maxAge,
|
||||
conn: conn,
|
||||
ch: make(chan *message),
|
||||
}
|
||||
a.wg.Add(2)
|
||||
a.wgS.Add(1)
|
||||
go func() {
|
||||
a.sendMain()
|
||||
a.wgS.Done()
|
||||
a.wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
a.recvMain()
|
||||
a.wg.Done()
|
||||
}()
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Advertiser) recvMain() error {
|
||||
// TODO: update listening interfaces of a.conn
|
||||
err := a.conn.ReadPackets(0, func(addr net.Addr, data []byte) error {
|
||||
if err := a.handleRaw(addr, data); err != nil {
|
||||
ssdplog.Printf("failed to handle message: %s", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Advertiser) sendMain() {
|
||||
for msg := range a.ch {
|
||||
_, err := a.conn.WriteTo(msg.data, msg.to)
|
||||
if err != nil {
|
||||
ssdplog.Printf("failed to send: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Advertiser) handleRaw(from net.Addr, raw []byte) error {
|
||||
if !bytes.HasPrefix(raw, []byte("M-SEARCH ")) {
|
||||
// unexpected method.
|
||||
return nil
|
||||
}
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
man = req.Header.Get("MAN")
|
||||
st = req.Header.Get("ST")
|
||||
)
|
||||
if man != `"ssdp:discover"` {
|
||||
return fmt.Errorf("unexpected MAN: %s", man)
|
||||
}
|
||||
if st != All && st != RootDevice && st != a.st {
|
||||
// skip when ST is not matched/expected.
|
||||
return nil
|
||||
}
|
||||
ssdplog.Printf("received M-SEARCH MAN=%s ST=%s from %s", man, st, from.String())
|
||||
// build and send a response.
|
||||
msg := buildOK(a.st, a.usn, a.locProv.Location(from, nil), a.server, a.maxAge)
|
||||
a.ch <- &message{to: from, data: multicast.BytesDataProvider(msg)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildOK(st, usn, location, server string, maxAge int) []byte {
|
||||
// bytes.Buffer#Write() is never fail, so we can omit error checks.
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("HTTP/1.1 200 OK\r\n")
|
||||
fmt.Fprintf(b, "EXT: \r\n")
|
||||
fmt.Fprintf(b, "ST: %s\r\n", st)
|
||||
fmt.Fprintf(b, "USN: %s\r\n", usn)
|
||||
if location != "" {
|
||||
fmt.Fprintf(b, "LOCATION: %s\r\n", location)
|
||||
}
|
||||
if server != "" {
|
||||
fmt.Fprintf(b, "SERVER: %s\r\n", server)
|
||||
}
|
||||
fmt.Fprintf(b, "CACHE-CONTROL: max-age=%d\r\n", maxAge)
|
||||
b.WriteString("\r\n")
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// Close stops advertisement.
|
||||
func (a *Advertiser) Close() error {
|
||||
if a.conn != nil {
|
||||
// closing order is very important. be careful to change:
|
||||
// stop sending loop by closing the channel and wait it.
|
||||
close(a.ch)
|
||||
a.wgS.Wait()
|
||||
// stop receiving loop by closing the connection.
|
||||
a.conn.Close()
|
||||
a.wg.Wait()
|
||||
a.conn = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alive announces ssdp:alive message.
|
||||
func (a *Advertiser) Alive() error {
|
||||
addr, err := multicast.SendAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := &aliveDataProvider{
|
||||
host: addr,
|
||||
nt: a.st,
|
||||
usn: a.usn,
|
||||
location: a.locProv,
|
||||
server: a.server,
|
||||
maxAge: a.maxAge,
|
||||
}
|
||||
a.ch <- &message{to: addr, data: msg}
|
||||
ssdplog.Printf("sent alive")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bye announces ssdp:byebye message.
|
||||
func (a *Advertiser) Bye() error {
|
||||
addr, err := multicast.SendAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg, err := buildBye(addr, a.st, a.usn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.ch <- &message{to: addr, data: multicast.BytesDataProvider(msg)}
|
||||
ssdplog.Printf("sent bye")
|
||||
return nil
|
||||
}
|
||||
110
vendor/github.com/koron/go-ssdp/announce.go
generated
vendored
Normal file
110
vendor/github.com/koron/go-ssdp/announce.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/multicast"
|
||||
)
|
||||
|
||||
// AnnounceAlive sends ssdp:alive message.
|
||||
// location should be a string or a ssdp.LocationProvider.
|
||||
func AnnounceAlive(nt, usn string, location interface{}, server string, maxAge int, localAddr string) error {
|
||||
locProv, err := toLocationProvider(location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// dial multicast UDP packet.
|
||||
conn, err := multicast.Listen(&multicast.AddrResolver{Addr: localAddr})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// build and send message.
|
||||
addr, err := multicast.SendAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := &aliveDataProvider{
|
||||
host: addr,
|
||||
nt: nt,
|
||||
usn: usn,
|
||||
location: locProv,
|
||||
server: server,
|
||||
maxAge: maxAge,
|
||||
}
|
||||
if _, err := conn.WriteTo(msg, addr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type aliveDataProvider struct {
|
||||
host net.Addr
|
||||
nt string
|
||||
usn string
|
||||
location LocationProvider
|
||||
server string
|
||||
maxAge int
|
||||
}
|
||||
|
||||
func (p *aliveDataProvider) Bytes(ifi *net.Interface) []byte {
|
||||
return buildAlive(p.host, p.nt, p.usn, p.location.Location(nil, ifi), p.server, p.maxAge)
|
||||
}
|
||||
|
||||
var _ multicast.DataProvider = (*aliveDataProvider)(nil)
|
||||
|
||||
func buildAlive(raddr net.Addr, nt, usn, location, server string, maxAge int) []byte {
|
||||
// bytes.Buffer#Write() is never fail, so we can omit error checks.
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("NOTIFY * HTTP/1.1\r\n")
|
||||
fmt.Fprintf(b, "HOST: %s\r\n", raddr.String())
|
||||
fmt.Fprintf(b, "NT: %s\r\n", nt)
|
||||
fmt.Fprintf(b, "NTS: %s\r\n", "ssdp:alive")
|
||||
fmt.Fprintf(b, "USN: %s\r\n", usn)
|
||||
if location != "" {
|
||||
fmt.Fprintf(b, "LOCATION: %s\r\n", location)
|
||||
}
|
||||
if server != "" {
|
||||
fmt.Fprintf(b, "SERVER: %s\r\n", server)
|
||||
}
|
||||
fmt.Fprintf(b, "CACHE-CONTROL: max-age=%d\r\n", maxAge)
|
||||
b.WriteString("\r\n")
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// AnnounceBye sends ssdp:byebye message.
|
||||
func AnnounceBye(nt, usn, localAddr string) error {
|
||||
// dial multicast UDP packet.
|
||||
conn, err := multicast.Listen(&multicast.AddrResolver{Addr: localAddr})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// build and send message.
|
||||
addr, err := multicast.SendAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg, err := buildBye(addr, nt, usn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := conn.WriteTo(multicast.BytesDataProvider(msg), addr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildBye(raddr net.Addr, nt, usn string) ([]byte, error) {
|
||||
b := new(bytes.Buffer)
|
||||
// FIXME: error should be checked.
|
||||
b.WriteString("NOTIFY * HTTP/1.1\r\n")
|
||||
fmt.Fprintf(b, "HOST: %s\r\n", raddr.String())
|
||||
fmt.Fprintf(b, "NT: %s\r\n", nt)
|
||||
fmt.Fprintf(b, "NTS: %s\r\n", "ssdp:byebye")
|
||||
fmt.Fprintf(b, "USN: %s\r\n", usn)
|
||||
b.WriteString("\r\n")
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
4
vendor/github.com/koron/go-ssdp/doc.go
generated
vendored
Normal file
4
vendor/github.com/koron/go-ssdp/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package ssdp provides SSDP advertiser or so.
|
||||
*/
|
||||
package ssdp
|
||||
4
vendor/github.com/koron/go-ssdp/internal/multicast/doc.go
generated
vendored
Normal file
4
vendor/github.com/koron/go-ssdp/internal/multicast/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package multicast provides utilities for network multicast.
|
||||
*/
|
||||
package multicast
|
||||
65
vendor/github.com/koron/go-ssdp/internal/multicast/interface.go
generated
vendored
Normal file
65
vendor/github.com/koron/go-ssdp/internal/multicast/interface.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package multicast
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type InterfacesProviderFunc func() []net.Interface
|
||||
|
||||
// InterfacesProvider specify a function to list all interfaces to multicast.
|
||||
// If no provider are given, all possible interfaces will be used.
|
||||
var InterfacesProvider InterfacesProviderFunc
|
||||
|
||||
// interfaces gets list of net.Interface to multicast UDP packet.
|
||||
func interfaces() ([]net.Interface, error) {
|
||||
if p := InterfacesProvider; p != nil {
|
||||
if list := p(); len(list) > 0 {
|
||||
return list, nil
|
||||
}
|
||||
}
|
||||
return interfacesIPv4()
|
||||
}
|
||||
|
||||
// interfacesIPv4 lists net.Interface on IPv4.
|
||||
func interfacesIPv4() ([]net.Interface, error) {
|
||||
iflist, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := make([]net.Interface, 0, len(iflist))
|
||||
for _, ifi := range iflist {
|
||||
if !hasLinkUp(&ifi) || !hasMulticast(&ifi) || !hasIPv4Address(&ifi) {
|
||||
continue
|
||||
}
|
||||
list = append(list, ifi)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// hasLinkUp checks an I/F have link-up or not.
|
||||
func hasLinkUp(ifi *net.Interface) bool {
|
||||
return ifi.Flags&net.FlagUp != 0
|
||||
}
|
||||
|
||||
// hasMulticast checks an I/F supports multicast or not.
|
||||
func hasMulticast(ifi *net.Interface) bool {
|
||||
return ifi.Flags&net.FlagMulticast != 0
|
||||
}
|
||||
|
||||
// hasIPv4Address checks an I/F have IPv4 address.
|
||||
func hasIPv4Address(ifi *net.Interface) bool {
|
||||
addrs, err := ifi.Addrs()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, a := range addrs {
|
||||
ip, _, err := net.ParseCIDR(a.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(ip.To4()) == net.IPv4len && !ip.IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
155
vendor/github.com/koron/go-ssdp/internal/multicast/multicast.go
generated
vendored
Normal file
155
vendor/github.com/koron/go-ssdp/internal/multicast/multicast.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
package multicast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/ssdplog"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
// Conn is multicast connection.
|
||||
type Conn struct {
|
||||
laddr *net.UDPAddr
|
||||
conn *net.UDPConn
|
||||
pconn *ipv4.PacketConn
|
||||
iflist []net.Interface
|
||||
}
|
||||
|
||||
// Listen starts to receiving multicast messages.
|
||||
func Listen(r *AddrResolver) (*Conn, error) {
|
||||
// prepare parameters.
|
||||
laddr, err := r.resolve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// connect.
|
||||
conn, err := net.ListenUDP("udp4", laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// configure socket to use with multicast.
|
||||
pconn, iflist, err := newIPv4MulticastConn(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &Conn{
|
||||
laddr: laddr,
|
||||
conn: conn,
|
||||
pconn: pconn,
|
||||
iflist: iflist,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newIPv4MulticastConn(conn *net.UDPConn) (*ipv4.PacketConn, []net.Interface, error) {
|
||||
iflist, err := interfaces()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addr, err := SendAddr()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pconn, err := joinGroupIPv4(conn, iflist, addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pconn, iflist, nil
|
||||
}
|
||||
|
||||
// joinGroupIPv4 makes the connection join to a group on interfaces.
|
||||
func joinGroupIPv4(conn *net.UDPConn, iflist []net.Interface, gaddr net.Addr) (*ipv4.PacketConn, error) {
|
||||
wrap := ipv4.NewPacketConn(conn)
|
||||
wrap.SetMulticastLoopback(true)
|
||||
// add interfaces to multicast group.
|
||||
joined := 0
|
||||
for _, ifi := range iflist {
|
||||
if err := wrap.JoinGroup(&ifi, gaddr); err != nil {
|
||||
ssdplog.Printf("failed to join group %s on %s: %s", gaddr.String(), ifi.Name, err)
|
||||
continue
|
||||
}
|
||||
joined++
|
||||
ssdplog.Printf("joined group %s on %s (#%d)", gaddr.String(), ifi.Name, ifi.Index)
|
||||
}
|
||||
if joined == 0 {
|
||||
return nil, errors.New("no interfaces had joined to group")
|
||||
}
|
||||
return wrap, nil
|
||||
}
|
||||
|
||||
// Close closes a multicast connection.
|
||||
func (mc *Conn) Close() error {
|
||||
if err := mc.pconn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
// mc.conn is closed by mc.pconn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataProvider provides a body of multicast message to send.
|
||||
type DataProvider interface {
|
||||
Bytes(*net.Interface) []byte
|
||||
}
|
||||
|
||||
//type multicastDataProviderFunc func(*net.Interface) []byte
|
||||
//
|
||||
//func (f multicastDataProviderFunc) Bytes(ifi *net.Interface) []byte {
|
||||
// return f(ifi)
|
||||
//}
|
||||
|
||||
type BytesDataProvider []byte
|
||||
|
||||
func (b BytesDataProvider) Bytes(ifi *net.Interface) []byte {
|
||||
return []byte(b)
|
||||
}
|
||||
|
||||
// WriteTo sends a multicast message to interfaces.
|
||||
func (mc *Conn) WriteTo(dataProv DataProvider, to net.Addr) (int, error) {
|
||||
if uaddr, ok := to.(*net.UDPAddr); ok && !uaddr.IP.IsMulticast() {
|
||||
return mc.conn.WriteTo(dataProv.Bytes(nil), to)
|
||||
}
|
||||
sum := 0
|
||||
for _, ifi := range mc.iflist {
|
||||
if err := mc.pconn.SetMulticastInterface(&ifi); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := mc.pconn.WriteTo(dataProv.Bytes(&ifi), nil, to)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
// LocalAddr returns local address to listen multicast packets.
|
||||
func (mc *Conn) LocalAddr() net.Addr {
|
||||
return mc.laddr
|
||||
}
|
||||
|
||||
// ReadPackets reads multicast packets.
|
||||
func (mc *Conn) ReadPackets(timeout time.Duration, h PacketHandler) error {
|
||||
buf := make([]byte, 65535)
|
||||
if timeout > 0 {
|
||||
mc.pconn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
for {
|
||||
n, _, addr, err := mc.pconn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
return io.EOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := h(addr, buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
65
vendor/github.com/koron/go-ssdp/internal/multicast/udp.go
generated
vendored
Normal file
65
vendor/github.com/koron/go-ssdp/internal/multicast/udp.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package multicast
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PacketHandler func(net.Addr, []byte) error
|
||||
|
||||
type AddrResolver struct {
|
||||
Addr string
|
||||
|
||||
mu sync.RWMutex
|
||||
udp *net.UDPAddr
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *AddrResolver) setAddress(addr string) {
|
||||
r.mu.Lock()
|
||||
r.Addr = addr
|
||||
r.udp = nil
|
||||
r.err = nil
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *AddrResolver) resolve() (*net.UDPAddr, error) {
|
||||
r.mu.RLock()
|
||||
if err := r.err; err != nil {
|
||||
r.mu.RUnlock()
|
||||
return nil, err
|
||||
}
|
||||
if udp := r.udp; udp != nil {
|
||||
r.mu.RUnlock()
|
||||
return udp, nil
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.udp, r.err = net.ResolveUDPAddr("udp4", r.Addr)
|
||||
return r.udp, r.err
|
||||
}
|
||||
|
||||
var RecvAddrResolver = &AddrResolver{Addr: "224.0.0.1:1900"}
|
||||
|
||||
// SetRecvAddrIPv4 updates multicast address where to receive packets.
|
||||
// This never fail now.
|
||||
func SetRecvAddrIPv4(addr string) error {
|
||||
RecvAddrResolver.setAddress(addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
var sendAddrResolver = &AddrResolver{Addr: "239.255.255.250:1900"}
|
||||
|
||||
// SendAddr returns an address to send multicast UDP package.
|
||||
func SendAddr() (*net.UDPAddr, error) {
|
||||
return sendAddrResolver.resolve()
|
||||
}
|
||||
|
||||
// SetSendAddrIPv4 updates a UDP address to send multicast packets.
|
||||
// This never fail now.
|
||||
func SetSendAddrIPv4(addr string) error {
|
||||
sendAddrResolver.setAddress(addr)
|
||||
return nil
|
||||
}
|
||||
16
vendor/github.com/koron/go-ssdp/internal/ssdplog/ssdplog.go
generated
vendored
Normal file
16
vendor/github.com/koron/go-ssdp/internal/ssdplog/ssdplog.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Package ssdplog provides log mechanism for ssdp.
|
||||
*/
|
||||
package ssdplog
|
||||
|
||||
import "log"
|
||||
|
||||
var LoggerProvider = func() *log.Logger { return nil }
|
||||
|
||||
func Printf(s string, a ...interface{}) {
|
||||
if p := LoggerProvider; p != nil {
|
||||
if l := p(); l != nil {
|
||||
l.Printf(s, a...)
|
||||
}
|
||||
}
|
||||
}
|
||||
40
vendor/github.com/koron/go-ssdp/location.go
generated
vendored
Normal file
40
vendor/github.com/koron/go-ssdp/location.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// LocationProvider provides address for Location header which can be reached from
|
||||
// "from" address network.
|
||||
type LocationProvider interface {
|
||||
// Location provides an address be reachable from the network located
|
||||
// by "from" address or "ifi" interface.
|
||||
// One of "from" or "ifi" must not be nil.
|
||||
Location(from net.Addr, ifi *net.Interface) string
|
||||
}
|
||||
|
||||
// LocationProviderFunc type is an adapter to allow the use of ordinary
|
||||
// functions are location providers.
|
||||
type LocationProviderFunc func(net.Addr, *net.Interface) string
|
||||
|
||||
func (f LocationProviderFunc) Location(from net.Addr, ifi *net.Interface) string {
|
||||
return f(from, ifi)
|
||||
}
|
||||
|
||||
type fixedLocation string
|
||||
|
||||
func (s fixedLocation) Location(net.Addr, *net.Interface) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func toLocationProvider(v interface{}) (LocationProvider, error) {
|
||||
switch w := v.(type) {
|
||||
case string:
|
||||
return fixedLocation(w), nil
|
||||
case LocationProvider:
|
||||
return w, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("location should be a string or a ssdp.LocationProvider but got %T", w)
|
||||
}
|
||||
}
|
||||
214
vendor/github.com/koron/go-ssdp/monitor.go
generated
vendored
Normal file
214
vendor/github.com/koron/go-ssdp/monitor.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/multicast"
|
||||
"github.com/koron/go-ssdp/internal/ssdplog"
|
||||
)
|
||||
|
||||
// Monitor monitors SSDP's alive and byebye messages.
|
||||
type Monitor struct {
|
||||
Alive AliveHandler
|
||||
Bye ByeHandler
|
||||
Search SearchHandler
|
||||
|
||||
conn *multicast.Conn
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Start starts to monitor SSDP messages.
|
||||
func (m *Monitor) Start() error {
|
||||
conn, err := multicast.Listen(multicast.RecvAddrResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ssdplog.Printf("monitoring on %s", conn.LocalAddr().String())
|
||||
m.conn = conn
|
||||
m.wg.Add(1)
|
||||
go func() {
|
||||
m.serve()
|
||||
m.wg.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) serve() error {
|
||||
// TODO: update listening interfaces of m.conn
|
||||
err := m.conn.ReadPackets(0, func(addr net.Addr, data []byte) error {
|
||||
msg := make([]byte, len(data))
|
||||
copy(msg, data)
|
||||
go m.handleRaw(addr, msg)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) handleRaw(addr net.Addr, raw []byte) error {
|
||||
// Add newline to workaround buggy SSDP responses
|
||||
if !bytes.HasSuffix(raw, endOfHeader) {
|
||||
raw = bytes.Join([][]byte{raw, endOfHeader}, nil)
|
||||
}
|
||||
if bytes.HasPrefix(raw, []byte("M-SEARCH ")) {
|
||||
return m.handleSearch(addr, raw)
|
||||
}
|
||||
if bytes.HasPrefix(raw, []byte("NOTIFY ")) {
|
||||
return m.handleNotify(addr, raw)
|
||||
}
|
||||
n := bytes.Index(raw, []byte("\r\n"))
|
||||
ssdplog.Printf("unexpected method: %q", string(raw[:n]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) handleNotify(addr net.Addr, raw []byte) error {
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch nts := req.Header.Get("NTS"); nts {
|
||||
case "ssdp:alive":
|
||||
if req.Method != "NOTIFY" {
|
||||
return fmt.Errorf("unexpected method for %q: %s", "ssdp:alive", req.Method)
|
||||
}
|
||||
if h := m.Alive; h != nil {
|
||||
h(&AliveMessage{
|
||||
From: addr,
|
||||
Type: req.Header.Get("NT"),
|
||||
USN: req.Header.Get("USN"),
|
||||
Location: req.Header.Get("LOCATION"),
|
||||
Server: req.Header.Get("SERVER"),
|
||||
rawHeader: req.Header,
|
||||
})
|
||||
}
|
||||
case "ssdp:byebye":
|
||||
if req.Method != "NOTIFY" {
|
||||
return fmt.Errorf("unexpected method for %q: %s", "ssdp:byebye", req.Method)
|
||||
}
|
||||
if h := m.Bye; h != nil {
|
||||
h(&ByeMessage{
|
||||
From: addr,
|
||||
Type: req.Header.Get("NT"),
|
||||
USN: req.Header.Get("USN"),
|
||||
rawHeader: req.Header,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown NTS: %s", nts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) handleSearch(addr net.Addr, raw []byte) error {
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
man := req.Header.Get("MAN")
|
||||
if man != `"ssdp:discover"` {
|
||||
return fmt.Errorf("unexpected MAN: %s", man)
|
||||
}
|
||||
if h := m.Search; h != nil {
|
||||
h(&SearchMessage{
|
||||
From: addr,
|
||||
Type: req.Header.Get("ST"),
|
||||
rawHeader: req.Header,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes monitoring.
|
||||
func (m *Monitor) Close() error {
|
||||
if m.conn != nil {
|
||||
m.conn.Close()
|
||||
m.conn = nil
|
||||
m.wg.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AliveMessage represents SSDP's ssdp:alive message.
|
||||
type AliveMessage struct {
|
||||
// From is a sender of this message
|
||||
From net.Addr
|
||||
|
||||
// Type is a property of "NT"
|
||||
Type string
|
||||
|
||||
// USN is a property of "USN"
|
||||
USN string
|
||||
|
||||
// Location is a property of "LOCATION"
|
||||
Location string
|
||||
|
||||
// Server is a property of "SERVER"
|
||||
Server string
|
||||
|
||||
rawHeader http.Header
|
||||
maxAge *int
|
||||
}
|
||||
|
||||
// Header returns all properties in alive message.
|
||||
func (m *AliveMessage) Header() http.Header {
|
||||
return m.rawHeader
|
||||
}
|
||||
|
||||
// MaxAge extracts "max-age" value from "CACHE-CONTROL" property.
|
||||
func (m *AliveMessage) MaxAge() int {
|
||||
if m.maxAge == nil {
|
||||
m.maxAge = new(int)
|
||||
*m.maxAge = extractMaxAge(m.rawHeader.Get("CACHE-CONTROL"), -1)
|
||||
}
|
||||
return *m.maxAge
|
||||
}
|
||||
|
||||
// AliveHandler is handler of Alive message.
|
||||
type AliveHandler func(*AliveMessage)
|
||||
|
||||
// ByeMessage represents SSDP's ssdp:byebye message.
|
||||
type ByeMessage struct {
|
||||
// From is a sender of this message
|
||||
From net.Addr
|
||||
|
||||
// Type is a property of "NT"
|
||||
Type string
|
||||
|
||||
// USN is a property of "USN"
|
||||
USN string
|
||||
|
||||
rawHeader http.Header
|
||||
}
|
||||
|
||||
// Header returns all properties in bye message.
|
||||
func (m *ByeMessage) Header() http.Header {
|
||||
return m.rawHeader
|
||||
}
|
||||
|
||||
// ByeHandler is handler of Bye message.
|
||||
type ByeHandler func(*ByeMessage)
|
||||
|
||||
// SearchMessage represents SSDP's ssdp:discover message.
|
||||
type SearchMessage struct {
|
||||
From net.Addr
|
||||
Type string
|
||||
|
||||
rawHeader http.Header
|
||||
}
|
||||
|
||||
// Header returns all properties in search message.
|
||||
func (s *SearchMessage) Header() http.Header {
|
||||
return s.rawHeader
|
||||
}
|
||||
|
||||
// SearchHandler is handler of Search message.
|
||||
type SearchHandler func(*SearchMessage)
|
||||
154
vendor/github.com/koron/go-ssdp/search.go
generated
vendored
Normal file
154
vendor/github.com/koron/go-ssdp/search.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/multicast"
|
||||
"github.com/koron/go-ssdp/internal/ssdplog"
|
||||
)
|
||||
|
||||
// Service is discovered service.
|
||||
type Service struct {
|
||||
// Type is a property of "ST"
|
||||
Type string
|
||||
|
||||
// USN is a property of "USN"
|
||||
USN string
|
||||
|
||||
// Location is a property of "LOCATION"
|
||||
Location string
|
||||
|
||||
// Server is a property of "SERVER"
|
||||
Server string
|
||||
|
||||
rawHeader http.Header
|
||||
maxAge *int
|
||||
}
|
||||
|
||||
var rxMaxAge = regexp.MustCompile(`\bmax-age\s*=\s*(\d+)\b`)
|
||||
|
||||
func extractMaxAge(s string, value int) int {
|
||||
v := value
|
||||
if m := rxMaxAge.FindStringSubmatch(s); m != nil {
|
||||
i64, err := strconv.ParseInt(m[1], 10, 32)
|
||||
if err == nil {
|
||||
v = int(i64)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MaxAge extracts "max-age" value from "CACHE-CONTROL" property.
|
||||
func (s *Service) MaxAge() int {
|
||||
if s.maxAge == nil {
|
||||
s.maxAge = new(int)
|
||||
*s.maxAge = extractMaxAge(s.rawHeader.Get("CACHE-CONTROL"), -1)
|
||||
}
|
||||
return *s.maxAge
|
||||
}
|
||||
|
||||
// Header returns all properties in response of search.
|
||||
func (s *Service) Header() http.Header {
|
||||
return s.rawHeader
|
||||
}
|
||||
|
||||
const (
|
||||
// All is a search type to search all services and devices.
|
||||
All = "ssdp:all"
|
||||
|
||||
// RootDevice is a search type to search UPnP root devices.
|
||||
RootDevice = "upnp:rootdevice"
|
||||
)
|
||||
|
||||
// Search searches services by SSDP.
|
||||
func Search(searchType string, waitSec int, localAddr string) ([]Service, error) {
|
||||
// dial multicast UDP packet.
|
||||
conn, err := multicast.Listen(&multicast.AddrResolver{Addr: localAddr})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
ssdplog.Printf("search on %s", conn.LocalAddr().String())
|
||||
|
||||
// send request.
|
||||
addr, err := multicast.SendAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err := buildSearch(addr, searchType, waitSec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.WriteTo(multicast.BytesDataProvider(msg), addr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait response.
|
||||
var list []Service
|
||||
h := func(a net.Addr, d []byte) error {
|
||||
srv, err := parseService(a, d)
|
||||
if err != nil {
|
||||
ssdplog.Printf("invalid search response from %s: %s", a.String(), err)
|
||||
return nil
|
||||
}
|
||||
list = append(list, *srv)
|
||||
ssdplog.Printf("search response from %s: %s", a.String(), srv.USN)
|
||||
return nil
|
||||
}
|
||||
d := time.Second * time.Duration(waitSec)
|
||||
if err := conn.ReadPackets(d, h); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func buildSearch(raddr net.Addr, searchType string, waitSec int) ([]byte, error) {
|
||||
b := new(bytes.Buffer)
|
||||
// FIXME: error should be checked.
|
||||
b.WriteString("M-SEARCH * HTTP/1.1\r\n")
|
||||
fmt.Fprintf(b, "HOST: %s\r\n", raddr.String())
|
||||
fmt.Fprintf(b, "MAN: %q\r\n", "ssdp:discover")
|
||||
fmt.Fprintf(b, "MX: %d\r\n", waitSec)
|
||||
fmt.Fprintf(b, "ST: %s\r\n", searchType)
|
||||
b.WriteString("\r\n")
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
var (
|
||||
errWithoutHTTPPrefix = errors.New("without HTTP prefix")
|
||||
)
|
||||
|
||||
var endOfHeader = []byte{'\r', '\n', '\r', '\n'}
|
||||
|
||||
func parseService(addr net.Addr, data []byte) (*Service, error) {
|
||||
if !bytes.HasPrefix(data, []byte("HTTP")) {
|
||||
return nil, errWithoutHTTPPrefix
|
||||
}
|
||||
// Complement newlines on tail of header for buggy SSDP responses.
|
||||
if !bytes.HasSuffix(data, endOfHeader) {
|
||||
// why we should't use append() for this purpose:
|
||||
// https://play.golang.org/p/IM1pONW9lqm
|
||||
data = bytes.Join([][]byte{data, endOfHeader}, nil)
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(data)), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return &Service{
|
||||
Type: resp.Header.Get("ST"),
|
||||
USN: resp.Header.Get("USN"),
|
||||
Location: resp.Header.Get("LOCATION"),
|
||||
Server: resp.Header.Get("SERVER"),
|
||||
rawHeader: resp.Header,
|
||||
}, nil
|
||||
}
|
||||
37
vendor/github.com/koron/go-ssdp/ssdp.go
generated
vendored
Normal file
37
vendor/github.com/koron/go-ssdp/ssdp.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/koron/go-ssdp/internal/multicast"
|
||||
"github.com/koron/go-ssdp/internal/ssdplog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
multicast.InterfacesProvider = func() []net.Interface {
|
||||
return Interfaces
|
||||
}
|
||||
ssdplog.LoggerProvider = func() *log.Logger {
|
||||
return Logger
|
||||
}
|
||||
}
|
||||
|
||||
// Interfaces specify target interfaces to multicast. If no interfaces are
|
||||
// specified, all interfaces will be used.
|
||||
var Interfaces []net.Interface
|
||||
|
||||
// Logger is default logger for SSDP module.
|
||||
var Logger *log.Logger
|
||||
|
||||
// SetMulticastRecvAddrIPv4 updates multicast address where to receive packets.
|
||||
// This never fail now.
|
||||
func SetMulticastRecvAddrIPv4(addr string) error {
|
||||
return multicast.SetRecvAddrIPv4(addr)
|
||||
}
|
||||
|
||||
// SetMulticastSendAddrIPv4 updates a UDP address to send multicast packets.
|
||||
// This never fail now.
|
||||
func SetMulticastSendAddrIPv4(addr string) error {
|
||||
return multicast.SetSendAddrIPv4(addr)
|
||||
}
|
||||
5
vendor/github.com/koron/go-ssdp/staticcheck.conf
generated
vendored
Normal file
5
vendor/github.com/koron/go-ssdp/staticcheck.conf
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# vim:set ft=toml:
|
||||
|
||||
checks = ["all"]
|
||||
|
||||
# based on: github.com/koron-go/_skeleton/staticcheck.conf
|
||||
Reference in New Issue
Block a user