diff --git a/net/conn/handshake.go b/net/conn/handshake.go index ab8cea8e3..36a187061 100644 --- a/net/conn/handshake.go +++ b/net/conn/handshake.go @@ -3,12 +3,15 @@ package conn import ( "errors" "fmt" + "strings" 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 +68,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 @@ -99,5 +106,43 @@ func Handshake3(ctx context.Context, c Conn) error { return err } + // 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 { + 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 } + +// checkNAT returns whether or not we might be behind a NAT +func checkNAT(observedaddr string) (bool, error) { + observedma, err := ma.NewMultiaddr(observedaddr) + if err != nil { + return false, err + } + addrs, err := u.GetLocalAddresses() + if err != nil { + return false, err + } + + omastr := observedma.String() + for _, addr := range addrs { + if strings.HasPrefix(omastr, addr.String()) { + return false, nil + } + } + + return true, nil +} 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..789c9dbbb 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,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..e80758979 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 the zero 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 9ba3d62f7..b738bbfcb 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,57 @@ func GetenvBool(name string) bool { v := strings.ToLower(os.Getenv(name)) 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"} + for _, loop := range loops { + if strings.HasPrefix(addr, loop) { + return true + } + } + 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 + } + + var maddrs []ma.Multiaddr + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + 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)) + } + } + } + return maddrs, nil +}