UDPServer / ip address resolution

Got the UDPServer ping/pong protocol working internally, only tested
against other running go hub servers. Should in theory work with
python server and clients, but still need to test that.

Also switched to serving udp on the same port as grpc, and taking that
into account when pinging other hubs with udp.

Unit test for udp ip address lookup.
This commit is contained in:
Jeffrey Picard 2021-11-09 19:39:13 -05:00
parent 602292281c
commit 395e1db489
5 changed files with 148 additions and 40 deletions

View file

@ -19,7 +19,6 @@ 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
@ -39,7 +38,6 @@ type Args struct {
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"
@ -88,7 +86,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})
@ -125,7 +122,6 @@ func ParseArgs(searchRequest *pb.SearchRequest) *Args {
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,

View file

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"log" "log"
"math"
"os" "os"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -66,6 +67,19 @@ func (s *Server) getNumSubs() int64 {
return *s.NumPeerSubs return *s.NumPeerSubs
} }
// getAndSetExternalIp takes the address of a peer running a UDP server and
// pings it, so we can determine our own external IP address.
func (s *Server) getAndSetExternalIp(msg *pb.ServerMessage) error {
myIp, err := UDPPing(msg.Address, msg.Port)
if err != nil {
return err
}
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 +87,33 @@ 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)
@ -97,19 +138,22 @@ func (s *Server) loadPeers() error {
} }
// 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) {
log.Println("Self peer, skipping ...") log.Println("Self peer, skipping ...")
continue continue
} }
srvMsg := &pb.ServerMessage{ srvMsg := &pb.ServerMessage{
Address: ipPort[0], Address: ipPort[0],
Port: ipPort[1], Port: ipPort[1],
} }
log.Printf("pinging peer %+v\n", srvMsg) log.Printf("pinging peer %+v\n", srvMsg)
err := s.addPeer(srvMsg, true) err = s.addPeer(srvMsg, true, true)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} }
log.Println("Returning from loadPeers") log.Println("Returning from loadPeers")
@ -133,20 +177,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,
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
} }
@ -175,11 +217,11 @@ func (s *Server) helloPeer(server *FederatedServer) (*pb.HelloMessage, error) {
msg := &pb.HelloMessage{ msg := &pb.HelloMessage{
Port: s.Args.Port, Port: s.Args.Port,
Host: s.Args.Host, Host: s.ExternalIP,
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, server)
res, err := c.Hello(ctx, msg) res, err := c.Hello(ctx, msg)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -282,19 +324,31 @@ func (s *Server) notifyPeerSubs(newServer *FederatedServer) {
// addPeer takes a new peer as a pb.ServerMessage, optionally checks to see // addPeer takes a new peer as a pb.ServerMessage, optionally checks to see
// if they're online, and adds them to our list of peer. If we're not currently // if they're online, and adds them to our list of peer. If we're not currently
// subscribed to a peer, it will also subscribe to it. // subscribed to a peer, it will also subscribe to it.
func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) error { func (s *Server) addPeer(msg *pb.ServerMessage, ping bool, subscribe bool) error {
// First thing we get our external ip if we don't have it, otherwise we
// could end up subscribed to our self, which is silly.
if s.ExternalIP == "" {
err := s.getAndSetExternalIp(msg)
if err != nil {
log.Println(err)
log.Println("WARNING: can't determine external IP, continuing with ", s.Args.Host)
}
}
if s.Args.Port == msg.Port && if s.Args.Port == msg.Port &&
(localHosts[msg.Address] || msg.Address == s.Args.Host) { (localHosts[msg.Address] || msg.Address == s.ExternalIP) {
log.Printf("%s:%s addPeer: Self peer, skipping...\n", s.Args.Host, s.Args.Port) log.Printf("%s:%s addPeer: Self peer, skipping...\n", s.ExternalIP, s.Args.Port)
return nil return nil
} }
k := peerKey(msg) k := peerKey(msg)
newServer := &FederatedServer{ newServer := &FederatedServer{
Address: msg.Address, Address: msg.Address,
Port: msg.Port, Port: msg.Port,
Ts: time.Now(), Ts: time.Now(),
} }
log.Printf("%s:%s adding peer %+v\n", s.Args.Host, s.Args.Port, msg)
log.Printf("%s:%s adding peer %+v\n", s.ExternalIP, s.Args.Port, msg)
if oldServer, loaded := s.PeerServersLoadOrStore(newServer); !loaded { if oldServer, loaded := s.PeerServersLoadOrStore(newServer); !loaded {
if ping { if ping {
_, err := s.helloPeer(newServer) _, err := s.helloPeer(newServer)
@ -312,11 +366,11 @@ func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) error {
s.notifyPeerSubs(newServer) s.notifyPeerSubs(newServer)
// Subscribe to all our peers for now // Subscribe to all our peers for now
if subscribe {
err := s.subscribeToPeer(newServer) err := s.subscribeToPeer(newServer)
if err != nil { if err != nil {
return err return err
} else { }
s.Subscribed = true
} }
} else { } else {
oldServer.Ts = time.Now() oldServer.Ts = time.Now()
@ -328,7 +382,7 @@ func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) error {
// peers. // peers.
func (s *Server) mergeFederatedServers(servers []*pb.ServerMessage) { func (s *Server) mergeFederatedServers(servers []*pb.ServerMessage) {
for _, srvMsg := range servers { for _, srvMsg := range servers {
err := s.addPeer(srvMsg, false) err := s.addPeer(srvMsg, 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)
@ -352,7 +406,7 @@ func (s *Server) makeHelloMessage() *pb.HelloMessage {
return &pb.HelloMessage{ return &pb.HelloMessage{
Port: s.Args.Port, Port: s.Args.Port,
Host: s.Args.Host, Host: s.ExternalIP,
Servers: servers, Servers: servers,
} }
} }

View file

@ -49,7 +49,6 @@ func makeDefaultArgs() *Args {
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,
@ -89,7 +88,7 @@ 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 = "0.0.0.0"
metrics.PeersKnown.Set(0) metrics.PeersKnown.Set(0)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
@ -107,7 +106,7 @@ func TestAddPeer(t *testing.T) {
} }
} }
//log.Printf("Adding peer %+v\n", msg) //log.Printf("Adding peer %+v\n", msg)
err := server.addPeer(msg, false) err := server.addPeer(msg, false, false)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -148,7 +147,7 @@ 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 = "0.0.0.0"
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
var msg *pb.ServerMessage var msg *pb.ServerMessage
@ -165,7 +164,7 @@ func TestPeerWriter(t *testing.T) {
} }
} }
//log.Printf("Adding peer %+v\n", msg) //log.Printf("Adding peer %+v\n", msg)
err := server.addPeer(msg, false) err := server.addPeer(msg, false, false)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -425,3 +424,59 @@ func TestAddPeerEndpoint3(t *testing.T) {
} }
} }
// TestAddPeer tests the ability to add peers
func TestUDPServer(t *testing.T) {
ctx := context.Background()
args := makeDefaultArgs()
args.StartUDP = true
args2 := makeDefaultArgs()
args2.Port = "50052"
args2.StartUDP = true
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)
msg := &pb.ServerMessage{
Address: "0.0.0.0",
Port: "50052",
}
err := server.addPeer(msg, true, true)
if err != nil {
log.Println(err)
}
server.GrpcServer.GracefulStop()
server2.GrpcServer.GracefulStop()
got1 := server.ExternalIP
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
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

@ -41,7 +41,7 @@ type Server struct {
PeerSubs map[string]*FederatedServer PeerSubs map[string]*FederatedServer
PeerSubsMut sync.RWMutex PeerSubsMut sync.RWMutex
NumPeerSubs *int64 NumPeerSubs *int64
Subscribed bool ExternalIP string
pb.UnimplementedHubServer pb.UnimplementedHubServer
} }
@ -202,7 +202,7 @@ func MakeHubServer(ctx context.Context, args *Args) *Server {
PeerSubs: make(map[string]*FederatedServer), PeerSubs: make(map[string]*FederatedServer),
PeerSubsMut: sync.RWMutex{}, PeerSubsMut: sync.RWMutex{},
NumPeerSubs: numSubs, NumPeerSubs: numSubs,
Subscribed: false, ExternalIP: "",
} }
// Start up our background services // Start up our background services
@ -219,10 +219,12 @@ 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.LoadPeers {
err = s.loadPeers() go func() {
err := s.loadPeers()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
}()
} }
return s return s
@ -251,7 +253,7 @@ func (s *Server) Hello(ctx context.Context, args *pb.HelloMessage) (*pb.HelloMes
} }
log.Println(server) log.Println(server)
err := s.addPeer(&pb.ServerMessage{Address: host, Port: port}, false) err := s.addPeer(&pb.ServerMessage{Address: host, Port: port}, 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)
@ -288,7 +290,7 @@ 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) err := s.addPeer(args, true, true)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
msg = "Failed" msg = "Failed"

View file

@ -99,12 +99,12 @@ 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,
@ -148,7 +148,8 @@ func (pong *SPVPong) DecodeAddress() string {
// 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) (string, 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 "", err
@ -192,7 +193,7 @@ func UDPPing(address string) (string, error) {
// 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 {