Perform external queries

This commit is contained in:
Jakob Borg 2013-12-22 17:13:51 -05:00
parent e48222ada0
commit 31ea72dbb3
4 changed files with 282 additions and 111 deletions

View File

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

View File

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

160
discover/encoding.go Normal file
View File

@ -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 ""
}
}

View File

@ -15,7 +15,8 @@ var testdata = []struct {
[]byte{0x20, 0x12, 0x10, 0x25, []byte{0x20, 0x12, 0x10, 0x25,
0x12, 0x34, 0x00, 0x00, 0x12, 0x34, 0x00, 0x00,
0x00, 0x00, 0x00, 0x05, 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{ &packet{
magic: 0x20121025, magic: 0x20121025,
port: 0x1234, port: 0x1234,
@ -27,11 +28,14 @@ var testdata = []struct {
[]byte{0x20, 0x12, 0x10, 0x25, []byte{0x20, 0x12, 0x10, 0x25,
0x34, 0x56, 0x00, 0x00, 0x34, 0x56, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 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{ &packet{
magic: 0x20121025, magic: 0x20121025,
port: 0x3456, port: 0x3456,
id: "hello!!!", id: "hello!!!",
ip: []byte{1, 2, 3, 4},
}, },
nil, nil,
}, },
@ -49,7 +53,8 @@ var testdata = []struct {
[]byte{0x20, 0x12, 0x10, 0x25, []byte{0x20, 0x12, 0x10, 0x25,
0x12, 0x34, 0x12, 0x34, // reserved bits not set to zero 0x12, 0x34, 0x12, 0x34, // reserved bits not set to zero
0x00, 0x00, 0x00, 0x06, 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, nil,
errFormat, errFormat,
}, },
@ -57,7 +62,8 @@ var testdata = []struct {
[]byte{0x20, 0x12, 0x10, 0x25, []byte{0x20, 0x12, 0x10, 0x25,
0x12, 0x34, 0x00, 0x00, 0x12, 0x34, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 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, nil,
errFormat, 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)
}
}
}