From 2eac5aeec608825d44a9fbbdbb901c2583217bea Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 2 Nov 2014 21:19:22 +0000 Subject: [PATCH 1/6] begin code for NAT detection --- net/conn/handshake.go | 21 ++++++++++++++++ net/handshake/pb/handshake.pb.go | 21 +++++++++------- net/handshake/pb/handshake.proto | 3 ++- util/util.go | 41 ++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/net/conn/handshake.go b/net/conn/handshake.go index ab8cea8e3..9a155c5a1 100644 --- a/net/conn/handshake.go +++ b/net/conn/handshake.go @@ -6,9 +6,11 @@ import ( handshake "github.com/jbenet/go-ipfs/net/handshake" hspb "github.com/jbenet/go-ipfs/net/handshake/pb" + u "github.com/jbenet/go-ipfs/util" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto" + ma "github.com/jbenet/go-multiaddr" ) // Handshake1 exchanges local and remote versions and compares them @@ -65,6 +67,10 @@ func Handshake3(ctx context.Context, c Conn) error { var remoteH, localH *hspb.Handshake3 localH = handshake.Handshake3Msg(lpeer) + + rma := c.RemoteMultiaddr() + localH.ObservedAddr = proto.String(rma.String()) + localB, err := proto.Marshal(localH) if err != nil { return err @@ -101,3 +107,18 @@ func Handshake3(ctx context.Context, c Conn) error { return nil } + +func CheckNAT(obsaddr string) (bool, error) { + oma, err := ma.NewMultiaddr(obsaddr) + if err != nil { + return false, err + } + addrs, err := u.GetLocalAddresses() + if err != nil { + return false, err + } + _ = oma + _ = addrs + + panic("not yet implemented!") +} diff --git a/net/handshake/pb/handshake.pb.go b/net/handshake/pb/handshake.pb.go index 2308297b9..2e641cdf5 100644 --- a/net/handshake/pb/handshake.pb.go +++ b/net/handshake/pb/handshake.pb.go @@ -14,15 +14,11 @@ It has these top-level messages: */ package handshake_pb -import proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/proto" -import json "encoding/json" +import proto "code.google.com/p/gogoprotobuf/proto" import math "math" -// discarding unused import mux "github.com/jbenet/go-ipfs/net/mux/mux.pb" - -// Reference proto, json, and math imports to suppress error if they are not otherwise used. +// Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal -var _ = &json.SyntaxError{} var _ = math.Inf // Handshake1 is delivered _before_ the secure channel is initialized @@ -56,8 +52,10 @@ func (m *Handshake1) GetAgentVersion() string { // Handshake3 is delivered _after_ the secure channel is initialized type Handshake3 struct { // listenAddrs are the multiaddrs this node listens for open connections on - ListenAddrs [][]byte `protobuf:"bytes,2,rep,name=listenAddrs" json:"listenAddrs,omitempty"` - XXX_unrecognized []byte `json:"-"` + ListenAddrs [][]byte `protobuf:"bytes,2,rep,name=listenAddrs" json:"listenAddrs,omitempty"` + // we'll have more fields here later. + ObservedAddr *string `protobuf:"bytes,4,opt,name=observedAddr" json:"observedAddr,omitempty"` + XXX_unrecognized []byte `json:"-"` } func (m *Handshake3) Reset() { *m = Handshake3{} } @@ -71,5 +69,12 @@ func (m *Handshake3) GetListenAddrs() [][]byte { return nil } +func (m *Handshake3) GetObservedAddr() string { + if m != nil && m.ObservedAddr != nil { + return *m.ObservedAddr + } + return "" +} + func init() { } diff --git a/net/handshake/pb/handshake.proto b/net/handshake/pb/handshake.proto index 61e773b88..1e795d881 100644 --- a/net/handshake/pb/handshake.proto +++ b/net/handshake/pb/handshake.proto @@ -1,6 +1,6 @@ package handshake.pb; -import "github.com/jbenet/go-ipfs/net/mux/mux.proto"; +//import "github.com/jbenet/go-ipfs/net/mux/mux.proto"; // Handshake1 is delivered _before_ the secure channel is initialized message Handshake1 { @@ -30,4 +30,5 @@ message Handshake3 { // repeated mux.ProtocolID services = 3; // we'll have more fields here later. + optional string observedAddr = 4; } diff --git a/util/util.go b/util/util.go index 9ba3d62f7..0076322e2 100644 --- a/util/util.go +++ b/util/util.go @@ -4,12 +4,16 @@ import ( "errors" "io" "math/rand" + "net" "os" "path/filepath" + "reflect" "strings" "time" ds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore" + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net" "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/mitchellh/go-homedir" ) @@ -107,3 +111,40 @@ func GetenvBool(name string) bool { v := strings.ToLower(os.Getenv(name)) return v == "true" || v == "t" || v == "1" } + +func IsLoopbackAddr(addr string) bool { + return addr == "/ip4/127.0.0.1" || addr == "/ip6/::1" +} + +func GetLocalAddresses() ([]ma.Multiaddr, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + var maddrs []ma.Multiaddr + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + log.Warningf("Skipping addr: %s", err) + continue + } + for _, addr := range addrs { + switch v := addr.(type) { + case *net.IPNet: + maddr, err := manet.FromIP(v.IP) + if err != nil { + log.Errorf("maddr parsing error: %s", err) + continue + } + if IsLoopbackAddr(maddr.String()) { + continue + } + maddrs = append(maddrs, maddr) + default: + log.Errorf("Got '%s' type = '%s'", v, reflect.TypeOf(v)) + } + } + } + return maddrs, nil +} From cb2fb9cfe6abd9ab8f0a1d3b52482f7ed2812e94 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 2 Nov 2014 21:59:59 +0000 Subject: [PATCH 2/6] print NAT if detected --- net/conn/handshake.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/net/conn/handshake.go b/net/conn/handshake.go index 9a155c5a1..1d4e63cd3 100644 --- a/net/conn/handshake.go +++ b/net/conn/handshake.go @@ -3,6 +3,7 @@ package conn import ( "errors" "fmt" + "strings" handshake "github.com/jbenet/go-ipfs/net/handshake" hspb "github.com/jbenet/go-ipfs/net/handshake/pb" @@ -105,6 +106,14 @@ func Handshake3(ctx context.Context, c Conn) error { return err } + nat, err := CheckNAT(remoteH.GetObservedAddr()) + if err != nil { + log.Errorf("Error in NAT detection: %s", err) + } + if nat { + log.Warning("We are probably behind a NAT!") + } + return nil } @@ -117,8 +126,13 @@ func CheckNAT(obsaddr string) (bool, error) { if err != nil { return false, err } - _ = oma - _ = addrs - panic("not yet implemented!") + omastr := oma.String() + for _, addr := range addrs { + if strings.HasPrefix(addr.String(), omastr) { + return false, nil + } + } + + return true, nil } From 094baf6fbf64cc7bba6c77a2afd2df781697fb63 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 3 Nov 2014 00:28:07 +0000 Subject: [PATCH 3/6] error out if attempting connection to loopback --- net/conn/dial.go | 5 +++++ util/util.go | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/net/conn/dial.go b/net/conn/dial.go index 7b9c1dc86..dcb196bf5 100644 --- a/net/conn/dial.go +++ b/net/conn/dial.go @@ -8,6 +8,7 @@ import ( manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net" peer "github.com/jbenet/go-ipfs/peer" + "github.com/jbenet/go-ipfs/util" ) // Dial connects to a particular peer, over a given network @@ -23,6 +24,10 @@ func (d *Dialer) Dial(ctx context.Context, network string, remote peer.Peer) (Co return nil, fmt.Errorf("No remote address for network %s", network) } + if util.IsLoopbackAddr(raddr.String()) { + return nil, fmt.Errorf("Attempted to connect to loopback address: %s", raddr) + } + remote, err := d.Peerstore.Add(remote) if err != nil { log.Errorf("Error putting peer into peerstore: %s", remote) diff --git a/util/util.go b/util/util.go index 0076322e2..81b143057 100644 --- a/util/util.go +++ b/util/util.go @@ -113,7 +113,13 @@ func GetenvBool(name string) bool { } func IsLoopbackAddr(addr string) bool { - return addr == "/ip4/127.0.0.1" || addr == "/ip6/::1" + loops := []string{"/ip4/127.0.0.1", "/ip6/::1", "/ip4/0.0.0.0"} + for _, loop := range loops { + if strings.HasPrefix(addr, loop) { + return true + } + } + return false } func GetLocalAddresses() ([]ma.Multiaddr, error) { From 402fdd2db9ded7ef5811b6e8a7f57dfd22b433f5 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 3 Nov 2014 01:45:45 +0000 Subject: [PATCH 4/6] changes from PR --- net/conn/dial.go | 5 ----- net/conn/handshake.go | 16 +++++++++++++--- net/handshake/pb/handshake.proto | 4 ++++ net/swarm/swarm.go | 11 +++++++++++ util/util.go | 11 +++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/net/conn/dial.go b/net/conn/dial.go index dcb196bf5..7b9c1dc86 100644 --- a/net/conn/dial.go +++ b/net/conn/dial.go @@ -8,7 +8,6 @@ import ( manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net" peer "github.com/jbenet/go-ipfs/peer" - "github.com/jbenet/go-ipfs/util" ) // Dial connects to a particular peer, over a given network @@ -24,10 +23,6 @@ func (d *Dialer) Dial(ctx context.Context, network string, remote peer.Peer) (Co return nil, fmt.Errorf("No remote address for network %s", network) } - if util.IsLoopbackAddr(raddr.String()) { - return nil, fmt.Errorf("Attempted to connect to loopback address: %s", raddr) - } - remote, err := d.Peerstore.Add(remote) if err != nil { log.Errorf("Error putting peer into peerstore: %s", remote) diff --git a/net/conn/handshake.go b/net/conn/handshake.go index 1d4e63cd3..61e6d183a 100644 --- a/net/conn/handshake.go +++ b/net/conn/handshake.go @@ -106,18 +106,28 @@ func Handshake3(ctx context.Context, c Conn) error { return err } - nat, err := CheckNAT(remoteH.GetObservedAddr()) + // If we are behind a NAT, inform the user that certain things might not work yet + nat, err := checkNAT(remoteH.GetObservedAddr()) if err != nil { log.Errorf("Error in NAT detection: %s", err) } if nat { - log.Warning("We are probably behind a NAT!") + msg := `Remote peer observed our address to be: %s + The local addresses are: %s + Thus, connection is going through NAT, and other connections may fail. + + IPFS NAT traversal is still under development. Please bug us on github or irc to fix this. + Baby steps: http://jbenet.static.s3.amazonaws.com/271dfcf/baby-steps.gif + ` + addrs, _ := u.GetLocalAddresses() + log.Warning(fmt.Sprintf(msg, remoteH.GetObservedAddr(), addrs)) } return nil } -func CheckNAT(obsaddr string) (bool, error) { +// checkNAT returns whether or not we might be behind a NAT +func checkNAT(obsaddr string) (bool, error) { oma, err := ma.NewMultiaddr(obsaddr) if err != nil { return false, err diff --git a/net/handshake/pb/handshake.proto b/net/handshake/pb/handshake.proto index 1e795d881..789c9dbbb 100644 --- a/net/handshake/pb/handshake.proto +++ b/net/handshake/pb/handshake.proto @@ -30,5 +30,9 @@ message Handshake3 { // repeated mux.ProtocolID services = 3; // we'll have more fields here later. + + // oservedAddr is the multiaddr of the remote endpoint that the local node perceives + // this is useful information to convey to the other side, as it helps the remote endpoint + // determine whether its connection to the local peer goes through NAT. optional string observedAddr = 4; } diff --git a/net/swarm/swarm.go b/net/swarm/swarm.go index 2d73a86f6..1f1996afd 100644 --- a/net/swarm/swarm.go +++ b/net/swarm/swarm.go @@ -3,6 +3,7 @@ package swarm import ( "errors" "fmt" + "strings" "sync" conn "github.com/jbenet/go-ipfs/net/conn" @@ -127,6 +128,16 @@ func (s *Swarm) Dial(peer peer.Peer) (conn.Conn, error) { Peerstore: s.peers, } + // If we are attempting to connect to a loopback addr, fail out early + raddr := peer.NetAddress("tcp") + if raddr == nil { + return nil, fmt.Errorf("No remote address for network tcp") + } + + if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") { + return nil, fmt.Errorf("Attempted to connect to loopback address: %s", raddr) + } + c, err = d.Dial(s.Context(), "tcp", peer) if err != nil { return nil, err diff --git a/util/util.go b/util/util.go index 81b143057..68109f03f 100644 --- a/util/util.go +++ b/util/util.go @@ -112,6 +112,8 @@ func GetenvBool(name string) bool { return v == "true" || v == "t" || v == "1" } +// IsLoopbackAddr returns whether or not the ip portion of the passed in multiaddr +// string is a loopback address func IsLoopbackAddr(addr string) bool { loops := []string{"/ip4/127.0.0.1", "/ip6/::1", "/ip4/0.0.0.0"} for _, loop := range loops { @@ -122,7 +124,10 @@ func IsLoopbackAddr(addr string) bool { return false } +// GetLocalAddresses returns a list of ip addresses associated with +// the local machine func GetLocalAddresses() ([]ma.Multiaddr, error) { + // Enumerate interfaces on this machine ifaces, err := net.Interfaces() if err != nil { return nil, err @@ -135,19 +140,25 @@ func GetLocalAddresses() ([]ma.Multiaddr, error) { log.Warningf("Skipping addr: %s", err) continue } + // Check each address and convert to a multiaddr for _, addr := range addrs { switch v := addr.(type) { case *net.IPNet: + + // Build multiaddr maddr, err := manet.FromIP(v.IP) if err != nil { log.Errorf("maddr parsing error: %s", err) continue } + + // Dont list loopback addresses if IsLoopbackAddr(maddr.String()) { continue } maddrs = append(maddrs, maddr) default: + // Not sure if any other types will show up here log.Errorf("Got '%s' type = '%s'", v, reflect.TypeOf(v)) } } From 05817373d03f809dab1a9a05a8e7af3232b02354 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 3 Nov 2014 20:42:40 +0000 Subject: [PATCH 5/6] Clear up naming around zero address --- net/swarm/swarm.go | 2 +- util/util.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/swarm/swarm.go b/net/swarm/swarm.go index 1f1996afd..e80758979 100644 --- a/net/swarm/swarm.go +++ b/net/swarm/swarm.go @@ -128,7 +128,7 @@ func (s *Swarm) Dial(peer peer.Peer) (conn.Conn, error) { Peerstore: s.peers, } - // If we are attempting to connect to a loopback addr, fail out early + // If we are attempting to connect to the zero addr, fail out early raddr := peer.NetAddress("tcp") if raddr == nil { return nil, fmt.Errorf("No remote address for network tcp") diff --git a/util/util.go b/util/util.go index 68109f03f..b738bbfcb 100644 --- a/util/util.go +++ b/util/util.go @@ -115,7 +115,7 @@ func GetenvBool(name string) bool { // IsLoopbackAddr returns whether or not the ip portion of the passed in multiaddr // string is a loopback address func IsLoopbackAddr(addr string) bool { - loops := []string{"/ip4/127.0.0.1", "/ip6/::1", "/ip4/0.0.0.0"} + loops := []string{"/ip4/127.0.0.1", "/ip6/::1"} for _, loop := range loops { if strings.HasPrefix(addr, loop) { return true From 3e620427455e38742ea2ca2e1c9f96248efa5188 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 3 Nov 2014 15:10:44 -0800 Subject: [PATCH 6/6] fix up NAT check and clear up naming --- net/conn/handshake.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/conn/handshake.go b/net/conn/handshake.go index 61e6d183a..36a187061 100644 --- a/net/conn/handshake.go +++ b/net/conn/handshake.go @@ -127,8 +127,8 @@ func Handshake3(ctx context.Context, c Conn) error { } // checkNAT returns whether or not we might be behind a NAT -func checkNAT(obsaddr string) (bool, error) { - oma, err := ma.NewMultiaddr(obsaddr) +func checkNAT(observedaddr string) (bool, error) { + observedma, err := ma.NewMultiaddr(observedaddr) if err != nil { return false, err } @@ -137,9 +137,9 @@ func checkNAT(obsaddr string) (bool, error) { return false, err } - omastr := oma.String() + omastr := observedma.String() for _, addr := range addrs { - if strings.HasPrefix(addr.String(), omastr) { + if strings.HasPrefix(omastr, addr.String()) { return false, nil } }