Browse Source

Perform external queries

Jakob Borg 12 years ago
parent
commit
31ea72dbb3
4 changed files with 270 additions and 99 deletions
  1. 3 0
      build.sh
  2. 76 95
      discover/discover.go
  3. 160 0
      discover/encoding.go
  4. 31 4
      discover/encoding_test.go

+ 3 - 0
build.sh

@@ -2,8 +2,11 @@
 
 version=$(git describe --always)
 
+go test ./... || exit 1
+
 for goos in darwin linux freebsd ; do
 	for goarch in amd64 386 ; do
+		echo "$goos-$goarch"
 		export GOOS="$goos"
 		export GOARCH="$goarch"
 		go build -ldflags "-X main.Version $version" \

+ 76 - 95
discover/discover.go

@@ -20,6 +20,12 @@ following format:
     \                   NodeID (variable length)                    \
     /                                                               /
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    |                          Length of IP                         |
+    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    /                                                               /
+    \                     IP (variable length)                      \
+    /                                                               /
+    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
 This is the XDR encoding of:
 
@@ -31,11 +37,15 @@ struct Announcement {
 
 (Hence NodeID is padded to a multiple of 32 bits)
 
-The sending node's address is not encoded -- it is taken to be the source
-address of the announcement. Every time such a packet is received, a local
-table that maps NodeID to Address is updated. When the local node wants to
-connect to another node with the address specification 'dynamic', this table is
-consulted.
+The sending node's address is not encoded in local announcement -- the Length
+of IP field is set to zero and the address is taken to be the source address of
+the announcement. In announcement packets sent by a discovery server in
+response to a query, the IP is present and the length is either 4 (IPv4) or 16
+(IPv6).
+
+Every time such a packet is received, a local table that maps NodeID to Address
+is updated. When the local node wants to connect to another node with the
+address specification 'dynamic', this table is consulted.
 
 For external discovery, an identical packet is sent every 30 minutes to the
 external discovery server. The server keeps information for up to 60 minutes.
@@ -71,8 +81,6 @@ server from being used as an amplifier in a DDoS attack.)
 package discover
 
 import (
-	"encoding/binary"
-	"errors"
 	"fmt"
 	"log"
 	"net"
@@ -86,11 +94,6 @@ const (
 	QueryMagic        = 0x19760309
 )
 
-var (
-	errBadMagic = errors.New("bad magic")
-	errFormat   = errors.New("incorrect packet format")
-)
-
 type Discoverer struct {
 	MyID             string
 	ListenPort       int
@@ -104,13 +107,7 @@ type Discoverer struct {
 	extServer    string
 }
 
-type packet struct {
-	magic uint32 // AnnouncementMagic or QueryMagic
-	port  uint16 // unset if magic == QueryMagic
-	id    string
-}
-
-// We tolerate a certain amount of errors because we might be running in
+// We tolerate a certain amount of errors because we might be running on
 // laptops that sleep and wake, have intermittent network connectivity, etc.
 // When we hit this many errors in succession, we stop.
 const maxErrors = 30
@@ -149,7 +146,7 @@ func NewDiscoverer(id string, port int, extPort int, extServer string) (*Discove
 func (d *Discoverer) sendAnnouncements() {
 	remote4 := &net.UDPAddr{IP: net.IP{255, 255, 255, 255}, Port: AnnouncementPort}
 
-	buf := encodePacket(packet{AnnouncementMagic, uint16(d.ListenPort), d.MyID})
+	buf := encodePacket(packet{AnnouncementMagic, uint16(d.ListenPort), d.MyID, nil})
 	go d.writeAnnouncements(buf, remote4, d.BroadcastIntv)
 }
 
@@ -160,7 +157,7 @@ func (d *Discoverer) sendExtAnnouncements() {
 		return
 	}
 
-	buf := encodePacket(packet{AnnouncementMagic, uint16(d.ExtListenPort), d.MyID})
+	buf := encodePacket(packet{AnnouncementMagic, uint16(d.ExtListenPort), d.MyID, nil})
 	for _, extIP := range extIPs {
 		remote4 := &net.UDPAddr{IP: extIP, Port: AnnouncementPort}
 		go d.writeAnnouncements(buf, remote4, d.ExtBroadcastIntv)
@@ -215,93 +212,77 @@ func (d *Discoverer) recvAnnouncements() {
 	log.Println("discover/read: stopping due to too many errors:", err)
 }
 
-func (d *Discoverer) Lookup(node string) (string, bool) {
-	d.registryLock.Lock()
-	defer d.registryLock.Unlock()
-	addr, ok := d.registry[node]
-	return addr, ok
-}
-
-func encodePacket(pkt packet) []byte {
-	var idbs = []byte(pkt.id)
-	var l = len(idbs) + pad(len(idbs)) + 4 + 4
-	if pkt.magic == AnnouncementMagic {
-		l += 4
-	}
-
-	var buf = make([]byte, l)
-	var offset = 0
-
-	binary.BigEndian.PutUint32(buf[offset:], pkt.magic)
-	offset += 4
-
-	if pkt.magic == AnnouncementMagic {
-		binary.BigEndian.PutUint16(buf[offset:], uint16(pkt.port))
-		offset += 4
+func (d *Discoverer) externalLookup(node string) (string, bool) {
+	extIPs, err := net.LookupIP(d.extServer)
+	if err != nil {
+		log.Printf("discover/external: %v; no external lookup", err)
+		return "", false
 	}
 
-	binary.BigEndian.PutUint32(buf[offset:], uint32(len(idbs)))
-	offset += 4
-	copy(buf[offset:], idbs)
+	var res = make(chan string, len(extIPs))
+	var failed = 0
+	for _, extIP := range extIPs {
+		remote := &net.UDPAddr{IP: extIP, Port: AnnouncementPort}
+		conn, err := net.DialUDP("udp", nil, remote)
+		if err != nil {
+			log.Printf("discover/external: %v; no external lookup", err)
+			failed++
+			continue
+		}
 
-	return buf
-}
+		_, err = conn.Write(encodePacket(packet{QueryMagic, 0, node, nil}))
+		if err != nil {
+			log.Printf("discover/external: %v; no external lookup", err)
+			failed++
+			continue
+		}
 
-func decodePacket(buf []byte) (*packet, error) {
-	var p packet
-	var offset int
+		go func() {
+			var buf = make([]byte, 1024)
+			_, err = conn.Read(buf)
+			if err != nil {
+				log.Printf("discover/external/read: %v; no external lookup", err)
+				return
+			}
 
-	if len(buf) < 4 {
-		// short packet
-		return nil, errFormat
-	}
-	p.magic = binary.BigEndian.Uint32(buf[offset:])
-	offset += 4
+			pkt, err := decodePacket(buf)
+			if err != nil {
+				log.Printf("discover/external/read: %v; no external lookup", err)
+				return
+			}
 
-	if p.magic != AnnouncementMagic && p.magic != QueryMagic {
-		return nil, errBadMagic
-	}
+			if pkt.magic != AnnouncementMagic {
+				log.Printf("discover/external/read: bad magic; no external lookup", err)
+				return
+			}
 
-	if p.magic == AnnouncementMagic {
-		if len(buf) < offset+4 {
-			// short packet
-			return nil, errFormat
-		}
-		p.port = binary.BigEndian.Uint16(buf[offset:])
-		offset += 2
-		reserved := binary.BigEndian.Uint16(buf[offset:])
-		if reserved != 0 {
-			return nil, errFormat
-		}
-		offset += 2
+			res <- fmt.Sprintf("%s:%d", ipStr(pkt.ip), pkt.port)
+		}()
 	}
 
-	if len(buf) < offset+4 {
-		// short packet
-		return nil, errFormat
+	if failed == len(extIPs) {
+		// no point in waiting
+		return "", false
 	}
-	l := binary.BigEndian.Uint32(buf[offset:])
-	offset += 4
 
-	if len(buf) < offset+int(l)+pad(int(l)) {
-		// short packet
-		return nil, errFormat
-	}
-	idbs := buf[offset : offset+int(l)]
-	p.id = string(idbs)
-	offset += int(l) + pad(int(l))
-	if len(buf[offset:]) > 0 {
-		// extra data
-		return nil, errFormat
+	select {
+	case r := <-res:
+		return r, true
+	case <-time.After(5 * time.Second):
+		return "", false
 	}
-
-	return &p, nil
 }
 
-func pad(l int) int {
-	d := l % 4
-	if d == 0 {
-		return 0
+func (d *Discoverer) Lookup(node string) (string, bool) {
+	d.registryLock.Lock()
+	addr, ok := d.registry[node]
+	d.registryLock.Unlock()
+
+	if ok {
+		return addr, true
+	} else if len(d.extServer) != 0 {
+		// We might want to cache this, but not permanently so it needs some intelligence
+		return d.externalLookup(node)
 	}
-	return 4 - d
+	return "", false
 }

+ 160 - 0
discover/encoding.go

@@ -0,0 +1,160 @@
+package discover
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+)
+
+type packet struct {
+	magic uint32 // AnnouncementMagic or QueryMagic
+	port  uint16 // unset if magic == QueryMagic
+	id    string
+	ip    []byte // zero length in local announcements
+}
+
+var (
+	errBadMagic = errors.New("bad magic")
+	errFormat   = errors.New("incorrect packet format")
+)
+
+func encodePacket(pkt packet) []byte {
+	if l := len(pkt.ip); l != 0 && l != 4 && l != 16 {
+		// bad ip format
+		return nil
+	}
+
+	var idbs = []byte(pkt.id)
+	var l = 4 + 4 + len(idbs) + pad(len(idbs))
+	if pkt.magic == AnnouncementMagic {
+		l += 4 + 4 + len(pkt.ip)
+	}
+
+	var buf = make([]byte, l)
+	var offset = 0
+
+	binary.BigEndian.PutUint32(buf[offset:], pkt.magic)
+	offset += 4
+
+	if pkt.magic == AnnouncementMagic {
+		binary.BigEndian.PutUint16(buf[offset:], uint16(pkt.port))
+		offset += 4
+	}
+
+	binary.BigEndian.PutUint32(buf[offset:], uint32(len(idbs)))
+	offset += 4
+	copy(buf[offset:], idbs)
+	offset += len(idbs) + pad(len(idbs))
+
+	if pkt.magic == AnnouncementMagic {
+		binary.BigEndian.PutUint32(buf[offset:], uint32(len(pkt.ip)))
+		offset += 4
+		copy(buf[offset:], pkt.ip)
+		offset += len(pkt.ip)
+	}
+
+	return buf
+}
+
+func decodePacket(buf []byte) (*packet, error) {
+	var p packet
+	var offset int
+
+	if len(buf) < 4 {
+		// short packet
+		return nil, errFormat
+	}
+	p.magic = binary.BigEndian.Uint32(buf[offset:])
+	offset += 4
+
+	if p.magic != AnnouncementMagic && p.magic != QueryMagic {
+		return nil, errBadMagic
+	}
+
+	if p.magic == AnnouncementMagic {
+		// Port Number
+
+		if len(buf) < offset+4 {
+			// short packet
+			return nil, errFormat
+		}
+		p.port = binary.BigEndian.Uint16(buf[offset:])
+		offset += 2
+		reserved := binary.BigEndian.Uint16(buf[offset:])
+		if reserved != 0 {
+			return nil, errFormat
+		}
+		offset += 2
+	}
+
+	// Node ID
+
+	if len(buf) < offset+4 {
+		// short packet
+		return nil, errFormat
+	}
+	l := binary.BigEndian.Uint32(buf[offset:])
+	offset += 4
+
+	if len(buf) < offset+int(l)+pad(int(l)) {
+		// short packet
+		return nil, errFormat
+	}
+	idbs := buf[offset : offset+int(l)]
+	p.id = string(idbs)
+	offset += int(l) + pad(int(l))
+
+	if p.magic == AnnouncementMagic {
+		// IP
+
+		if len(buf) < offset+4 {
+			// short packet
+			return nil, errFormat
+		}
+		l = binary.BigEndian.Uint32(buf[offset:])
+		offset += 4
+
+		if l != 0 && l != 4 && l != 16 {
+			// weird ip length
+			return nil, errFormat
+		}
+		if len(buf) < offset+int(l) {
+			// short packet
+			return nil, errFormat
+		}
+		if l > 0 {
+			p.ip = buf[offset : offset+int(l)]
+			offset += int(l)
+		}
+	}
+
+	if len(buf[offset:]) > 0 {
+		// extra data
+		return nil, errFormat
+	}
+
+	return &p, nil
+}
+
+func pad(l int) int {
+	d := l % 4
+	if d == 0 {
+		return 0
+	}
+	return 4 - d
+}
+
+func ipStr(ip []byte) string {
+	switch len(ip) {
+	case 4:
+		return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
+	case 16:
+		return fmt.Sprintf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
+			ip[0], ip[1], ip[2], ip[3],
+			ip[4], ip[5], ip[6], ip[7],
+			ip[8], ip[9], ip[10], ip[11],
+			ip[12], ip[13], ip[14], ip[15])
+	default:
+		return ""
+	}
+}

+ 31 - 4
discover/discover_test.go → discover/encoding_test.go

@@ -15,7 +15,8 @@ var testdata = []struct {
 		[]byte{0x20, 0x12, 0x10, 0x25,
 			0x12, 0x34, 0x00, 0x00,
 			0x00, 0x00, 0x00, 0x05,
-			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00},
+			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00},
 		&packet{
 			magic: 0x20121025,
 			port:  0x1234,
@@ -27,11 +28,14 @@ var testdata = []struct {
 		[]byte{0x20, 0x12, 0x10, 0x25,
 			0x34, 0x56, 0x00, 0x00,
 			0x00, 0x00, 0x00, 0x08,
-			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x21, 0x21},
+			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x21, 0x21,
+			0x00, 0x00, 0x00, 0x04,
+			0x01, 0x02, 0x03, 0x04},
 		&packet{
 			magic: 0x20121025,
 			port:  0x3456,
 			id:    "hello!!!",
+			ip:    []byte{1, 2, 3, 4},
 		},
 		nil,
 	},
@@ -49,7 +53,8 @@ var testdata = []struct {
 		[]byte{0x20, 0x12, 0x10, 0x25,
 			0x12, 0x34, 0x12, 0x34, // reserved bits not set to zero
 			0x00, 0x00, 0x00, 0x06,
-			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x00, 0x00},
+			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00},
 		nil,
 		errFormat,
 	},
@@ -57,7 +62,8 @@ var testdata = []struct {
 		[]byte{0x20, 0x12, 0x10, 0x25,
 			0x12, 0x34, 0x00, 0x00,
 			0x00, 0x00, 0x00, 0x06,
-			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21}, // missing padding
+			0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, // missing padding
+			0x00, 0x00, 0x00, 0x00},
 		nil,
 		errFormat,
 	},
@@ -109,3 +115,24 @@ func TestEncodePacket(t *testing.T) {
 		}
 	}
 }
+
+var ipstrTests = []struct {
+	d []byte
+	s string
+}{
+	{[]byte{192, 168, 34}, ""},
+	{[]byte{192, 168, 0, 34}, "192.168.0.34"},
+	{[]byte{0x20, 0x01, 0x12, 0x34,
+		0x34, 0x56, 0x56, 0x78,
+		0x78, 0x00, 0x00, 0xdc,
+		0x00, 0x00, 0x43, 0x54}, "2001:1234:3456:5678:7800:00dc:0000:4354"},
+}
+
+func TestIPStr(t *testing.T) {
+	for _, tc := range ipstrTests {
+		s1 := ipStr(tc.d)
+		if s1 != tc.s {
+			t.Errorf("Incorrect ipstr %q != %q", tc.s, s1)
+		}
+	}
+}