5387aeeebe
Cleanup, more reorg, more arguments, started adding tests Comments and another test Simplify writing of peers and add unit test
226 lines
4.8 KiB
Go
226 lines
4.8 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
)
|
|
|
|
const maxBufferSize = 1024
|
|
// genesis blocktime (which is actually wrong)
|
|
const magic = 1446058291
|
|
const protocolVersion = 1
|
|
|
|
// SPVPing is a struct for the format of how to ping another hub over udp.
|
|
// format b'!lB64s'
|
|
type SPVPing struct {
|
|
magic uint32
|
|
version byte
|
|
padding []byte //64
|
|
}
|
|
|
|
// SPVPong is a struct for the return pong from another hub server.
|
|
// format b'!BBL32s4sH'
|
|
type SPVPong struct {
|
|
protocolVersion byte
|
|
flags byte
|
|
height uint32
|
|
tip []byte // 32
|
|
srcAddrRaw []byte // 4
|
|
country uint16
|
|
}
|
|
|
|
// encodeSPVPing creates a slice of bytes to ping another hub with
|
|
// over udp.
|
|
func encodeSPVPing() []byte {
|
|
data := make([]byte, 69)
|
|
|
|
binary.BigEndian.PutUint32(data, magic)
|
|
data[4] = protocolVersion
|
|
|
|
return data
|
|
}
|
|
|
|
// decodeSPVPing takes a slice of bytes and decodes an SPVPing struct from them.
|
|
func decodeSPVPing(data []byte) *SPVPing {
|
|
if len(data) < 69 {
|
|
return nil
|
|
}
|
|
|
|
parsedMagic := binary.BigEndian.Uint32(data)
|
|
parsedProtocalVersion := data[4]
|
|
return &SPVPing{
|
|
magic: parsedMagic,
|
|
version: parsedProtocalVersion,
|
|
}
|
|
}
|
|
|
|
// Encode is a function for SPVPong structs to encode them into bytes for
|
|
// sending over udp.
|
|
func (pong *SPVPong) Encode() []byte {
|
|
data := make([]byte, 44)
|
|
|
|
data[0] = pong.protocolVersion
|
|
data[1] = pong.flags
|
|
binary.BigEndian.PutUint32(data[2:], pong.height)
|
|
copy(data[6:], pong.tip)
|
|
copy(data[38:], pong.srcAddrRaw)
|
|
binary.BigEndian.PutUint16(data[42:], pong.country)
|
|
|
|
return data
|
|
}
|
|
|
|
// makeSPVPong creates an SPVPong struct according to given parameters.
|
|
// FIXME: Currently, does not correctly encode the country.
|
|
func makeSPVPong(flags int, height int, tip []byte, sourceAddr string, country string) *SPVPong {
|
|
byteAddr := EncodeAddress(sourceAddr)
|
|
countryInt := 1
|
|
return &SPVPong{
|
|
protocolVersion: protocolVersion,
|
|
flags: byte(flags),
|
|
height: uint32(height),
|
|
tip: tip,
|
|
srcAddrRaw: byteAddr,
|
|
country: uint16(countryInt),
|
|
}
|
|
}
|
|
|
|
// decodeSPVPong takes a slice of bytes and decodes an SPVPong struct
|
|
// from it.
|
|
func decodeSPVPong(data []byte) *SPVPong {
|
|
if len(data) < 44 {
|
|
return nil
|
|
}
|
|
|
|
parsedProtocalVersion := data[0]
|
|
flags := data[1]
|
|
height := binary.BigEndian.Uint32(data[:2])
|
|
tip := make([]byte, 32)
|
|
copy(tip, data[6:38])
|
|
srcRawAddr := make([]byte, 4)
|
|
copy(srcRawAddr, data[38:42])
|
|
country := binary.BigEndian.Uint16(data[:42])
|
|
return &SPVPong{
|
|
protocolVersion: parsedProtocalVersion,
|
|
flags: flags,
|
|
height: height,
|
|
tip: tip,
|
|
srcAddrRaw: srcRawAddr,
|
|
country: country,
|
|
}
|
|
}
|
|
|
|
// EncodeAddress takes an ipv4 address and encodes it into bytes for the hub
|
|
// Ping/Pong protocol.
|
|
func EncodeAddress(addr string) []byte {
|
|
parts := strings.Split(addr, ".")
|
|
|
|
if len(parts) != 4 {
|
|
return []byte{}
|
|
}
|
|
|
|
data := make([]byte, 4)
|
|
for i, part := range parts {
|
|
x, err := strconv.Atoi(part)
|
|
if err != nil || x > 255 {
|
|
return []byte{}
|
|
}
|
|
data[i] = byte(x)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// DecodeAddress gets the string ipv4 address from an SPVPong struct.
|
|
func (pong *SPVPong) DecodeAddress() string {
|
|
return fmt.Sprintf("%d.%d.%d.%d",
|
|
pong.srcAddrRaw[0],
|
|
pong.srcAddrRaw[1],
|
|
pong.srcAddrRaw[2],
|
|
pong.srcAddrRaw[3],
|
|
)
|
|
}
|
|
|
|
// UDPPing sends a ping over udp to another hub and returns the ip address of
|
|
// this hub.
|
|
func UDPPing(address string) (string, error) {
|
|
addr, err := net.ResolveUDPAddr("udp", address)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
conn, err := net.DialUDP("udp", nil, addr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
_, err = conn.Write(encodeSPVPing())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
buffer := make([]byte, maxBufferSize)
|
|
deadline := time.Now().Add(time.Second)
|
|
err = conn.SetReadDeadline(deadline)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
n, _, err := conn.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
pong := decodeSPVPong(buffer[:n])
|
|
|
|
if pong == nil {
|
|
return "", errors.Base("Pong decoding failed")
|
|
}
|
|
|
|
myAddr := pong.DecodeAddress()
|
|
|
|
return myAddr, nil
|
|
}
|
|
|
|
// UDPServer is a goroutine that starts an udp server that implements the hubs
|
|
// Ping/Pong protocol to find out about each other without making full TCP
|
|
// connections.
|
|
func UDPServer(args *Args) error {
|
|
address := ":" + args.UDPPort
|
|
tip := make([]byte, 32)
|
|
addr, err := net.ResolveUDPAddr("udp", address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
buffer := make([]byte, maxBufferSize)
|
|
for {
|
|
//TODO verify ping
|
|
_, addr, err := conn.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sAddr := addr.IP.String()
|
|
pong := makeSPVPong(0,0, tip, sAddr, args.Country)
|
|
data := pong.Encode()
|
|
|
|
_, err = conn.WriteToUDP(data, addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|