Merge pull request #271 from jbenet/listen-addrs

Swarm + Handshake Listen Addresses
This commit is contained in:
Juan Batiz-Benet 2014-11-05 09:25:52 -08:00
commit 7cfe2f1a00
28 changed files with 627 additions and 213 deletions

6
Godeps/Godeps.json generated
View File

@ -1,6 +1,6 @@
{
"ImportPath": "github.com/jbenet/go-ipfs",
"GoVersion": "go1.3.3",
"GoVersion": "go1.3",
"Packages": [
"./..."
],
@ -102,8 +102,8 @@
},
{
"ImportPath": "github.com/jbenet/go-multiaddr",
"Comment": "0.1.2-17-g68a2067",
"Rev": "68a20675cb0829da219def0d90afe17a7219e8c7"
"Comment": "0.1.2-27-g62a88e0",
"Rev": "62a88e015e1bf5d6aaca29aec1aba0722f21c8d3"
},
{
"ImportPath": "github.com/jbenet/go-multihash",

View File

@ -34,16 +34,6 @@ addr.Protocols()
// }
```
### Other formats
```go
// handles the stupid url version too
m = ma.NewUrl("udp4://127.0.0.1:1234")
// <Multiaddr /ip4/127.0.0.1/udp/1234>
m.Url(buf)
// udp4://127.0.0.1:1234
```
### En/decapsulate
```go

View File

@ -67,6 +67,30 @@ func bytesToString(b []byte) (ret string, err error) {
return s, nil
}
func bytesSplit(b []byte) (ret [][]byte, err error) {
// panic handler, in case we try accessing bytes incorrectly.
defer func() {
if e := recover(); e != nil {
ret = [][]byte{}
err = e.(error)
}
}()
ret = [][]byte{}
for len(b) > 0 {
p := ProtocolWithCode(int(b[0]))
if p == nil {
return [][]byte{}, fmt.Errorf("no protocol with code %d", b[0])
}
length := 1 + (p.Size / 8)
ret = append(ret, b[:length])
b = b[length:]
}
return ret, nil
}
func addressStringToBytes(p *Protocol, s string) []byte {
switch p.Code {

View File

@ -84,10 +84,10 @@ func (m *multiaddr) Encapsulate(o Multiaddr) Multiaddr {
mb := m.bytes
ob := o.Bytes()
var b bytes.Buffer
b.Write(mb)
b.Write(ob)
return &multiaddr{bytes: b.Bytes()}
b := make([]byte, len(mb)+len(ob))
copy(b, mb)
copy(b[len(mb):], ob)
return &multiaddr{bytes: b}
}
// Decapsulate unwraps Multiaddr up until the given Multiaddr is found.

Binary file not shown.

View File

@ -0,0 +1,96 @@
package main
import (
"encoding/hex"
"flag"
"fmt"
"os"
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"
)
// flags
var formats = []string{"string", "bytes", "hex", "slice"}
var format string
var hideLoopback bool
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s [<multiaddr>]\n\nFlags:\n", os.Args[0])
flag.PrintDefaults()
}
usage := fmt.Sprintf("output format, one of: %v", formats)
flag.StringVar(&format, "format", "string", usage)
flag.StringVar(&format, "f", "string", usage+" (shorthand)")
flag.BoolVar(&hideLoopback, "hide-loopback", false, "do not display loopback addresses")
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) == 0 {
output(localAddresses()...)
} else {
output(address(args[0]))
}
}
func localAddresses() []ma.Multiaddr {
maddrs, err := manet.InterfaceMultiaddrs()
if err != nil {
die(err)
}
if !hideLoopback {
return maddrs
}
var maddrs2 []ma.Multiaddr
for _, a := range maddrs {
if !manet.IsIPLoopback(a) {
maddrs2 = append(maddrs2, a)
}
}
return maddrs2
}
func address(addr string) ma.Multiaddr {
m, err := ma.NewMultiaddr(addr)
if err != nil {
die(err)
}
return m
}
func output(ms ...ma.Multiaddr) {
for _, m := range ms {
fmt.Println(outfmt(m))
}
}
func outfmt(m ma.Multiaddr) string {
switch format {
case "string":
return m.String()
case "slice":
return fmt.Sprintf("%v", m.Bytes())
case "bytes":
return string(m.Bytes())
case "hex":
return "0x" + hex.EncodeToString(m.Bytes())
}
die("error: invalid format", format)
return ""
}
func die(v ...interface{}) {
fmt.Fprint(os.Stderr, v...)
fmt.Fprint(os.Stderr, "\n")
flag.Usage()
os.Exit(-1)
}

View File

@ -91,6 +91,49 @@ func TestBytesToString(t *testing.T) {
testString("/ip4/127.0.0.1/udp/1234", "047f0000011104d2")
}
func TestBytesSplitAndJoin(t *testing.T) {
testString := func(s string, res []string) {
m, err := NewMultiaddr(s)
if err != nil {
t.Error("failed to convert", s)
}
split := Split(m)
if len(split) != len(res) {
t.Error("not enough split components", split)
return
}
for i, a := range split {
if a.String() != res[i] {
t.Errorf("split component failed: %s != %s", a, res[i])
}
}
joined := Join(split...)
if !m.Equal(joined) {
t.Errorf("joined components failed: %s != %s", m, joined)
}
// modifying underlying bytes is fine.
m2 := m.(*multiaddr)
for i := range m2.bytes {
m2.bytes[i] = 0
}
for i, a := range split {
if a.String() != res[i] {
t.Errorf("split component failed: %s != %s", a, res[i])
}
}
}
testString("/ip4/1.2.3.4/udp/1234", []string{"/ip4/1.2.3.4", "/udp/1234"})
testString("/ip4/1.2.3.4/tcp/1/ip4/2.3.4.5/udp/2",
[]string{"/ip4/1.2.3.4", "/tcp/1", "/ip4/2.3.4.5", "/udp/2"})
}
func TestProtocols(t *testing.T) {
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
if err != nil {

View File

@ -1,4 +1,4 @@
package net
package manet
import (
"fmt"
@ -62,6 +62,13 @@ func FromNetAddr(a net.Addr) (ma.Multiaddr, error) {
}
return FromIP(ac.IP)
case "ip+net":
ac, ok := a.(*net.IPNet)
if !ok {
return nil, errIncorrectNetAddr
}
return FromIP(ac.IP)
default:
return nil, fmt.Errorf("unknown network %v", a.Network())
}
@ -123,30 +130,3 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
}
return network, host, nil
}
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
// This means: /{IP4, IP6}[/{TCP, UDP}]
func IsThinWaist(m ma.Multiaddr) bool {
p := m.Protocols()
// nothing? not even a waist.
if len(p) == 0 {
return false
}
if p[0].Code != ma.P_IP4 && p[0].Code != ma.P_IP6 {
return false
}
// only IP? still counts.
if len(p) == 1 {
return true
}
switch p[1].Code {
case ma.P_TCP, ma.P_UDP, ma.P_IP4, ma.P_IP6:
return true
default:
return false
}
}

View File

@ -1,4 +1,4 @@
package net
package manet
import (
"net"

View File

@ -1,5 +1,5 @@
// Package net provides Multiaddr specific versions of common
// Package manet provides Multiaddr specific versions of common
// functions in stdlib's net package. This means wrappers of
// standard net symbols like net.Dial and net.Listen, as well
// as conversion to/from net.Addr.
package net
package manet

View File

@ -0,0 +1,76 @@
package manet
import (
"bytes"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
// Loopback Addresses
var (
// IP4Loopback is the ip4 loopback multiaddr
IP4Loopback = ma.StringCast("/ip4/127.0.0.1")
// IP6Loopback is the ip6 loopback multiaddr
IP6Loopback = ma.StringCast("/ip6/::1")
// IP6LinkLocalLoopback is the ip6 link-local loopback multiaddr
IP6LinkLocalLoopback = ma.StringCast("/ip6/fe80::1")
)
// Unspecified Addresses (used for )
var (
IP4Unspecified = ma.StringCast("/ip4/0.0.0.0")
IP6Unspecified = ma.StringCast("/ip6/::")
)
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
// This means: /{IP4, IP6}[/{TCP, UDP}]
func IsThinWaist(m ma.Multiaddr) bool {
p := m.Protocols()
// nothing? not even a waist.
if len(p) == 0 {
return false
}
if p[0].Code != ma.P_IP4 && p[0].Code != ma.P_IP6 {
return false
}
// only IP? still counts.
if len(p) == 1 {
return true
}
switch p[1].Code {
case ma.P_TCP, ma.P_UDP, ma.P_IP4, ma.P_IP6:
return true
default:
return false
}
}
// IsIPLoopback returns whether a Multiaddr is a "Loopback" IP address
// This means either /ip4/127.0.0.1 or /ip6/::1
func IsIPLoopback(m ma.Multiaddr) bool {
b := m.Bytes()
// /ip4/127 prefix (_entire_ /8 is loopback...)
if bytes.HasPrefix(b, []byte{4, 127}) {
return true
}
// /ip6/::1
if IP6Loopback.Equal(m) || IP6LinkLocalLoopback.Equal(m) {
return true
}
return false
}
// IsIPUnspecified returns whether a Multiaddr is am Unspecified IP address
// This means either /ip4/0.0.0.0 or /ip6/::
func IsIPUnspecified(m ma.Multiaddr) bool {
return IP4Unspecified.Equal(m) || IP6Unspecified.Equal(m)
}

View File

@ -1,4 +1,4 @@
package net
package manet
import (
"fmt"
@ -216,3 +216,20 @@ func Listen(laddr ma.Multiaddr) (Listener, error) {
laddr: laddr,
}, nil
}
// InterfaceMultiaddrs will return the addresses matching net.InterfaceAddrs
func InterfaceMultiaddrs() ([]ma.Multiaddr, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
maddrs := make([]ma.Multiaddr, len(addrs))
for i, a := range addrs {
maddrs[i], err = FromNetAddr(a)
if err != nil {
return nil, err
}
}
return maddrs, nil
}

View File

@ -1,4 +1,4 @@
package net
package manet
import (
"bytes"
@ -198,3 +198,47 @@ func TestListenAndDial(t *testing.T) {
cA.Close()
wg.Wait()
}
func TestIPLoopback(t *testing.T) {
if IP4Loopback.String() != "/ip4/127.0.0.1" {
t.Error("IP4Loopback incorrect:", IP4Loopback)
}
if IP6Loopback.String() != "/ip6/::1" {
t.Error("IP6Loopback incorrect:", IP6Loopback)
}
if IP6LinkLocalLoopback.String() != "/ip6/fe80::1" {
t.Error("IP6LinkLocalLoopback incorrect:", IP6Loopback)
}
if !IsIPLoopback(IP4Loopback) {
t.Error("IsIPLoopback failed (IP4Loopback)")
}
if !IsIPLoopback(IP6Loopback) {
t.Error("IsIPLoopback failed (IP6Loopback)")
}
if !IsIPLoopback(IP6LinkLocalLoopback) {
t.Error("IsIPLoopback failed (IP6LinkLocalLoopback)")
}
}
func TestIPUnspecified(t *testing.T) {
if IP4Unspecified.String() != "/ip4/0.0.0.0" {
t.Error("IP4Unspecified incorrect:", IP4Unspecified)
}
if IP6Unspecified.String() != "/ip6/::" {
t.Error("IP6Unspecified incorrect:", IP6Unspecified)
}
if !IsIPUnspecified(IP4Unspecified) {
t.Error("IsIPUnspecified failed (IP4Unspecified)")
}
if !IsIPUnspecified(IP6Unspecified) {
t.Error("IsIPUnspecified failed (IP6Unspecified)")
}
}

View File

@ -0,0 +1,56 @@
package multiaddr
import "fmt"
// Split returns the sub-address portions of a multiaddr.
func Split(m Multiaddr) []Multiaddr {
split, err := bytesSplit(m.Bytes())
if err != nil {
panic(fmt.Errorf("invalid multiaddr %s", m.String()))
}
addrs := make([]Multiaddr, len(split))
for i, addr := range split {
addrs[i] = &multiaddr{bytes: addr}
}
return addrs
}
// Join returns a combination of addresses.
func Join(ms ...Multiaddr) Multiaddr {
length := 0
bs := make([][]byte, len(ms))
for i, m := range ms {
bs[i] = m.Bytes()
length += len(bs[i])
}
bidx := 0
b := make([]byte, length)
for _, mb := range bs {
for i := range mb {
b[bidx] = mb[i]
bidx++
}
}
return &multiaddr{bytes: b}
}
// Cast re-casts a byte slice as a multiaddr. will panic if it fails to parse.
func Cast(b []byte) Multiaddr {
_, err := bytesToString(b)
if err != nil {
panic(fmt.Errorf("multiaddr failed to parse: %s", err))
}
return &multiaddr{bytes: b}
}
// StringCast like Cast, but parses a string. Will also panic if it fails to parse.
func StringCast(s string) Multiaddr {
m, err := NewMultiaddr(s)
if err != nil {
panic(fmt.Errorf("multiaddr failed to parse: %s", err))
}
return m
}

View File

@ -123,7 +123,12 @@ func NewIpfsNode(cfg *config.Config, online bool) (n *IpfsNode, err error) {
}
// setup the network
n.Network, err = inet.NewIpfsNetwork(ctx, n.Identity, n.Peerstore, muxMap)
listenAddrs, err := listenAddresses(cfg)
if err != nil {
return nil, err
}
n.Network, err = inet.NewIpfsNetwork(ctx, listenAddrs, n.Identity, n.Peerstore, muxMap)
if err != nil {
return nil, err
}
@ -183,16 +188,6 @@ func initIdentity(cfg *config.Config, peers peer.Peerstore, online bool) (peer.P
return nil, err
}
// address is optional
if len(cfg.Addresses.Swarm) > 0 {
maddr, err := ma.NewMultiaddr(cfg.Addresses.Swarm)
if err != nil {
return nil, err
}
peer.AddAddress(maddr)
}
// when not online, don't need to parse private keys (yet)
if online {
skb, err := base64.StdEncoding.DecodeString(cfg.Identity.PrivKey)
@ -233,3 +228,18 @@ func initConnections(ctx context.Context, cfg *config.Config, pstore peer.Peerst
}
}
}
func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) {
var listen []ma.Multiaddr
if len(cfg.Addresses.Swarm) > 0 {
maddr, err := ma.NewMultiaddr(cfg.Addresses.Swarm)
if err != nil {
return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm: %s", cfg.Addresses.Swarm)
}
listen = append(listen, maddr)
}
return listen, nil
}

View File

@ -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
}

View File

@ -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,45 @@ 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
}
if lpeer.AddAddress(observedAddr) {
log.Infof("(nat) added new local, remote-observed address: %s", 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
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -6,6 +6,8 @@ import (
srv "github.com/jbenet/go-ipfs/net/service"
peer "github.com/jbenet/go-ipfs/peer"
ctxc "github.com/jbenet/go-ipfs/util/ctxcloser"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
// Network is the interface IPFS uses for connecting to the world.
@ -37,6 +39,14 @@ type Network interface {
// SendMessage sends given Message out
SendMessage(msg.NetMessage) error
// ListenAddresses returns a list of addresses at which this network listens.
ListenAddresses() []ma.Multiaddr
// InterfaceListenAddresses returns a list of addresses at which this network
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
InterfaceListenAddresses() ([]ma.Multiaddr, error)
}
// Sender interface for network services.

View File

@ -8,6 +8,7 @@ import (
ctxc "github.com/jbenet/go-ipfs/util/ctxcloser"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
// IpfsNetwork implements the Network interface,
@ -27,7 +28,7 @@ type IpfsNetwork struct {
}
// NewIpfsNetwork is the structure that implements the network interface
func NewIpfsNetwork(ctx context.Context, local peer.Peer,
func NewIpfsNetwork(ctx context.Context, listen []ma.Multiaddr, local peer.Peer,
peers peer.Peerstore, pmap *mux.ProtocolMap) (*IpfsNetwork, error) {
in := &IpfsNetwork{
@ -37,7 +38,7 @@ func NewIpfsNetwork(ctx context.Context, local peer.Peer,
}
var err error
in.swarm, err = swarm.NewSwarm(ctx, local, peers)
in.swarm, err = swarm.NewSwarm(ctx, listen, local, peers)
if err != nil {
in.Close()
return nil, err
@ -96,3 +97,15 @@ func (n *IpfsNetwork) GetPeerList() []peer.Peer {
func (n *IpfsNetwork) GetBandwidthTotals() (in uint64, out uint64) {
return n.muxer.GetBandwidthTotals()
}
// ListenAddresses returns a list of addresses at which this network listens.
func (n *IpfsNetwork) ListenAddresses() []ma.Multiaddr {
return n.swarm.ListenAddresses()
}
// InterfaceListenAddresses returns a list of addresses at which this network
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
func (n *IpfsNetwork) InterfaceListenAddresses() ([]ma.Multiaddr, error) {
return n.swarm.InterfaceListenAddresses()
}

107
net/swarm/addrs.go Normal file
View File

@ -0,0 +1,107 @@
package swarm
import (
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
// ListenAddresses returns a list of addresses at which this swarm listens.
func (s *Swarm) ListenAddresses() []ma.Multiaddr {
addrs := make([]ma.Multiaddr, len(s.listeners))
for i, l := range s.listeners {
addrs[i] = l.Multiaddr()
}
return addrs
}
// InterfaceListenAddresses returns a list of addresses at which this swarm
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
func (s *Swarm) InterfaceListenAddresses() ([]ma.Multiaddr, error) {
return resolveUnspecifiedAddresses(s.ListenAddresses())
}
// resolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
func resolveUnspecifiedAddresses(unspecifiedAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) {
var outputAddrs []ma.Multiaddr
// todo optimize: only fetch these if we have a "any" addr.
ifaceAddrs, err := interfaceAddresses()
if err != nil {
return nil, err
}
for _, a := range unspecifiedAddrs {
// split address into its components
split := ma.Split(a)
// if first component (ip) is not unspecified, use it as is.
if !manet.IsIPUnspecified(split[0]) {
outputAddrs = append(outputAddrs)
continue
}
// unspecified? add one address per interface.
for _, ia := range ifaceAddrs {
split[0] = ia
joined := ma.Join(split...)
outputAddrs = append(outputAddrs, joined)
}
}
log.Info("InterfaceListenAddresses:", outputAddrs)
return outputAddrs, nil
}
// interfaceAddresses returns a list of addresses associated with local machine
func interfaceAddresses() ([]ma.Multiaddr, error) {
maddrs, err := manet.InterfaceMultiaddrs()
if err != nil {
return nil, err
}
var nonLoopback []ma.Multiaddr
for _, a := range maddrs {
if !manet.IsIPLoopback(a) {
nonLoopback = append(nonLoopback, a)
}
}
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
`

View File

@ -12,14 +12,14 @@ import (
)
// Open listeners for each network the swarm should listen on
func (s *Swarm) listen() error {
func (s *Swarm) listen(addrs []ma.Multiaddr) error {
hasErr := false
retErr := &ListenErr{
Errors: make([]error, len(s.local.Addresses())),
Errors: make([]error, len(addrs)),
}
// listen on every address
for i, addr := range s.local.Addresses() {
for i, addr := range addrs {
err := s.connListen(addr)
if err != nil {
hasErr = true
@ -37,11 +37,21 @@ func (s *Swarm) listen() error {
// Listen for new connections on the given multiaddr
func (s *Swarm) connListen(maddr ma.Multiaddr) error {
resolved, err := resolveUnspecifiedAddresses([]ma.Multiaddr{maddr})
if err != nil {
return err
}
list, err := conn.Listen(s.Context(), maddr, s.local, s.peers)
if err != nil {
return err
}
// add resolved local addresses to peer
for _, addr := range resolved {
s.local.AddAddress(addr)
}
// make sure port can be reused. TOOD this doesn't work...
// if err := setSocketReuse(list); err != nil {
// return err
@ -102,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()

View File

@ -13,6 +13,7 @@ import (
ctxc "github.com/jbenet/go-ipfs/util/ctxcloser"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
var log = u.Logger("swarm")
@ -70,7 +71,7 @@ type Swarm struct {
}
// NewSwarm constructs a Swarm, with a Chan.
func NewSwarm(ctx context.Context, local peer.Peer, ps peer.Peerstore) (*Swarm, error) {
func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr, local peer.Peer, ps peer.Peerstore) (*Swarm, error) {
s := &Swarm{
Pipe: msg.NewPipe(10),
conns: conn.MultiConnMap{},
@ -83,7 +84,7 @@ func NewSwarm(ctx context.Context, local peer.Peer, ps peer.Peerstore) (*Swarm,
s.ContextCloser = ctxc.NewContextCloser(ctx, s.close)
go s.fanOut()
return s, s.listen()
return s, s.listen(listenAddrs)
}
// close stops a swarm. It's the underlying function called by ContextCloser
@ -210,6 +211,3 @@ func (s *Swarm) GetPeerList() []peer.Peer {
s.connsLock.RUnlock()
return out
}
// Temporary to ensure that the Swarm always matches the Network interface as we are changing it
// var _ Network = &Swarm{}

View File

@ -57,7 +57,7 @@ func makeSwarms(ctx context.Context, t *testing.T, addrs []string) ([]*Swarm, []
for _, addr := range addrs {
local := setupPeer(t, addr)
peerstore := peer.NewPeerstore()
swarm, err := NewSwarm(ctx, local, peerstore)
swarm, err := NewSwarm(ctx, local.Addresses(), local, peerstore)
if err != nil {
t.Fatal(err)
}

View File

@ -66,7 +66,8 @@ type Peer interface {
Addresses() []ma.Multiaddr
// AddAddress adds the given Multiaddr address to Peer's addresses.
AddAddress(a ma.Multiaddr)
// returns whether this was a newly added address.
AddAddress(a ma.Multiaddr) bool
// NetAddress returns the first Multiaddr found for a given network.
NetAddress(n string) ma.Multiaddr
@ -141,16 +142,18 @@ func (p *peer) Addresses() []ma.Multiaddr {
}
// AddAddress adds the given Multiaddr address to Peer's addresses.
func (p *peer) AddAddress(a ma.Multiaddr) {
// Returns whether this address was a newly added address
func (p *peer) AddAddress(a ma.Multiaddr) bool {
p.Lock()
defer p.Unlock()
for _, addr := range p.addresses {
if addr.Equal(a) {
return
return false
}
}
p.addresses = append(p.addresses, a)
return true
}
// NetAddress returns the first Multiaddr found for a given network.

View File

@ -24,7 +24,7 @@ func setupDHT(ctx context.Context, t *testing.T, p peer.Peer) *IpfsDHT {
peerstore := peer.NewPeerstore()
dhts := netservice.NewService(ctx, nil) // nil handler for now, need to patch it
net, err := inet.NewIpfsNetwork(ctx, p, peerstore, &mux.ProtocolMap{
net, err := inet.NewIpfsNetwork(ctx, p.Addresses(), p, peerstore, &mux.ProtocolMap{
mux.ProtocolID_Routing: dhts,
})
if err != nil {

View File

@ -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
}