Skip to content

Instantly share code, notes, and snippets.

@tru
Created March 11, 2014 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tru/9488079 to your computer and use it in GitHub Desktop.
Save tru/9488079 to your computer and use it in GitHub Desktop.
GDM lookup in Go.
package gdm
import (
"fmt"
"log"
"net"
"strconv"
"strings"
"time"
)
var SERVER_WAIT_TIME time.Duration = 2
var REFRESH_INTERVAL time.Duration = 10
var BROWSE_PORT int = 32414
type GDMMessage struct {
Address *net.UDPAddr
Added bool
Properties map[string]string
}
type GDMBrowser struct {
Connection *net.UDPConn
MessageChannel chan *GDMMessage
ticker *time.Ticker
}
func setupBrowser() *GDMBrowser {
// setup listener to broadcast stuff
var conn *net.UDPConn
if udpaddr, err := net.ResolveUDPAddr("udp4", ":0"); err == nil {
var lerr error
if conn, lerr = net.ListenUDP("udp4", udpaddr); err != nil {
log.Println("Failed to listen:", lerr)
return nil
}
} else {
return nil
}
return &GDMBrowser{Connection: conn, MessageChannel: make(chan *GDMMessage), ticker: time.NewTicker(time.Second * REFRESH_INTERVAL)}
}
func newGDMMessage(data string, addr *net.UDPAddr) *GDMMessage {
msglines := strings.Split(data, "\r\n")
gdmmsg := GDMMessage{Address: addr, Added: true, Properties: make(map[string]string)}
for _, m := range msglines {
if strings.Contains(m, ":") {
kv := strings.Split(m, ":")
if len(kv) == 2 {
gdmmsg.Properties[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
}
}
return &gdmmsg
}
func (browser *GDMBrowser) listen() {
go func() {
log.Println("Listening")
for {
var buf [1024]byte
_, raddr, err := browser.Connection.ReadFromUDP(buf[0:])
if err == nil {
msg := string(buf[0:])
if strings.HasPrefix(msg, "HTTP/1.0 200 OK") {
gdmmsg := newGDMMessage(msg, raddr)
log.Println("Reply from", gdmmsg.Properties["Name"])
browser.MessageChannel <- gdmmsg
}
} else {
log.Println("ReadFromUDP error: ", err)
}
}
}()
}
func (browser *GDMBrowser) browse(port int) {
addrs := findBroadcastAddrs()
ports := strconv.Itoa(port)
for _, a := range addrs {
log.Println("Sending browse packet on", a+":"+ports)
if udpdest, err := net.ResolveUDPAddr("udp4", a+":"+ports); err == nil {
buf := []byte("M-SEARCH * HTTP/1.1\r\n\r\n")
if _, err := browser.Connection.WriteToUDP(buf, udpdest); err != nil {
log.Println("Failed to send m-search:", err)
}
} else {
log.Println("Failed to resolveudpaddr:", err)
}
}
browser.waitForReply()
}
func (browser *GDMBrowser) waitForReply() {
go func() {
timer := time.NewTimer(time.Second * SERVER_WAIT_TIME)
servers := make(map[string]*GDMMessage)
done := false
for !done {
select {
case <-timer.C:
done = true
case msg := <-browser.MessageChannel:
if uuid, ok := msg.Properties["Resource-Identifier"]; ok {
servers[uuid] = msg
}
}
}
log.Println("Scanned", len(servers), "servers")
}()
}
func findBroadcastAddrs() []string {
var broadcastAddrs []string
ifaces, _ := net.Interfaces()
for _, a := range ifaces {
/* only send on interfaces that are up and can
do broadcast */
upandbcast := net.FlagBroadcast | net.FlagUp
if a.Flags&upandbcast == upandbcast {
addr, _ := a.Addrs()
if len(addr) != 0 {
for _, ifaddr := range addr {
var bcast []string
ip, ipn, _ := net.ParseCIDR(ifaddr.String())
// we only handle ipv4 addresses for now
ipv4 := ip.To4()
if ipv4 == nil {
continue
}
for i, m := range ipn.Mask {
if m == 0 {
bcast = append(bcast, "255")
} else {
bcast = append(bcast, fmt.Sprintf("%d", ipv4[i]))
}
}
broadcastAddrs = append(broadcastAddrs, strings.Join(bcast, "."))
}
}
}
}
return broadcastAddrs
}
func Init() *GDMBrowser {
log.SetPrefix("[GDM] ")
browser := setupBrowser()
browser.listen()
browser.browse(BROWSE_PORT)
// browse when the ticker hits
go func() {
for {
<-browser.ticker.C
browser.browse(BROWSE_PORT)
}
}()
return browser
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment