diff --git a/net/conn/handshake.go b/net/conn/handshake.go
index caabf9dce..496c23c40 100644
--- a/net/conn/handshake.go
+++ b/net/conn/handshake.go
@@ -3,15 +3,12 @@ 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-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
 )
 
 // Handshake1 exchanges local and remote versions and compares them
@@ -62,87 +59,48 @@ func Handshake1(ctx context.Context, c Conn) error {
 }
 
 // Handshake3 exchanges local and remote service information
-func Handshake3(ctx context.Context, c Conn) error {
+func Handshake3(ctx context.Context, c Conn) (*handshake.Handshake3Result, error) {
 	rpeer := c.RemotePeer()
 	lpeer := c.LocalPeer()
 
+	// setup + send the message to remote
 	var remoteH, localH *hspb.Handshake3
-	localH = handshake.Handshake3Msg(lpeer)
-
-	rma := c.RemoteMultiaddr()
-	localH.ObservedAddr = proto.String(rma.String())
-
+	localH = handshake.Handshake3Msg(lpeer, c.RemoteMultiaddr())
 	localB, err := proto.Marshal(localH)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	c.Out() <- localB
 	log.Debugf("Handshake1: sent to %s", rpeer)
 
+	// wait + listen for response
 	select {
 	case <-ctx.Done():
-		return ctx.Err()
+		return nil, ctx.Err()
 
 	case <-c.Closing():
-		return errors.New("Handshake3: error remote connection closed")
+		return nil, errors.New("Handshake3: error remote connection closed")
 
 	case remoteB, ok := <-c.In():
 		if !ok {
-			return fmt.Errorf("Handshake3 error receiving from conn: %v", rpeer)
+			return nil, fmt.Errorf("Handshake3 error receiving from conn: %v", rpeer)
 		}
 
 		remoteH = new(hspb.Handshake3)
 		err = proto.Unmarshal(remoteB, remoteH)
 		if err != nil {
-			return fmt.Errorf("Handshake3 could not decode remote msg: %q", err)
+			return nil, fmt.Errorf("Handshake3 could not decode remote msg: %q", err)
 		}
 
 		log.Debugf("Handshake3 received from %s", rpeer)
 	}
 
-	if err := handshake.Handshake3UpdatePeer(rpeer, remoteH); err != nil {
+	// actually update our state based on the new knowledge
+	res, err := handshake.Handshake3Update(lpeer, rpeer, remoteH)
+	if err != nil {
 		log.Errorf("Handshake3 failed to update %s", rpeer)
-		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
+	res.RemoteObservedAddress = c.RemoteMultiaddr()
+	return res, nil
 }
diff --git a/net/handshake/handshake3.go b/net/handshake/handshake3.go
index fa7126754..ad83b13ec 100644
--- a/net/handshake/handshake3.go
+++ b/net/handshake/handshake3.go
@@ -13,18 +13,21 @@ import (
 var log = u.Logger("handshake")
 
 // Handshake3Msg constructs a Handshake3 msg.
-func Handshake3Msg(localPeer peer.Peer) *pb.Handshake3 {
+func Handshake3Msg(localPeer peer.Peer, remoteAddr ma.Multiaddr) *pb.Handshake3 {
 	var msg pb.Handshake3
 	// don't need publicKey after secure channel.
 	// msg.PublicKey = localPeer.PubKey().Bytes()
 
-	// addresses
+	// local listen addresses
 	addrs := localPeer.Addresses()
 	msg.ListenAddrs = make([][]byte, len(addrs))
 	for i, a := range addrs {
 		msg.ListenAddrs[i] = a.Bytes()
 	}
 
+	// observed remote address
+	msg.ObservedAddr = remoteAddr.Bytes()
+
 	// services
 	// srv := localPeer.Services()
 	// msg.Services = make([]mux.ProtocolID, len(srv))
@@ -35,20 +38,43 @@ func Handshake3Msg(localPeer peer.Peer) *pb.Handshake3 {
 	return &msg
 }
 
-// Handshake3UpdatePeer updates a remote peer with the information in the
-// handshake3 msg we received from them.
-func Handshake3UpdatePeer(remotePeer peer.Peer, msg *pb.Handshake3) error {
+// Handshake3Update updates local knowledge with the information in the
+// handshake3 msg we received from remote client.
+func Handshake3Update(lpeer, rpeer peer.Peer, msg *pb.Handshake3) (*Handshake3Result, error) {
+	res := &Handshake3Result{}
 
-	// addresses
+	// our observed address
+	observedAddr, err := ma.NewMultiaddrBytes(msg.GetObservedAddr())
+	if err != nil {
+		return res, err
+	}
+	lpeer.AddAddress(observedAddr)
+	res.LocalObservedAddress = observedAddr
+
+	// remote's reported addresses
 	for _, a := range msg.GetListenAddrs() {
 		addr, err := ma.NewMultiaddrBytes(a)
 		if err != nil {
 			err = fmt.Errorf("remote peer address not a multiaddr: %s", err)
-			log.Errorf("Handshake3: error %s", err)
-			return err
+			log.Errorf("Handshake3 error %s", err)
+			return res, err
 		}
-		remotePeer.AddAddress(addr)
+		rpeer.AddAddress(addr)
+		res.RemoteListenAddresses = append(res.RemoteListenAddresses, addr)
 	}
 
-	return nil
+	return res, nil
+}
+
+// Handshake3Result collects the knowledge gained in Handshake3.
+type Handshake3Result struct {
+
+	// The addresses reported by the remote client
+	RemoteListenAddresses []ma.Multiaddr
+
+	// The address of the remote client we observed in this connection
+	RemoteObservedAddress ma.Multiaddr
+
+	// The address the remote client observed from this connection
+	LocalObservedAddress ma.Multiaddr
 }
diff --git a/net/handshake/pb/handshake.pb.go b/net/handshake/pb/handshake.pb.go
index 8a0a31efe..77ed5a41e 100644
--- a/net/handshake/pb/handshake.pb.go
+++ b/net/handshake/pb/handshake.pb.go
@@ -15,10 +15,12 @@ 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 math "math"
 
-// Reference imports to suppress errors if they are not otherwise used.
+// Reference proto, json, and math imports to suppress error if they are not otherwise used.
 var _ = proto.Marshal
+var _ = &json.SyntaxError{}
 var _ = math.Inf
 
 // Handshake1 is delivered _before_ the secure channel is initialized
@@ -51,11 +53,13 @@ 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 are the multiaddrs the sender node listens for open connections on
 	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:"-"`
+	// oservedAddr is the multiaddr of the remote endpoint that the sender 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.
+	ObservedAddr     []byte `protobuf:"bytes,4,opt,name=observedAddr" json:"observedAddr,omitempty"`
+	XXX_unrecognized []byte `json:"-"`
 }
 
 func (m *Handshake3) Reset()         { *m = Handshake3{} }
@@ -69,11 +73,11 @@ func (m *Handshake3) GetListenAddrs() [][]byte {
 	return nil
 }
 
-func (m *Handshake3) GetObservedAddr() string {
-	if m != nil && m.ObservedAddr != nil {
-		return *m.ObservedAddr
+func (m *Handshake3) GetObservedAddr() []byte {
+	if m != nil {
+		return m.ObservedAddr
 	}
-	return ""
+	return nil
 }
 
 func init() {
diff --git a/net/handshake/pb/handshake.proto b/net/handshake/pb/handshake.proto
index 789c9dbbb..1dc7cad93 100644
--- a/net/handshake/pb/handshake.proto
+++ b/net/handshake/pb/handshake.proto
@@ -22,7 +22,7 @@ message Handshake3 {
   // - then again, if we change / disable secure channel, may still want it.
   // optional bytes publicKey = 1;
 
-  // listenAddrs are the multiaddrs this node listens for open connections on
+  // listenAddrs are the multiaddrs the sender node listens for open connections on
   repeated bytes listenAddrs = 2;
 
   // TODO
@@ -31,8 +31,8 @@ message Handshake3 {
 
   // we'll have more fields here later.
 
-  // oservedAddr is the multiaddr of the remote endpoint that the local node perceives
+  // oservedAddr is the multiaddr of the remote endpoint that the sender 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;
+  optional bytes observedAddr = 4;
 }
diff --git a/net/swarm/addrs.go b/net/swarm/addrs.go
index be927fb2f..531ec62ae 100644
--- a/net/swarm/addrs.go
+++ b/net/swarm/addrs.go
@@ -72,3 +72,36 @@ func interfaceAddresses() ([]ma.Multiaddr, error) {
 
 	return nonLoopback, nil
 }
+
+// addrInList returns whether or not an address is part of a list.
+// this is useful to check if NAT is happening (or other bugs?)
+func addrInList(addr ma.Multiaddr, list []ma.Multiaddr) bool {
+	for _, addr2 := range list {
+		if addr.Equal(addr2) {
+			return true
+		}
+	}
+	return false
+}
+
+// checkNATWarning checks if our observed addresses differ. if so,
+// informs the user that certain things might not work yet
+func (s *Swarm) checkNATWarning(observed ma.Multiaddr) {
+	listen, err := s.InterfaceListenAddresses()
+	if err != nil {
+		log.Errorf("Error retrieving swarm.InterfaceListenAddresses: %s", err)
+		return
+	}
+
+	if !addrInList(observed, listen) { // probably a nat
+		log.Warningf(natWarning, observed, listen)
+	}
+}
+
+const natWarning = `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
+`
diff --git a/net/swarm/conn.go b/net/swarm/conn.go
index bd0cd8680..63f6910a4 100644
--- a/net/swarm/conn.go
+++ b/net/swarm/conn.go
@@ -112,11 +112,15 @@ func (s *Swarm) connSetup(c conn.Conn) (conn.Conn, error) {
 
 	// handshake3
 	ctxT, _ := context.WithTimeout(c.Context(), conn.HandshakeTimeout)
-	if err := conn.Handshake3(ctxT, c); err != nil {
+	h3result, err := conn.Handshake3(ctxT, c)
+	if err != nil {
 		c.Close()
 		return nil, fmt.Errorf("Handshake3 failed: %s", err)
 	}
 
+	// check for nats. you know, just in case.
+	s.checkNATWarning(h3result.LocalObservedAddress)
+
 	// add to conns
 	s.connsLock.Lock()
 
diff --git a/util/util.go b/util/util.go
index b738bbfcb..da07dab4c 100644
--- a/util/util.go
+++ b/util/util.go
@@ -4,16 +4,13 @@ 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"
 )
 
@@ -111,57 +108,3 @@ 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
-}