Merge pull request #24 from lbryio/feature/18/jeffreypicard/udp-endpoint

UDPServer / ip address resolution
This commit is contained in:
Jeffrey Picard 2021-12-06 11:57:47 -05:00 committed by GitHub
commit 516b95d96b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 6563 additions and 265 deletions

View file

@ -11,4 +11,4 @@ jobs:
with: with:
go-version: 1.16.5 go-version: 1.16.5
- run: go build . - run: go build .
- run: cd server && go test -v -race - run: go test -v -race ./...

View file

@ -30,4 +30,4 @@ jobs:
- run: go get github.com/golang/protobuf/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc - run: go get github.com/golang/protobuf/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc
- run: go build . - run: go build .
- run: ./protobuf/build.sh - run: ./protobuf/build.sh
- run: cd server && go test -v -race - run: go test -v -race ./...

View file

@ -18,8 +18,8 @@ var (
Help: "Number of errors by type", Help: "Number of errors by type",
}, []string{"error_type"}) }, []string{"error_type"})
QueryTime = promauto.NewHistogramVec(prometheus.HistogramOpts{ QueryTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "query_time", Name: "query_time",
Help: "Histogram of query times", Help: "Histogram of query times",
Buckets: HistogramBuckets, Buckets: HistogramBuckets,
}, []string{"method"}) }, []string{"method"})
PeersKnown = promauto.NewGauge(prometheus.GaugeOpts{ PeersKnown = promauto.NewGauge(prometheus.GaugeOpts{
@ -31,4 +31,3 @@ var (
Help: "Number of peers that are subscribed to us.", Help: "Number of peers that are subscribed to us.",
}) })
) )

14
main.go
View file

@ -26,19 +26,7 @@ func main() {
s := server.MakeHubServer(ctxWCancel, args) s := server.MakeHubServer(ctxWCancel, args)
s.Run() s.Run()
//l, err := net.Listen("tcp", ":"+args.Port)
//if err != nil {
// log.Fatalf("failed to listen: %v", err)
//}
//
//pb.RegisterHubServer(s.GrpcServer, s)
//reflection.Register(s.GrpcServer)
//
//log.Printf("listening on %s\n", l.Addr().String())
//log.Println(s.Args)
//if err := s.GrpcServer.Serve(l); err != nil {
// log.Fatalf("failed to serve: %v", err)
//}
return return
} }

File diff suppressed because it is too large Load diff

5031
protobuf/go/claim.pb.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -16,42 +16,42 @@ const (
// Args struct contains the arguments to the hub server. // Args struct contains the arguments to the hub server.
type Args struct { type Args struct {
CmdType int CmdType int
Host string Host string
Port string Port string
UDPPort string EsHost string
EsHost string EsPort string
EsPort string PrometheusPort string
PrometheusPort string EsIndex string
EsIndex string RefreshDelta int
RefreshDelta int CacheTTL int
CacheTTL int PeerFile string
PeerFile string Country string
Country string DisableEs bool
DisableEs bool Debug bool
Debug bool DisableLoadPeers bool
LoadPeers bool DisableStartPrometheus bool
StartPrometheus bool DisableStartUDP bool
StartUDP bool DisableWritePeers bool
WritePeers bool DisableFederation bool
} }
const ( const (
DefaultHost = "0.0.0.0" DefaultHost = "0.0.0.0"
DefaultPort = "50051" DefaultPort = "50051"
DefaultUdpPort = "41119" DefaultEsHost = "http://localhost"
DefaultEsHost = "http://localhost" DefaultEsIndex = "claims"
DefaultEsIndex = "claims" DefaultEsPort = "9200"
DefaultEsPort = "9200" DefaultPrometheusPort = "2112"
DefaultPrometheusPort = "2112" DefaultRefreshDelta = 5
DefaultRefreshDelta = 5 DefaultCacheTTL = 5
DefaultCacheTTL = 5 DefaultPeerFile = "peers.txt"
DefaultPeerFile = "peers.txt" DefaultCountry = "US"
DefaultCountry = "US" DefaultDisableLoadPeers = false
DefaultLoadPeers = true DefaultDisableStartPrometheus = false
DefaultStartPrometheus = true DefaultDisableStartUDP = false
DefaultStartUDP = true DefaultDisableWritePeers = false
DefaultWritePeers = true DefaultDisableFederation = false
) )
// GetEnvironment takes the environment variables as an array of strings // GetEnvironment takes the environment variables as an array of strings
@ -88,7 +88,6 @@ func ParseArgs(searchRequest *pb.SearchRequest) *Args {
port := parser.String("", "rpcport", &argparse.Options{Required: false, Help: "RPC port", Default: DefaultPort}) port := parser.String("", "rpcport", &argparse.Options{Required: false, Help: "RPC port", Default: DefaultPort})
esHost := parser.String("", "eshost", &argparse.Options{Required: false, Help: "elasticsearch host", Default: DefaultEsHost}) esHost := parser.String("", "eshost", &argparse.Options{Required: false, Help: "elasticsearch host", Default: DefaultEsHost})
esPort := parser.String("", "esport", &argparse.Options{Required: false, Help: "elasticsearch port", Default: DefaultEsPort}) esPort := parser.String("", "esport", &argparse.Options{Required: false, Help: "elasticsearch port", Default: DefaultEsPort})
udpPort := parser.String("", "uspport", &argparse.Options{Required: false, Help: "udp ping port", Default: DefaultUdpPort})
prometheusPort := parser.String("", "prometheus-port", &argparse.Options{Required: false, Help: "prometheus port", Default: DefaultPrometheusPort}) prometheusPort := parser.String("", "prometheus-port", &argparse.Options{Required: false, Help: "prometheus port", Default: DefaultPrometheusPort})
esIndex := parser.String("", "esindex", &argparse.Options{Required: false, Help: "elasticsearch index name", Default: DefaultEsIndex}) esIndex := parser.String("", "esindex", &argparse.Options{Required: false, Help: "elasticsearch index name", Default: DefaultEsIndex})
refreshDelta := parser.Int("", "refresh-delta", &argparse.Options{Required: false, Help: "elasticsearch index refresh delta in seconds", Default: DefaultRefreshDelta}) refreshDelta := parser.Int("", "refresh-delta", &argparse.Options{Required: false, Help: "elasticsearch index refresh delta in seconds", Default: DefaultRefreshDelta})
@ -98,10 +97,11 @@ func ParseArgs(searchRequest *pb.SearchRequest) *Args {
debug := parser.Flag("", "debug", &argparse.Options{Required: false, Help: "enable debug logging", Default: false}) debug := parser.Flag("", "debug", &argparse.Options{Required: false, Help: "enable debug logging", Default: false})
disableEs := parser.Flag("", "disable-es", &argparse.Options{Required: false, Help: "Disable elastic search, for running/testing independently", Default: false}) disableEs := parser.Flag("", "disable-es", &argparse.Options{Required: false, Help: "Disable elastic search, for running/testing independently", Default: false})
loadPeers := parser.Flag("", "load-peers", &argparse.Options{Required: false, Help: "load peers from disk at startup", Default: DefaultLoadPeers}) disableLoadPeers := parser.Flag("", "disable-load-peers", &argparse.Options{Required: false, Help: "Disable load peers from disk at startup", Default: DefaultDisableLoadPeers})
startPrometheus := parser.Flag("", "start-prometheus", &argparse.Options{Required: false, Help: "Start prometheus server", Default: DefaultStartPrometheus}) disableStartPrometheus := parser.Flag("", "disable-start-prometheus", &argparse.Options{Required: false, Help: "Disable start prometheus server", Default: DefaultDisableStartPrometheus})
startUdp := parser.Flag("", "start-udp", &argparse.Options{Required: false, Help: "Start UDP ping server", Default: DefaultStartUDP}) disableStartUdp := parser.Flag("", "disable-start-udp", &argparse.Options{Required: false, Help: "Disable start UDP ping server", Default: DefaultDisableStartUDP})
writePeers := parser.Flag("", "write-peers", &argparse.Options{Required: false, Help: "Write peer to disk as we learn about them", Default: DefaultWritePeers}) disableWritePeers := parser.Flag("", "disable-write-peers", &argparse.Options{Required: false, Help: "Disable write peer to disk as we learn about them", Default: DefaultDisableWritePeers})
disableFederation := parser.Flag("", "disable-federation", &argparse.Options{Required: false, Help: "Disable server federation", Default: DefaultDisableFederation})
text := parser.String("", "text", &argparse.Options{Required: false, Help: "text query"}) text := parser.String("", "text", &argparse.Options{Required: false, Help: "text query"})
name := parser.String("", "name", &argparse.Options{Required: false, Help: "name"}) name := parser.String("", "name", &argparse.Options{Required: false, Help: "name"})
@ -120,24 +120,24 @@ func ParseArgs(searchRequest *pb.SearchRequest) *Args {
} }
args := &Args{ args := &Args{
CmdType: SearchCmd, CmdType: SearchCmd,
Host: *host, Host: *host,
Port: *port, Port: *port,
EsHost: *esHost, EsHost: *esHost,
EsPort: *esPort, EsPort: *esPort,
UDPPort: *udpPort, PrometheusPort: *prometheusPort,
PrometheusPort: *prometheusPort, EsIndex: *esIndex,
EsIndex: *esIndex, RefreshDelta: *refreshDelta,
RefreshDelta: *refreshDelta, CacheTTL: *cacheTTL,
CacheTTL: *cacheTTL, PeerFile: *peerFile,
PeerFile: *peerFile, Country: *country,
Country: *country, DisableEs: *disableEs,
DisableEs: *disableEs, Debug: *debug,
Debug: *debug, DisableLoadPeers: *disableLoadPeers,
LoadPeers: *loadPeers, DisableStartPrometheus: *disableStartPrometheus,
StartPrometheus: *startPrometheus, DisableStartUDP: *disableStartUdp,
StartUDP: *startUdp, DisableWritePeers: *disableWritePeers,
WritePeers: *writePeers, DisableFederation: *disableFederation,
} }
if esHost, ok := environment["ELASTIC_HOST"]; ok { if esHost, ok := environment["ELASTIC_HOST"]; ok {
@ -157,7 +157,7 @@ func ParseArgs(searchRequest *pb.SearchRequest) *Args {
} }
/* /*
Verify no invalid argument combinations Verify no invalid argument combinations
*/ */
if len(*channelIds) > 0 && *channelId != "" { if len(*channelIds) > 0 && *channelId != "" {
log.Fatal("Cannot specify both channel_id and channel_ids") log.Fatal("Cannot specify both channel_id and channel_ids")

View file

@ -4,6 +4,8 @@ import (
"bufio" "bufio"
"context" "context"
"log" "log"
"math"
"net"
"os" "os"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -14,31 +16,31 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
// FederatedServer hold relevant information about peers that we known about. // Peer holds relevant information about peers that we know about.
type FederatedServer struct { type Peer struct {
Address string Address string
Port string Port string
Ts time.Time LastSeen time.Time
} }
var ( var (
localHosts = map[string]bool{ localHosts = map[string]bool{
"127.0.0.1": true, "127.0.0.1": true,
"0.0.0.0": true, "0.0.0.0": true,
"localhost": true, "localhost": true,
"<nil>": true, // Empty net.IP turned into a string
} }
) )
// peerKey takes a peer and returns the key that for that peer
// peerKey takes a ServerMessage object and returns the key that for that peer
// in our peer table. // in our peer table.
func peerKey(msg *pb.ServerMessage) string { func peerKey(peer *Peer) string {
return msg.Address + ":" + msg.Port return peer.Address + ":" + peer.Port
} }
// peerKey is a function on a FederatedServer struct to return the key for that // peerKey is a function on a FederatedServer struct to return the key for that
// peer is out peer table. // peer is out peer table.
func (peer *FederatedServer) peerKey() string { func (peer *Peer) peerKey() string {
return peer.Address + ":" + peer.Port return peer.Address + ":" + peer.Port
} }
@ -66,6 +68,19 @@ func (s *Server) getNumSubs() int64 {
return *s.NumPeerSubs return *s.NumPeerSubs
} }
// getAndSetExternalIp detects the server's external IP and stores it.
func (s *Server) getAndSetExternalIp(ip, port string) error {
pong, err := UDPPing(ip, port)
if err != nil {
return err
}
myIp := pong.DecodeAddress()
log.Println("my ip: ", myIp)
s.ExternalIP = myIp
return nil
}
// loadPeers takes the arguments given to the hub at startup and loads the // loadPeers takes the arguments given to the hub at startup and loads the
// previously known peers from disk and verifies their existence before // previously known peers from disk and verifies their existence before
// storing them as known peers. Returns a map of peerKey -> object // storing them as known peers. Returns a map of peerKey -> object
@ -73,6 +88,32 @@ func (s *Server) loadPeers() error {
peerFile := s.Args.PeerFile peerFile := s.Args.PeerFile
port := s.Args.Port port := s.Args.Port
// First we make sure our server has come up, so we can answer back to peers.
var failures = 0
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
retry:
time.Sleep(time.Second * time.Duration(math.Pow(float64(failures), 2)))
conn, err := grpc.DialContext(ctx,
"0.0.0.0:"+port,
grpc.WithInsecure(),
grpc.WithBlock(),
)
if err != nil {
if failures > 3 {
log.Println("Warning! Our endpoint doesn't seem to have come up, didn't load peers")
return err
}
failures += 1
goto retry
}
if err = conn.Close(); err != nil {
log.Println(err)
}
cancel()
f, err := os.Open(peerFile) f, err := os.Open(peerFile)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -90,26 +131,30 @@ func (s *Server) loadPeers() error {
} }
for _, line := range text { for _, line := range text {
ipPort := strings.Split(line,":") ipPort := strings.Split(line, ":")
if len(ipPort) != 2 { if len(ipPort) != 2 {
log.Println("Malformed entry in peer file") log.Println("Malformed entry in peer file")
continue continue
} }
// If the peer is us, skip // If the peer is us, skip
log.Println(ipPort) log.Println(ipPort)
if ipPort[1] == port && localHosts[ipPort[0]] { if ipPort[1] == port &&
(localHosts[ipPort[0]] || ipPort[0] == s.ExternalIP.String()) {
log.Println("Self peer, skipping ...") log.Println("Self peer, skipping ...")
continue continue
} }
srvMsg := &pb.ServerMessage{
Address: ipPort[0], newPeer := &Peer{
Port: ipPort[1], Address: ipPort[0],
Port: ipPort[1],
LastSeen: time.Now(),
} }
log.Printf("pinging peer %+v\n", srvMsg) log.Printf("pinging peer %+v\n", newPeer)
err := s.addPeer(srvMsg, true) err = s.addPeer(newPeer, true, true)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} }
log.Println("Returning from loadPeers") log.Println("Returning from loadPeers")
@ -118,7 +163,7 @@ func (s *Server) loadPeers() error {
// subscribeToPeer subscribes us to a peer to we'll get updates about their // subscribeToPeer subscribes us to a peer to we'll get updates about their
// known peers. // known peers.
func (s *Server) subscribeToPeer(peer *FederatedServer) error { func (s *Server) subscribeToPeer(peer *Peer) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
@ -133,20 +178,18 @@ func (s *Server) subscribeToPeer(peer *FederatedServer) error {
defer conn.Close() defer conn.Close()
msg := &pb.ServerMessage{ msg := &pb.ServerMessage{
Address: s.Args.Host, Address: s.ExternalIP.String(),
Port: s.Args.Port, Port: s.Args.Port,
} }
c := pb.NewHubClient(conn) c := pb.NewHubClient(conn)
log.Printf("%s:%s subscribing to %+v\n", s.Args.Host, s.Args.Port, peer) log.Printf("%s:%s subscribing to %+v\n", s.ExternalIP, s.Args.Port, peer)
_, err = c.PeerSubscribe(ctx, msg) _, err = c.PeerSubscribe(ctx, msg)
if err != nil { if err != nil {
return err return err
} }
s.Subscribed = true
return nil return nil
} }
@ -155,13 +198,13 @@ func (s *Server) subscribeToPeer(peer *FederatedServer) error {
// This is used to confirm existence of peers on start and let them // This is used to confirm existence of peers on start and let them
// know about us. Returns the response from the server on success, // know about us. Returns the response from the server on success,
// nil otherwise. // nil otherwise.
func (s *Server) helloPeer(server *FederatedServer) (*pb.HelloMessage, error) { func (s *Server) helloPeer(peer *Peer) (*pb.HelloMessage, error) {
log.Println("In helloPeer") log.Println("In helloPeer")
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
conn, err := grpc.DialContext(ctx, conn, err := grpc.DialContext(ctx,
server.Address+":"+server.Port, peer.Address+":"+peer.Port,
grpc.WithInsecure(), grpc.WithInsecure(),
grpc.WithBlock(), grpc.WithBlock(),
) )
@ -174,12 +217,12 @@ func (s *Server) helloPeer(server *FederatedServer) (*pb.HelloMessage, error) {
c := pb.NewHubClient(conn) c := pb.NewHubClient(conn)
msg := &pb.HelloMessage{ msg := &pb.HelloMessage{
Port: s.Args.Port, Port: s.Args.Port,
Host: s.Args.Host, Host: s.ExternalIP.String(),
Servers: []*pb.ServerMessage{}, Servers: []*pb.ServerMessage{},
} }
log.Printf("%s:%s saying hello to %+v\n", s.Args.Host, s.Args.Port, server) log.Printf("%s:%s saying hello to %+v\n", s.ExternalIP, s.Args.Port, peer)
res, err := c.Hello(ctx, msg) res, err := c.Hello(ctx, msg)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -193,7 +236,7 @@ func (s *Server) helloPeer(server *FederatedServer) (*pb.HelloMessage, error) {
// writePeers writes our current known peers to disk. // writePeers writes our current known peers to disk.
func (s *Server) writePeers() { func (s *Server) writePeers() {
if !s.Args.WritePeers { if s.Args.DisableWritePeers {
return return
} }
f, err := os.Create(s.Args.PeerFile) f, err := os.Create(s.Args.PeerFile)
@ -222,8 +265,11 @@ func (s *Server) writePeers() {
} }
// notifyPeer takes a peer to notify and a new peer we just learned about // notifyPeer takes a peer to notify and a new peer we just learned about
// and calls AddPeer on the first. // and informs the already known peer about the new peer.
func notifyPeer(peerToNotify *FederatedServer, newPeer *FederatedServer) error { func (s *Server) notifyPeer(peerToNotify *Peer, newPeer *Peer) error {
if s.Args.DisableFederation {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
@ -254,12 +300,12 @@ func notifyPeer(peerToNotify *FederatedServer, newPeer *FederatedServer) error {
// notifyPeerSubs takes a new peer server we just learned about and notifies // notifyPeerSubs takes a new peer server we just learned about and notifies
// all the peers that have subscribed to us about it. // all the peers that have subscribed to us about it.
func (s *Server) notifyPeerSubs(newServer *FederatedServer) { func (s *Server) notifyPeerSubs(newPeer *Peer) {
var unsubscribe []string var unsubscribe []string
s.PeerSubsMut.RLock() s.PeerSubsMut.RLock()
for key, peer := range s.PeerSubs { for key, peer := range s.PeerSubs {
log.Printf("Notifying peer %s of new node %+v\n", key, newServer) log.Printf("Notifying peer %s of new node %+v\n", key, newPeer)
err := notifyPeer(peer, newServer) err := s.notifyPeer(peer, newPeer)
if err != nil { if err != nil {
log.Println("Failed to send data to ", key) log.Println("Failed to send data to ", key)
log.Println(err) log.Println(err)
@ -279,25 +325,36 @@ func (s *Server) notifyPeerSubs(newServer *FederatedServer) {
s.PeerSubsMut.Unlock() s.PeerSubsMut.Unlock()
} }
// addPeer takes a new peer as a pb.ServerMessage, optionally checks to see // addPeer takes a new peer, optionally checks to see if they're online, and
// if they're online, and adds them to our list of peer. If we're not currently // adds them to our list of peers. It will also optionally subscribe to it.
// subscribed to a peer, it will also subscribe to it. func (s *Server) addPeer(newPeer *Peer, ping bool, subscribe bool) error {
func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) error { if s.Args.DisableFederation {
if s.Args.Port == msg.Port &&
(localHosts[msg.Address] || msg.Address == s.Args.Host) {
log.Printf("%s:%s addPeer: Self peer, skipping...\n", s.Args.Host, s.Args.Port)
return nil return nil
} }
k := peerKey(msg) // First thing we get our external ip if we don't have it, otherwise we
newServer := &FederatedServer{ // could end up subscribed to our self, which is silly.
Address: msg.Address, nilIP := net.IP{}
Port: msg.Port, localIP1 := net.IPv4(127, 0, 0, 1)
Ts: time.Now(), if s.ExternalIP.Equal(nilIP) || s.ExternalIP.Equal(localIP1) {
err := s.getAndSetExternalIp(newPeer.Address, newPeer.Port)
if err != nil {
log.Println(err)
log.Println("WARNING: can't determine external IP, continuing with ", s.Args.Host)
}
} }
log.Printf("%s:%s adding peer %+v\n", s.Args.Host, s.Args.Port, msg)
if oldServer, loaded := s.PeerServersLoadOrStore(newServer); !loaded { if s.Args.Port == newPeer.Port &&
(localHosts[newPeer.Address] || newPeer.Address == s.ExternalIP.String()) {
log.Printf("%s:%s addPeer: Self peer, skipping...\n", s.ExternalIP, s.Args.Port)
return nil
}
k := peerKey(newPeer)
log.Printf("%s:%s adding peer %+v\n", s.ExternalIP, s.Args.Port, newPeer)
if oldServer, loaded := s.PeerServersLoadOrStore(newPeer); !loaded {
if ping { if ping {
_, err := s.helloPeer(newServer) _, err := s.helloPeer(newPeer)
if err != nil { if err != nil {
s.PeerServersMut.Lock() s.PeerServersMut.Lock()
delete(s.PeerServers, k) delete(s.PeerServers, k)
@ -309,26 +366,31 @@ func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) error {
s.incNumPeers() s.incNumPeers()
metrics.PeersKnown.Inc() metrics.PeersKnown.Inc()
s.writePeers() s.writePeers()
s.notifyPeerSubs(newServer) s.notifyPeerSubs(newPeer)
// Subscribe to all our peers for now // Subscribe to all our peers for now
err := s.subscribeToPeer(newServer) if subscribe {
if err != nil { err := s.subscribeToPeer(newPeer)
return err if err != nil {
} else { return err
s.Subscribed = true }
} }
} else { } else {
oldServer.Ts = time.Now() oldServer.LastSeen = time.Now()
} }
return nil return nil
} }
// mergeFederatedServers is an internal convenience function to add a list of // mergePeers is an internal convenience function to add a list of
// peers. // peers.
func (s *Server) mergeFederatedServers(servers []*pb.ServerMessage) { func (s *Server) mergePeers(servers []*pb.ServerMessage) {
for _, srvMsg := range servers { for _, srvMsg := range servers {
err := s.addPeer(srvMsg, false) newPeer := &Peer{
Address: srvMsg.Address,
Port: srvMsg.Port,
LastSeen: time.Now(),
}
err := s.addPeer(newPeer, false, true)
// This shouldn't happen because we're not pinging them. // This shouldn't happen because we're not pinging them.
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -345,14 +407,14 @@ func (s *Server) makeHelloMessage() *pb.HelloMessage {
for _, peer := range s.PeerServers { for _, peer := range s.PeerServers {
servers = append(servers, &pb.ServerMessage{ servers = append(servers, &pb.ServerMessage{
Address: peer.Address, Address: peer.Address,
Port: peer.Port, Port: peer.Port,
}) })
} }
s.PeerServersMut.RUnlock() s.PeerServersMut.RUnlock()
return &pb.HelloMessage{ return &pb.HelloMessage{
Port: s.Args.Port, Port: s.Args.Port,
Host: s.Args.Host, Host: s.ExternalIP.String(),
Servers: servers, Servers: servers,
} }
} }

View file

@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"net"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -44,24 +45,23 @@ func removeFile(fileName string) {
func makeDefaultArgs() *Args { func makeDefaultArgs() *Args {
args := &Args{ args := &Args{
CmdType: ServeCmd, CmdType: ServeCmd,
Host: DefaultHost, Host: DefaultHost,
Port: DefaultPort, Port: DefaultPort,
EsHost: DefaultEsHost, EsHost: DefaultEsHost,
EsPort: DefaultEsPort, EsPort: DefaultEsPort,
UDPPort: DefaultUdpPort, PrometheusPort: DefaultPrometheusPort,
PrometheusPort: DefaultPrometheusPort, EsIndex: DefaultEsIndex,
EsIndex: DefaultEsIndex, RefreshDelta: DefaultRefreshDelta,
RefreshDelta: DefaultRefreshDelta, CacheTTL: DefaultCacheTTL,
CacheTTL: DefaultCacheTTL, PeerFile: DefaultPeerFile,
PeerFile: DefaultPeerFile, Country: DefaultCountry,
Country: DefaultCountry, DisableEs: true,
DisableEs: true, Debug: true,
Debug: true, DisableLoadPeers: true,
LoadPeers: false, DisableStartPrometheus: true,
StartPrometheus: false, DisableStartUDP: true,
StartUDP: false, DisableWritePeers: true,
WritePeers: false,
} }
return args return args
@ -75,7 +75,7 @@ func TestAddPeer(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
want int want int
} { }{
{ {
name: "Add 10 peers", name: "Add 10 peers",
want: 10, want: 10,
@ -87,27 +87,27 @@ func TestAddPeer(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){ t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args) server := MakeHubServer(ctx, args)
server.Subscribed = true server.ExternalIP = net.IPv4(0, 0, 0, 0)
metrics.PeersKnown.Set(0) metrics.PeersKnown.Set(0)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
var msg *pb.ServerMessage var peer *Peer
if strings.Contains(tt.name, "1 unique") { if strings.Contains(tt.name, "1 unique") {
msg = &pb.ServerMessage{ peer = &Peer{
Address: "1.1.1.1", Address: "1.1.1.1",
Port: "50051", Port: "50051",
} }
} else { } else {
x := i + 1 x := i + 1
msg = &pb.ServerMessage{ peer = &Peer{
Address: fmt.Sprintf("%d.%d.%d.%d", x, x, x, x), Address: fmt.Sprintf("%d.%d.%d.%d", x, x, x, x),
Port: "50051", Port: "50051",
} }
} }
//log.Printf("Adding peer %+v\n", msg) //log.Printf("Adding peer %+v\n", msg)
err := server.addPeer(msg, false) err := server.addPeer(peer, false, false)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -129,12 +129,12 @@ func TestAddPeer(t *testing.T) {
func TestPeerWriter(t *testing.T) { func TestPeerWriter(t *testing.T) {
ctx := context.Background() ctx := context.Background()
args := makeDefaultArgs() args := makeDefaultArgs()
args.WritePeers = true args.DisableWritePeers = false
tests := []struct { tests := []struct {
name string name string
want int want int
} { }{
{ {
name: "Add 10 peers", name: "Add 10 peers",
want: 10, want: 10,
@ -146,26 +146,26 @@ func TestPeerWriter(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){ t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args) server := MakeHubServer(ctx, args)
server.Subscribed = true server.ExternalIP = net.IPv4(0, 0, 0, 0)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
var msg *pb.ServerMessage var peer *Peer
if strings.Contains(tt.name, "1 unique") { if strings.Contains(tt.name, "1 unique") {
msg = &pb.ServerMessage{ peer = &Peer{
Address: "1.1.1.1", Address: "1.1.1.1",
Port: "50051", Port: "50051",
} }
} else { } else {
x := i + 1 x := i + 1
msg = &pb.ServerMessage{ peer = &Peer{
Address: fmt.Sprintf("%d.%d.%d.%d", x, x, x, x), Address: fmt.Sprintf("%d.%d.%d.%d", x, x, x, x),
Port: "50051", Port: "50051",
} }
} }
//log.Printf("Adding peer %+v\n", msg) //log.Printf("Adding peer %+v\n", peer)
err := server.addPeer(msg, false) err := server.addPeer(peer, false, false)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -188,12 +188,11 @@ func TestAddPeerEndpoint(t *testing.T) {
args2 := makeDefaultArgs() args2 := makeDefaultArgs()
args2.Port = "50052" args2.Port = "50052"
tests := []struct { tests := []struct {
name string name string
wantServerOne int64 wantServerOne int64
wantServerTwo int64 wantServerTwo int64
} { }{
{ {
// outside -> server1.AddPeer(server2, ping=true) : server1 = 1, server2 = 0 // outside -> server1.AddPeer(server2, ping=true) : server1 = 1, server2 = 0
// server1 -> server2.Hello(server1) : server1 = 1, server2 = 0 // server1 -> server2.Hello(server1) : server1 = 1, server2 = 0
@ -204,14 +203,14 @@ func TestAddPeerEndpoint(t *testing.T) {
// server1 -> server2.AddPeer(server2) : server1 = 1, server2 = 1 // server1 -> server2.AddPeer(server2) : server1 = 1, server2 = 1
// server2 self peer, skipping : server1 = 1, server2 = 1 // server2 self peer, skipping : server1 = 1, server2 = 1
// server1 -> server2.PeerSubscribe(server1) : server1 = 1, server2 = 1 // server1 -> server2.PeerSubscribe(server1) : server1 = 1, server2 = 1
name: "Add 1 peer", name: "Add 1 peer",
wantServerOne: 1, wantServerOne: 1,
wantServerTwo: 1, wantServerTwo: 1,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){ t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args) server := MakeHubServer(ctx, args)
server2 := MakeHubServer(ctx, args2) server2 := MakeHubServer(ctx, args2)
metrics.PeersKnown.Set(0) metrics.PeersKnown.Set(0)
@ -262,15 +261,14 @@ func TestAddPeerEndpoint2(t *testing.T) {
args2.Port = "50052" args2.Port = "50052"
args3.Port = "50053" args3.Port = "50053"
tests := []struct { tests := []struct {
name string name string
wantServerOne int64 wantServerOne int64
wantServerTwo int64 wantServerTwo int64
wantServerThree int64 wantServerThree int64
} { }{
{ {
name: "Add 2 peers", name: "Add 2 peers",
wantServerOne: 2, wantServerOne: 2,
wantServerTwo: 2, wantServerTwo: 2,
wantServerThree: 2, wantServerThree: 2,
@ -278,7 +276,7 @@ func TestAddPeerEndpoint2(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){ t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args) server := MakeHubServer(ctx, args)
server2 := MakeHubServer(ctx, args2) server2 := MakeHubServer(ctx, args2)
server3 := MakeHubServer(ctx, args3) server3 := MakeHubServer(ctx, args3)
@ -335,7 +333,6 @@ func TestAddPeerEndpoint2(t *testing.T) {
} }
// TestAddPeerEndpoint3 tests the ability to add peers // TestAddPeerEndpoint3 tests the ability to add peers
func TestAddPeerEndpoint3(t *testing.T) { func TestAddPeerEndpoint3(t *testing.T) {
ctx := context.Background() ctx := context.Background()
@ -345,15 +342,14 @@ func TestAddPeerEndpoint3(t *testing.T) {
args2.Port = "50052" args2.Port = "50052"
args3.Port = "50053" args3.Port = "50053"
tests := []struct { tests := []struct {
name string name string
wantServerOne int64 wantServerOne int64
wantServerTwo int64 wantServerTwo int64
wantServerThree int64 wantServerThree int64
} { }{
{ {
name: "Add 1 peer to each", name: "Add 1 peer to each",
wantServerOne: 2, wantServerOne: 2,
wantServerTwo: 2, wantServerTwo: 2,
wantServerThree: 2, wantServerThree: 2,
@ -361,7 +357,7 @@ func TestAddPeerEndpoint3(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T){ t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args) server := MakeHubServer(ctx, args)
server2 := MakeHubServer(ctx, args2) server2 := MakeHubServer(ctx, args2)
server3 := MakeHubServer(ctx, args3) server3 := MakeHubServer(ctx, args3)
@ -425,3 +421,58 @@ func TestAddPeerEndpoint3(t *testing.T) {
} }
} }
// TestAddPeer tests the ability to add peers
func TestUDPServer(t *testing.T) {
ctx := context.Background()
args := makeDefaultArgs()
args.DisableStartUDP = false
args2 := makeDefaultArgs()
args2.Port = "50052"
args2.DisableStartUDP = false
tests := []struct {
name string
want string
}{
{
name: "hubs server external ip",
want: "127.0.0.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := MakeHubServer(ctx, args)
server2 := MakeHubServer(ctx, args2)
go server.Run()
go server2.Run()
metrics.PeersKnown.Set(0)
peer := &Peer{
Address: "0.0.0.0",
Port: "50052",
}
err := server.addPeer(peer, true, true)
if err != nil {
log.Println(err)
}
server.GrpcServer.GracefulStop()
server2.GrpcServer.GracefulStop()
got1 := server.ExternalIP.String()
if got1 != tt.want {
t.Errorf("server.ExternalIP = %s, want %s\n", got1, tt.want)
t.Errorf("server.Args.Port = %s\n", server.Args.Port)
}
got2 := server2.ExternalIP.String()
if got2 != tt.want {
t.Errorf("server2.ExternalIP = %s, want %s\n", got2, tt.want)
t.Errorf("server2.Args.Port = %s\n", server2.Args.Port)
}
})
}
}

View file

@ -275,16 +275,16 @@ func (s *Server) Search(ctx context.Context, in *pb.SearchRequest) (*pb.Outputs,
setPageVars(in, &pageSize, &from) setPageVars(in, &pageSize, &from)
/* /*
cache based on search request params cache based on search request params
include from value and number of results. include from value and number of results.
When another search request comes in with same search params When another search request comes in with same search params
and same or increased offset (which we currently don't even use?) and same or increased offset (which we currently don't even use?)
that will be a cache hit. that will be a cache hit.
FIXME: For now the cache is turned off when in debugging mode FIXME: For now the cache is turned off when in debugging mode
(for unit tests) because it breaks on some of them. (for unit tests) because it breaks on some of them.
FIXME: Currently the cache just skips the initial search, FIXME: Currently the cache just skips the initial search,
the mgets and post processing are still done. There's probably the mgets and post processing are still done. There's probably
a more efficient way to store the final result. a more efficient way to store the final result.
*/ */
if val, err := s.QueryCache.Get(cacheKey); err != nil { if val, err := s.QueryCache.Get(cacheKey); err != nil {
@ -518,15 +518,15 @@ func (s *Server) setupEsQuery(
} }
replacements := map[string]string{ replacements := map[string]string{
"name": "normalized_name", "name": "normalized_name",
"normalized": "normalized_name", "normalized": "normalized_name",
"claim_name": "normalized_name", "claim_name": "normalized_name",
"txid": "tx_id", "txid": "tx_id",
"nout": "tx_nout", "nout": "tx_nout",
"reposted": "repost_count", "reposted": "repost_count",
"valid_channel_signature": "is_signature_valid", "valid_channel_signature": "is_signature_valid",
"claim_id": "_id", "claim_id": "_id",
"signature_digest": "signature", "signature_digest": "signature",
} }
textFields := map[string]bool{ textFields := map[string]bool{
@ -967,4 +967,3 @@ func removeBlocked(searchHits []*record) ([]*record, []*record, map[string]*pb.B
return newHits, blockedHits, blockedChannels return newHits, blockedHits, blockedChannels
} }

View file

@ -35,17 +35,16 @@ type Server struct {
LastRefreshCheck time.Time LastRefreshCheck time.Time
RefreshDelta time.Duration RefreshDelta time.Duration
NumESRefreshes int64 NumESRefreshes int64
PeerServers map[string]*FederatedServer PeerServers map[string]*Peer
PeerServersMut sync.RWMutex PeerServersMut sync.RWMutex
NumPeerServers *int64 NumPeerServers *int64
PeerSubs map[string]*FederatedServer PeerSubs map[string]*Peer
PeerSubsMut sync.RWMutex PeerSubsMut sync.RWMutex
NumPeerSubs *int64 NumPeerSubs *int64
Subscribed bool ExternalIP net.IP
pb.UnimplementedHubServer pb.UnimplementedHubServer
} }
func getVersion() string { func getVersion() string {
return meta.Version return meta.Version
} }
@ -89,7 +88,7 @@ func getVersion() string {
'blockchain.address.unsubscribe' 'blockchain.address.unsubscribe'
*/ */
func (s *Server) PeerSubsLoadOrStore(peer *FederatedServer) (actual *FederatedServer, loaded bool) { func (s *Server) PeerSubsLoadOrStore(peer *Peer) (actual *Peer, loaded bool) {
key := peer.peerKey() key := peer.peerKey()
s.PeerSubsMut.RLock() s.PeerSubsMut.RLock()
if actual, ok := s.PeerSubs[key]; ok { if actual, ok := s.PeerSubs[key]; ok {
@ -104,7 +103,7 @@ func (s *Server) PeerSubsLoadOrStore(peer *FederatedServer) (actual *FederatedSe
} }
} }
func (s *Server) PeerServersLoadOrStore(peer *FederatedServer) (actual *FederatedServer, loaded bool) { func (s *Server) PeerServersLoadOrStore(peer *Peer) (actual *Peer, loaded bool) {
key := peer.peerKey() key := peer.peerKey()
s.PeerServersMut.RLock() s.PeerServersMut.RLock()
if actual, ok := s.PeerServers[key]; ok { if actual, ok := s.PeerServers[key]; ok {
@ -196,20 +195,20 @@ func MakeHubServer(ctx context.Context, args *Args) *Server {
LastRefreshCheck: time.Now(), LastRefreshCheck: time.Now(),
RefreshDelta: refreshDelta, RefreshDelta: refreshDelta,
NumESRefreshes: 0, NumESRefreshes: 0,
PeerServers: make(map[string]*FederatedServer), PeerServers: make(map[string]*Peer),
PeerServersMut: sync.RWMutex{}, PeerServersMut: sync.RWMutex{},
NumPeerServers: numPeers, NumPeerServers: numPeers,
PeerSubs: make(map[string]*FederatedServer), PeerSubs: make(map[string]*Peer),
PeerSubsMut: sync.RWMutex{}, PeerSubsMut: sync.RWMutex{},
NumPeerSubs: numSubs, NumPeerSubs: numSubs,
Subscribed: false, ExternalIP: net.IPv4(127, 0, 0, 1),
} }
// Start up our background services // Start up our background services
if args.StartPrometheus { if !args.DisableStartPrometheus {
go s.prometheusEndpoint(s.Args.PrometheusPort, "metrics") go s.prometheusEndpoint(s.Args.PrometheusPort, "metrics")
} }
if args.StartUDP { if !args.DisableStartUDP {
go func() { go func() {
err := UDPServer(args) err := UDPServer(args)
if err != nil { if err != nil {
@ -218,11 +217,13 @@ func MakeHubServer(ctx context.Context, args *Args) *Server {
}() }()
} }
// Load peers from disk and subscribe to one if there are any // Load peers from disk and subscribe to one if there are any
if args.LoadPeers { if !args.DisableLoadPeers {
err = s.loadPeers() go func() {
if err != nil { err := s.loadPeers()
log.Println(err) if err != nil {
} log.Println(err)
}
}()
} }
return s return s
@ -244,21 +245,21 @@ func (s *Server) Hello(ctx context.Context, args *pb.HelloMessage) (*pb.HelloMes
metrics.RequestsCount.With(prometheus.Labels{"method": "hello"}).Inc() metrics.RequestsCount.With(prometheus.Labels{"method": "hello"}).Inc()
port := args.Port port := args.Port
host := args.Host host := args.Host
server := &FederatedServer{ newPeer := &Peer{
Address: host, Address: host,
Port: port, Port: port,
Ts: time.Now(), LastSeen: time.Now(),
} }
log.Println(server) log.Println(newPeer)
err := s.addPeer(&pb.ServerMessage{Address: host, Port: port}, false) err := s.addPeer(newPeer, false, true)
// They just contacted us, so this shouldn't happen // They just contacted us, so this shouldn't happen
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
s.mergeFederatedServers(args.Servers) s.mergePeers(args.Servers)
s.writePeers() s.writePeers()
s.notifyPeerSubs(server) s.notifyPeerSubs(newPeer)
return s.makeHelloMessage(), nil return s.makeHelloMessage(), nil
} }
@ -268,10 +269,10 @@ func (s *Server) Hello(ctx context.Context, args *pb.HelloMessage) (*pb.HelloMes
func (s *Server) PeerSubscribe(ctx context.Context, in *pb.ServerMessage) (*pb.StringValue, error) { func (s *Server) PeerSubscribe(ctx context.Context, in *pb.ServerMessage) (*pb.StringValue, error) {
metrics.RequestsCount.With(prometheus.Labels{"method": "peer_subscribe"}).Inc() metrics.RequestsCount.With(prometheus.Labels{"method": "peer_subscribe"}).Inc()
var msg = "Success" var msg = "Success"
peer := &FederatedServer{ peer := &Peer{
Address: in.Address, Address: in.Address,
Port: in.Port, Port: in.Port,
Ts: time.Now(), LastSeen: time.Now(),
} }
if _, loaded := s.PeerSubsLoadOrStore(peer); !loaded { if _, loaded := s.PeerSubsLoadOrStore(peer); !loaded {
@ -288,7 +289,12 @@ func (s *Server) PeerSubscribe(ctx context.Context, in *pb.ServerMessage) (*pb.S
func (s *Server) AddPeer(ctx context.Context, args *pb.ServerMessage) (*pb.StringValue, error) { func (s *Server) AddPeer(ctx context.Context, args *pb.ServerMessage) (*pb.StringValue, error) {
metrics.RequestsCount.With(prometheus.Labels{"method": "add_peer"}).Inc() metrics.RequestsCount.With(prometheus.Labels{"method": "add_peer"}).Inc()
var msg = "Success" var msg = "Success"
err := s.addPeer(args, true) newPeer := &Peer{
Address: args.Address,
Port: args.Port,
LastSeen: time.Now(),
}
err := s.addPeer(newPeer, true, true)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
msg = "Failed" msg = "Failed"

View file

@ -2,19 +2,25 @@ package server
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"time" "time"
pb "github.com/lbryio/hub/protobuf/go"
"github.com/lbryio/lbry.go/v2/extras/errors" "github.com/lbryio/lbry.go/v2/extras/errors"
) )
const maxBufferSize = 1024 const maxBufferSize = 1024
// genesis blocktime (which is actually wrong) // genesis blocktime (which is actually wrong)
const magic = 1446058291 // 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 protocolVersion = 1
const defaultFlags = 0b00000000
const availableFlag = 0b00000001
// SPVPing is a struct for the format of how to ping another hub over udp. // SPVPing is a struct for the format of how to ping another hub over udp.
// format b'!lB64s' // format b'!lB64s'
@ -31,7 +37,7 @@ type SPVPong struct {
flags byte flags byte
height uint32 height uint32
tip []byte // 32 tip []byte // 32
srcAddrRaw []byte // 4 srcAddrRaw []byte // 4
country uint16 country uint16
} }
@ -55,7 +61,7 @@ func decodeSPVPing(data []byte) *SPVPing {
parsedMagic := binary.BigEndian.Uint32(data) parsedMagic := binary.BigEndian.Uint32(data)
parsedProtocalVersion := data[4] parsedProtocalVersion := data[4]
return &SPVPing{ return &SPVPing{
magic: parsedMagic, magic: parsedMagic,
version: parsedProtocalVersion, version: parsedProtocalVersion,
} }
} }
@ -65,7 +71,7 @@ func decodeSPVPing(data []byte) *SPVPing {
func (pong *SPVPong) Encode() []byte { func (pong *SPVPong) Encode() []byte {
data := make([]byte, 44) data := make([]byte, 44)
data[0] = pong.protocolVersion data[0] = pong.protocolVersion
data[1] = pong.flags data[1] = pong.flags
binary.BigEndian.PutUint32(data[2:], pong.height) binary.BigEndian.PutUint32(data[2:], pong.height)
copy(data[6:], pong.tip) copy(data[6:], pong.tip)
@ -76,10 +82,13 @@ func (pong *SPVPong) Encode() []byte {
} }
// makeSPVPong creates an SPVPong struct according to given parameters. // 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 { func makeSPVPong(flags int, height int, tip []byte, sourceAddr string, country string) *SPVPong {
byteAddr := EncodeAddress(sourceAddr) byteAddr := EncodeAddress(sourceAddr)
countryInt := 1 var countryInt int32
var ok bool
if countryInt, ok = pb.Location_Country_value[country]; !ok {
countryInt = int32(pb.Location_UNKNOWN_COUNTRY)
}
return &SPVPong{ return &SPVPong{
protocolVersion: protocolVersion, protocolVersion: protocolVersion,
flags: byte(flags), flags: byte(flags),
@ -99,19 +108,19 @@ func decodeSPVPong(data []byte) *SPVPong {
parsedProtocalVersion := data[0] parsedProtocalVersion := data[0]
flags := data[1] flags := data[1]
height := binary.BigEndian.Uint32(data[:2]) height := binary.BigEndian.Uint32(data[2:])
tip := make([]byte, 32) tip := make([]byte, 32)
copy(tip, data[6:38]) copy(tip, data[6:38])
srcRawAddr := make([]byte, 4) srcRawAddr := make([]byte, 4)
copy(srcRawAddr, data[38:42]) copy(srcRawAddr, data[38:42])
country := binary.BigEndian.Uint16(data[:42]) country := binary.BigEndian.Uint16(data[42:])
return &SPVPong{ return &SPVPong{
protocolVersion: parsedProtocalVersion, protocolVersion: parsedProtocalVersion,
flags: flags, flags: flags,
height: height, height: height,
tip: tip, tip: tip,
srcAddrRaw: srcRawAddr, srcAddrRaw: srcRawAddr,
country: country, country: country,
} }
} }
@ -137,8 +146,8 @@ func EncodeAddress(addr string) []byte {
} }
// DecodeAddress gets the string ipv4 address from an SPVPong struct. // DecodeAddress gets the string ipv4 address from an SPVPong struct.
func (pong *SPVPong) DecodeAddress() string { func (pong *SPVPong) DecodeAddress() net.IP {
return fmt.Sprintf("%d.%d.%d.%d", return net.IPv4(
pong.srcAddrRaw[0], pong.srcAddrRaw[0],
pong.srcAddrRaw[1], pong.srcAddrRaw[1],
pong.srcAddrRaw[2], pong.srcAddrRaw[2],
@ -146,53 +155,72 @@ func (pong *SPVPong) DecodeAddress() string {
) )
} }
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 // UDPPing sends a ping over udp to another hub and returns the ip address of
// this hub. // this hub.
func UDPPing(address string) (string, error) { func UDPPing(ip, port string) (*SPVPong, error) {
address := ip + ":" + port
addr, err := net.ResolveUDPAddr("udp", address) addr, err := net.ResolveUDPAddr("udp", address)
if err != nil { if err != nil {
return "", err return nil, err
} }
conn, err := net.DialUDP("udp", nil, addr) conn, err := net.DialUDP("udp", nil, addr)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer conn.Close() defer conn.Close()
_, err = conn.Write(encodeSPVPing()) _, err = conn.Write(encodeSPVPing())
if err != nil { if err != nil {
return "", err return nil, err
} }
buffer := make([]byte, maxBufferSize) buffer := make([]byte, maxBufferSize)
deadline := time.Now().Add(time.Second) deadline := time.Now().Add(time.Second)
err = conn.SetReadDeadline(deadline) err = conn.SetReadDeadline(deadline)
if err != nil { if err != nil {
return "", err return nil, err
} }
n, _, err := conn.ReadFromUDP(buffer) n, _, err := conn.ReadFromUDP(buffer)
if err != nil { if err != nil {
return "", err return nil, err
} }
pong := decodeSPVPong(buffer[:n]) pong := decodeSPVPong(buffer[:n])
if pong == nil { if pong == nil {
return "", errors.Base("Pong decoding failed") return nil, errors.Base("Pong decoding failed")
} }
myAddr := pong.DecodeAddress() return pong, nil
return myAddr, nil
} }
// UDPServer is a goroutine that starts an udp server that implements the hubs // 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 // Ping/Pong protocol to find out about each other without making full TCP
// connections. // connections.
func UDPServer(args *Args) error { func UDPServer(args *Args) error {
address := ":" + args.UDPPort address := ":" + args.Port
tip := make([]byte, 32) tip := make([]byte, 32)
addr, err := net.ResolveUDPAddr("udp", address) addr, err := net.ResolveUDPAddr("udp", address)
if err != nil { if err != nil {
@ -215,7 +243,7 @@ func UDPServer(args *Args) error {
} }
sAddr := addr.IP.String() sAddr := addr.IP.String()
pong := makeSPVPong(0,0, tip, sAddr, args.Country) pong := makeSPVPong(defaultFlags|availableFlag, 0, tip, sAddr, args.Country)
data := pong.Encode() data := pong.Encode()
_, err = conn.WriteToUDP(data, addr) _, err = conn.WriteToUDP(data, addr)

83
server/udp_test.go Normal file
View file

@ -0,0 +1,83 @@
package server
import (
"log"
"os/exec"
"strings"
"testing"
)
// TestUDPPing tests UDPPing correctness against prod server.
func TestUDPPing(t *testing.T) {
args := makeDefaultArgs()
args.DisableStartUDP = true
tests := []struct {
name string
wantIP string
wantCountry string
wantProtocolVersion int
wantHeightMin int
wantFlags byte
}{
{
name: "Correctly parse information from production server.",
wantIP: "SETME",
wantCountry: "US",
wantProtocolVersion: 1,
wantHeightMin: 1060000,
wantFlags: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
toAddr := "spv16.lbry.com"
toPort := "50001"
pong, err := UDPPing(toAddr, toPort)
gotCountry := pong.DecodeCountry()
if err != nil {
log.Println(err)
}
res, err := exec.Command("dig", "@resolver4.opendns.com", "myip.opendns.com", "+short").Output()
if err != nil {
log.Println(err)
}
digIP := strings.TrimSpace(string(res))
udpIP := pong.DecodeAddress().String()
tt.wantIP = digIP
log.Println("Height:", pong.DecodeHeight())
log.Printf("Flags: %x\n", pong.DecodeFlags())
log.Println("ProtocolVersion:", pong.DecodeProtocolVersion())
log.Printf("Tip: %x\n", pong.DecodeTip())
gotHeight := pong.DecodeHeight()
gotProtocolVersion := pong.DecodeProtocolVersion()
gotFlags := pong.DecodeFlags()
gotIP := udpIP
if gotIP != tt.wantIP {
t.Errorf("ip: got: '%s', want: '%s'\n", gotIP, tt.wantIP)
}
if gotCountry != tt.wantCountry {
t.Errorf("country: got: '%s', want: '%s'\n", gotCountry, tt.wantCountry)
}
if gotHeight < tt.wantHeightMin {
t.Errorf("height: got: %d, want >=: %d\n", gotHeight, tt.wantHeightMin)
}
if gotProtocolVersion != tt.wantProtocolVersion {
t.Errorf("protocolVersion: got: %d, want: %d\n", gotProtocolVersion, tt.wantProtocolVersion)
}
if gotFlags != tt.wantFlags {
t.Errorf("flags: got: %d, want: %d\n", gotFlags, tt.wantFlags)
}
})
}
}