herald.go/server/udp.go

255 lines
5.6 KiB
Go

package server
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"strings"
"time"
pb "github.com/lbryio/herald.go/protobuf/go"
)
const maxBufferSize = 1024
// genesis blocktime (which is actually wrong)
// magic constant for the UDPPing protocol. The above comment is taken from
// the python code this was implemented off of.
// https://github.com/lbryio/lbry-sdk/blob/7d49b046d44a4b7067d5dc1d6cd65ff0475c71c8/lbry/wallet/server/udp.py#L12
const magic = 1446058291
const protocolVersion = 1
const defaultFlags = 0b00000000
const availableFlag = 0b00000001
// 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.
func makeSPVPong(flags int, height int, tip []byte, sourceAddr string, country string) *SPVPong {
byteAddr := EncodeAddress(sourceAddr)
var countryInt int32
var ok bool
if countryInt, ok = pb.Location_Country_value[country]; !ok {
countryInt = int32(pb.Location_UNKNOWN_COUNTRY)
}
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() net.IP {
return net.IPv4(
pong.srcAddrRaw[0],
pong.srcAddrRaw[1],
pong.srcAddrRaw[2],
pong.srcAddrRaw[3],
)
}
func (pong *SPVPong) DecodeCountry() string {
return pb.Location_Country_name[int32(pong.country)]
}
func (pong *SPVPong) DecodeProtocolVersion() int {
return int(pong.protocolVersion)
}
func (pong *SPVPong) DecodeHeight() int {
return int(pong.height)
}
func (pong *SPVPong) DecodeTip() []byte {
return pong.tip
}
func (pong *SPVPong) DecodeFlags() byte {
return pong.flags
}
// UDPPing sends a ping over udp to another hub and returns the ip address of
// this hub.
func UDPPing(ip, port string) (*SPVPong, error) {
address := ip + ":" + port
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return nil, err
}
defer conn.Close()
_, err = conn.Write(encodeSPVPing())
if err != nil {
return nil, err
}
buffer := make([]byte, maxBufferSize)
deadline := time.Now().Add(time.Second)
err = conn.SetReadDeadline(deadline)
if err != nil {
return nil, err
}
n, _, err := conn.ReadFromUDP(buffer)
if err != nil {
return nil, err
}
pong := decodeSPVPong(buffer[:n])
if pong == nil {
return nil, fmt.Errorf("Pong decoding failed")
}
return pong, 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 (s *Server) UDPServer(port int) error {
address := ":" + strconv.Itoa(port)
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(defaultFlags|availableFlag, 0, tip, sAddr, s.Args.Country)
data := pong.Encode()
_, err = conn.WriteToUDP(data, addr)
if err != nil {
return err
}
}
}