Commit 51fd99e3 authored by Jeromy's avatar Jeromy
Browse files

extract from 0.4.0

parent 5a0162c7
package mocknet
import (
"sync"
"time"
)
// A ratelimiter is used by a link to determine how long to wait before sending
// data given a bandwidth cap.
type ratelimiter struct {
lock sync.Mutex
bandwidth float64 // bytes per nanosecond
allowance float64 // in bytes
maxAllowance float64 // in bytes
......@@ -29,6 +31,8 @@ func NewRatelimiter(bandwidth float64) *ratelimiter {
// Changes bandwidth of a ratelimiter and resets its allowance
func (r *ratelimiter) UpdateBandwidth(bandwidth float64) {
r.lock.Lock()
defer r.lock.Unlock()
// Convert bandwidth from bytes/second to bytes/nanosecond
b := bandwidth / float64(time.Second)
r.bandwidth = b
......@@ -40,6 +44,8 @@ func (r *ratelimiter) UpdateBandwidth(bandwidth float64) {
// Returns how long to wait before sending data with length 'dataSize' bytes
func (r *ratelimiter) Limit(dataSize int) time.Duration {
r.lock.Lock()
defer r.lock.Unlock()
// update time
var duration time.Duration = time.Duration(0)
if r.bandwidth == 0 {
......
......@@ -3,14 +3,14 @@ package addrutil
import (
"fmt"
logging "github.com/ipfs/go-ipfs/vendor/go-log-v1.0.0"
logging "QmWRypnfEwrgH4k93KEHN5hng7VjKYkWmzDYRuTZeh2Mgh/go-log"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-multiaddr-net"
context "golang.org/x/net/context"
)
var log = logging.Logger("p2p/net/swarm/addr")
var log = logging.Logger("github.com/ipfs/go-libp2p/p2p/net/swarm/addr")
// SupportedTransportStrings is the list of supported transports for the swarm.
// These are strings of encapsulated multiaddr protocols. E.g.:
......
......@@ -3,8 +3,8 @@ package addrutil
import (
"testing"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
ma "github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-multiaddr-net"
)
func newMultiaddr(t *testing.T, s string) ma.Multiaddr {
......
......@@ -2,6 +2,7 @@ package swarm
import (
"net"
"sort"
"sync"
"testing"
"time"
......@@ -9,12 +10,12 @@ import (
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
peer "github.com/ipfs/go-libp2p/p2p/peer"
testutil "github.com/ipfs/go-ipfs/util/testutil"
ci "github.com/ipfs/go-ipfs/util/testutil/ci"
testutil "util/testutil"
ci "util/testutil/ci"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-multiaddr-net"
context "golang.org/x/net/context"
)
func acceptAndHang(l net.Listener) {
......@@ -419,18 +420,18 @@ func TestDialBackoffClears(t *testing.T) {
}
s1.peers.AddAddrs(s2.local, ifaceAddrs1, peer.PermanentAddrTTL)
before = time.Now()
if _, err := s1.Dial(ctx, s2.local); err == nil {
t.Fatal("should have failed to dial backed off peer")
}
time.Sleep(baseBackoffTime)
if c, err := s1.Dial(ctx, s2.local); err != nil {
t.Fatal(err)
} else {
c.Close()
t.Log("correctly connected")
}
duration = time.Now().Sub(before)
if duration >= dt {
// t.Error("took too long", duration, dt)
}
if s1.backf.Backoff(s2.local) {
t.Error("s2 should no longer be on backoff")
......@@ -438,3 +439,38 @@ func TestDialBackoffClears(t *testing.T) {
t.Log("correctly cleared backoff")
}
}
func mkAddr(t *testing.T, s string) ma.Multiaddr {
a, err := ma.NewMultiaddr(s)
if err != nil {
t.Fatal(err)
}
return a
}
func TestAddressSorting(t *testing.T) {
u1 := mkAddr(t, "/ip4/152.12.23.53/udp/1234/utp")
u2l := mkAddr(t, "/ip4/127.0.0.1/udp/1234/utp")
local := mkAddr(t, "/ip4/127.0.0.1/tcp/1234")
norm := mkAddr(t, "/ip4/6.5.4.3/tcp/1234")
l := AddrList{local, u1, u2l, norm}
sort.Sort(l)
if !l[0].Equal(u2l) {
t.Fatal("expected utp local addr to be sorted first: ", l[0])
}
if !l[1].Equal(u1) {
t.Fatal("expected utp addr to be sorted second")
}
if !l[2].Equal(local) {
t.Fatal("expected tcp localhost addr thid")
}
if !l[3].Equal(norm) {
t.Fatal("expected normal addr last")
}
}
......@@ -5,8 +5,8 @@ import (
peer "github.com/ipfs/go-libp2p/p2p/peer"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
)
func TestPeers(t *testing.T) {
......
package swarm
import (
"runtime"
"sync"
"testing"
"time"
ci "github.com/ipfs/go-ipfs/util/testutil/ci"
peer "github.com/ipfs/go-libp2p/p2p/peer"
ci "util/testutil/ci"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
)
func TestSimultOpen(t *testing.T) {
......@@ -49,7 +50,8 @@ func TestSimultOpenMany(t *testing.T) {
addrs := 20
rounds := 10
if ci.IsRunning() {
if ci.IsRunning() || runtime.GOOS == "darwin" {
// osx has a limit of 256 file descriptors
addrs = 10
rounds = 5
}
......
......@@ -7,22 +7,26 @@ import (
"sync"
"time"
logging "github.com/ipfs/go-ipfs/vendor/go-log-v1.0.0"
metrics "github.com/ipfs/go-libp2p/p2p/metrics"
mconn "github.com/ipfs/go-libp2p/p2p/metrics/conn"
inet "github.com/ipfs/go-libp2p/p2p/net"
conn "github.com/ipfs/go-libp2p/p2p/net/conn"
filter "github.com/ipfs/go-libp2p/p2p/net/filter"
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
transport "github.com/ipfs/go-libp2p/p2p/net/transport"
peer "github.com/ipfs/go-libp2p/p2p/peer"
metrics "github.com/ipfs/go-libp2p/util/metrics"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
ps "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream"
pst "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-stream-muxer"
psy "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-stream-muxer/yamux"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
goprocessctx "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
prom "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus"
mafilter "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
ps "github.com/jbenet/go-peerstream"
pst "github.com/jbenet/go-stream-muxer"
psmss "github.com/jbenet/go-stream-muxer/multistream"
"github.com/jbenet/goprocess"
goprocessctx "github.com/jbenet/goprocess/context"
prom "github.com/prometheus/client_golang/prometheus"
mafilter "github.com/whyrusleeping/multiaddr-filter"
context "golang.org/x/net/context"
logging "QmWRypnfEwrgH4k93KEHN5hng7VjKYkWmzDYRuTZeh2Mgh/go-log"
)
var log = logging.Logger("swarm2")
......@@ -37,9 +41,7 @@ var peersTotal = prom.NewGaugeVec(prom.GaugeOpts{
}, []string{"peer_id"})
func init() {
tpt := *psy.DefaultTransport
tpt.MaxStreamWindowSize = 512 * 1024
PSTransport = &tpt
PSTransport = psmss.NewTransport()
}
// Swarm is a connection muxer, allowing connections to other peers to
......@@ -58,12 +60,19 @@ type Swarm struct {
backf dialbackoff
dialT time.Duration // mainly for tests
dialer *conn.Dialer
notifmu sync.RWMutex
notifs map[inet.Notifiee]ps.Notifiee
transports []transport.Transport
// filters for addresses that shouldnt be dialed
Filters *filter.Filters
// file descriptor rate limited
fdRateLimit chan struct{}
proc goprocess.Process
ctx context.Context
bwc metrics.Reporter
......@@ -78,6 +87,10 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
return nil, err
}
wrap := func(c transport.Conn) transport.Conn {
return mconn.WrapConn(bwc, c)
}
s := &Swarm{
swarm: ps.NewSwarm(PSTransport),
local: local,
......@@ -85,8 +98,11 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
ctx: ctx,
dialT: DialTimeout,
notifs: make(map[inet.Notifiee]ps.Notifiee),
transports: []transport.Transport{transport.NewTCPTransport()},
bwc: bwc,
fdRateLimit: make(chan struct{}, concurrentFdDials),
Filters: filter.NewFilters(),
dialer: conn.NewDialer(local, peers.PrivKey(local), wrap),
}
// configure Swarm
......@@ -97,7 +113,12 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
prom.MustRegisterOrGet(peersTotal)
s.Notify((*metricsNotifiee)(s))
return s, s.listen(listenAddrs)
err = s.setupInterfaces(listenAddrs)
if err != nil {
return nil, err
}
return s, nil
}
func (s *Swarm) teardown() error {
......@@ -130,7 +151,7 @@ func (s *Swarm) Listen(addrs ...ma.Multiaddr) error {
return err
}
return s.listen(addrs)
return s.setupInterfaces(addrs)
}
// Process returns the Process of the swarm
......
......@@ -4,7 +4,7 @@ import (
conn "github.com/ipfs/go-libp2p/p2p/net/conn"
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
ma "github.com/jbenet/go-multiaddr"
)
// ListenAddresses returns a list of addresses at which this swarm listens.
......
......@@ -3,13 +3,13 @@ package swarm
import (
"testing"
testutil "github.com/ipfs/go-ipfs/util/testutil"
metrics "github.com/ipfs/go-libp2p/p2p/metrics"
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
peer "github.com/ipfs/go-libp2p/p2p/peer"
metrics "github.com/ipfs/go-libp2p/util/metrics"
testutil "util/testutil"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
)
func TestFilterAddrs(t *testing.T) {
......
......@@ -8,9 +8,9 @@ import (
conn "github.com/ipfs/go-libp2p/p2p/net/conn"
peer "github.com/ipfs/go-libp2p/p2p/peer"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
ps "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
ps "github.com/jbenet/go-peerstream"
context "golang.org/x/net/context"
)
// a Conn is a simple wrapper around a ps.Conn that also exposes
......
package swarm
import (
"bytes"
"errors"
"fmt"
"math/rand"
"net"
"sort"
"sync"
"time"
"github.com/jbenet/go-multiaddr-net"
conn "github.com/ipfs/go-libp2p/p2p/net/conn"
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
peer "github.com/ipfs/go-libp2p/p2p/peer"
lgbl "github.com/ipfs/go-libp2p/util/eventlog/loggables"
mconn "github.com/ipfs/go-libp2p/util/metrics/conn"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
process "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
processctx "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
ratelimit "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/ratelimit"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
lgbl "util/eventlog/loggables"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
)
// Diagram of dial sync:
......@@ -44,6 +40,9 @@ var (
// add loop back in Dial(.)
const dialAttempts = 1
// number of concurrent outbound dials over transports that consume file descriptors
const concurrentFdDials = 160
// DialTimeout is the amount of time each dial attempt has. We can think about making
// this larger down the road, or putting more granular timeouts (i.e. within each
// subcomponent of Dial)
......@@ -115,6 +114,7 @@ func (ds *dialsync) Unlock(dst peer.ID) {
if !found {
panic("called dialDone with no ongoing dials to peer: " + dst.Pretty())
}
delete(ds.ongoing, dst) // remove ongoing dial
close(wait) // release everyone else
ds.lock.Unlock()
......@@ -145,44 +145,71 @@ func (ds *dialsync) Unlock(dst peer.ID) {
// dialbackoff.Clear(p)
// }
//
type dialbackoff struct {
entries map[peer.ID]struct{}
entries map[peer.ID]*backoffPeer
lock sync.RWMutex
}
type backoffPeer struct {
tries int
until time.Time
}
func (db *dialbackoff) init() {
if db.entries == nil {
db.entries = make(map[peer.ID]struct{})
db.entries = make(map[peer.ID]*backoffPeer)
}
}
// Backoff returns whether the client should backoff from dialing
// peeer p
func (db *dialbackoff) Backoff(p peer.ID) bool {
// peer p
func (db *dialbackoff) Backoff(p peer.ID) (backoff bool) {
db.lock.Lock()
defer db.lock.Unlock()
db.init()
_, found := db.entries[p]
db.lock.Unlock()
return found
bp, found := db.entries[p]
if found && time.Now().Before(bp.until) {
return true
}
return false
}
const baseBackoffTime = time.Second * 5
const maxBackoffTime = time.Minute * 5
// AddBackoff lets other nodes know that we've entered backoff with
// peer p, so dialers should not wait unnecessarily. We still will
// attempt to dial with one goroutine, in case we get through.
func (db *dialbackoff) AddBackoff(p peer.ID) {
db.lock.Lock()
defer db.lock.Unlock()
db.init()
db.entries[p] = struct{}{}
db.lock.Unlock()
bp, ok := db.entries[p]
if !ok {
db.entries[p] = &backoffPeer{
tries: 1,
until: time.Now().Add(baseBackoffTime),
}
return
}
expTimeAdd := time.Second * time.Duration(bp.tries*bp.tries)
if expTimeAdd > maxBackoffTime {
expTimeAdd = maxBackoffTime
}
bp.until = time.Now().Add(baseBackoffTime + expTimeAdd)
bp.tries++
}
// Clear removes a backoff record. Clients should call this after a
// successful Dial.
func (db *dialbackoff) Clear(p peer.ID) {
db.lock.Lock()
defer db.lock.Unlock()
db.init()
delete(db.entries, p)
db.lock.Unlock()
}
// Dial connects to a peer.
......@@ -225,14 +252,20 @@ func (s *Swarm) gatedDialAttempt(ctx context.Context, p peer.ID) (*Conn, error)
// check if there's an ongoing dial to this peer
if ok, wait := s.dsync.Lock(p); ok {
defer s.dsync.Unlock(p)
// if this peer has been backed off, lets get out of here
if s.backf.Backoff(p) {
log.Event(ctx, "swarmDialBackoff", logdial)
return nil, ErrDialBackoff
}
// ok, we have been charged to dial! let's do it.
// if it succeeds, dial will add the conn to the swarm itself.
defer log.EventBegin(ctx, "swarmDialAttemptStart", logdial).Done()
ctxT, cancel := context.WithTimeout(ctx, s.dialT)
conn, err := s.dial(ctxT, p)
cancel()
s.dsync.Unlock(p)
log.Debugf("dial end %s", conn)
if err != nil {
log.Event(ctx, "swarmDialBackoffAdd", logdial)
......@@ -287,14 +320,6 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) {
log.Debug("Dial not given PrivateKey, so WILL NOT SECURE conn.")
}
// get our own addrs. try dialing out from our listener addresses (reusing ports)
// Note that using our peerstore's addresses here is incorrect, as that would
// include observed addresses. TODO: make peerstore's address book smarter.
localAddrs := s.ListenAddresses()
if len(localAddrs) == 0 {
log.Debug("Dialing out with no local addresses.")
}
// get remote peer addrs
remoteAddrs := s.peers.Addrs(p)
// make sure we can use the addresses.
......@@ -319,23 +344,8 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) {
return nil, err
}
// open connection to peer
d := &conn.Dialer{
Dialer: manet.Dialer{
Dialer: net.Dialer{
Timeout: s.dialT,
},
},
LocalPeer: s.local,
LocalAddrs: localAddrs,
PrivateKey: sk,
Wrapper: func(c manet.Conn) manet.Conn {
return mconn.WrapConn(s.bwc, c)
},
}
// try to get a connection to any addr
connC, err := s.dialAddrs(ctx, d, p, remoteAddrs)
connC, err := s.dialAddrs(ctx, p, remoteAddrs)
if err != nil {
logdial["error"] = err
return nil, err
......@@ -355,7 +365,10 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) {
return swarmC, nil
}
func (s *Swarm) dialAddrs(ctx context.Context, d *conn.Dialer, p peer.ID, remoteAddrs []ma.Multiaddr) (conn.Conn, error) {
func (s *Swarm) dialAddrs(ctx context.Context, p peer.ID, remoteAddrs []ma.Multiaddr) (conn.Conn, error) {
// sort addresses so preferred addresses are dialed sooner
sort.Sort(AddrList(remoteAddrs))
// try to connect to one of the peer's known addresses.
// we dial concurrently to each of the addresses, which:
......@@ -367,78 +380,89 @@ func (s *Swarm) dialAddrs(ctx context.Context, d *conn.Dialer, p peer.ID, remote
ctx, cancel := context.WithCancel(ctx)
defer cancel() // cancel work when we exit func
foundConn := make(chan struct{})
conns := make(chan conn.Conn, len(remoteAddrs))
conns := make(chan conn.Conn)
errs := make(chan error, len(remoteAddrs))
// dialSingleAddr is used in the rate-limited async thing below.
dialSingleAddr := func(addr ma.Multiaddr) {
connC, err := s.dialAddr(ctx, d, p, addr)
// rebind chans in scope so we can nil them out easily
connsout := conns
errsout := errs
connC, err := s.dialAddr(ctx, p, addr)
if err != nil {
connsout = nil
} else if connC == nil {
// NOTE: this really should never happen
log.Errorf("failed to dial %s %s and got no error!", p, addr)
err = fmt.Errorf("failed to dial %s %s", p, addr)
connsout = nil
} else {
errsout = nil
}
// check parent still wants our results
select {
case <-foundConn:
case <-ctx.Done():
if connC != nil {
connC.Close()
}
return
default:
}
if err != nil {
errs <- err
} else if connC == nil {
errs <- fmt.Errorf("failed to dial %s %s", p, addr)
} else {
conns <- connC
case errsout <- err:
case connsout <- connC:
}
}
// this whole thing is in a goroutine so we can use foundConn
// to end early.
go func() {
// rate limiting just in case. at most 10 addrs at once.
limiter := ratelimit.NewRateLimiter(process.Background(), 10)
limiter.Go(func(worker process.Process) {
// permute addrs so we try different sets first each time.
for _, i := range rand.Perm(len(remoteAddrs)) {
limiter := make(chan struct{}, 8)
for _, addr := range remoteAddrs {
// returns whatever ratelimiting is acceptable for workerAddr.
// may not rate limit at all.
rl := s.addrDialRateLimit(addr)
select {
case <-foundConn: // if one of them succeeded already
break
case <-worker.Closing(): // our context was cancelled
break
default:
case <-ctx.Done(): // our context was cancelled
return
case rl <- struct{}{}:
// take the token, move on
}
workerAddr := remoteAddrs[i] // shadow variable to avoid race
limiter.LimitedGo(func(worker process.Process) {
dialSingleAddr(workerAddr)
})
select {
case <-ctx.Done(): // our context was cancelled
return
case limiter <- struct{}{}:
// take the token, move on
}
})
processctx.CloseAfterContext(limiter, ctx)
go func(rlc <-chan struct{}, a ma.Multiaddr) {
dialSingleAddr(a)
<-limiter
<-rlc
}(rl, addr)
}
}()
// wair fot the results.
// wair for the results.
exitErr := fmt.Errorf("failed to dial %s", p)
for i := 0; i < len(remoteAddrs); i++ {
for range remoteAddrs {
select {
case exitErr = <-errs: //
log.Debug("dial error: ", exitErr)
case connC := <-conns:
// take the first + return asap
close(foundConn)
return connC, nil
case <-ctx.Done():
// break out and return error
break
}
}
return nil, exitErr
}
func (s *Swarm) dialAddr(ctx context.Context, d *conn.Dialer, p peer.ID, addr ma.Multiaddr) (conn.Conn, error) {
func (s *Swarm) dialAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr) (conn.Conn, error) {
log.Debugf("%s swarm dialing %s %s", s.local, p, addr)
connC, err := d.Dial(ctx, addr, p)
connC, err := s.dialer.Dial(ctx, addr, p)
if err != nil {
return nil, fmt.Errorf("%s --> %s dial attempt failed: %s", s.local, p, err)
}
......@@ -491,3 +515,72 @@ func dialConnSetup(ctx context.Context, s *Swarm, connC conn.Conn) (*Conn, error
return swarmC, err
}
// addrDialRateLimit returns a ratelimiting channel for dialing transport
// addrs like a. for example, tcp is fd-ratelimited. utp is not ratelimited.
func (s *Swarm) addrDialRateLimit(a ma.Multiaddr) chan struct{} {
if isFDCostlyTransport(a) {
return s.fdRateLimit
}
// do not rate limit it at all
return make(chan struct{}, 1)
}
func isFDCostlyTransport(a ma.Multiaddr) bool {
return isTCPMultiaddr(a)
}
func isTCPMultiaddr(a ma.Multiaddr) bool {
p := a.Protocols()
return len(p) == 2 && (p[0].Name == "ip4" || p[0].Name == "ip6") && p[1].Name == "tcp"
}
type AddrList []ma.Multiaddr
func (al AddrList) Len() int {
return len(al)
}
func (al AddrList) Swap(i, j int) {
al[i], al[j] = al[j], al[i]
}
func (al AddrList) Less(i, j int) bool {
a := al[i]
b := al[j]
// dial localhost addresses next, they should fail immediately
lba := manet.IsIPLoopback(a)
lbb := manet.IsIPLoopback(b)
if lba {
if !lbb {
return true
}
}
// dial utp and similar 'non-fd-consuming' addresses first
fda := isFDCostlyTransport(a)
fdb := isFDCostlyTransport(b)
if !fda {
if fdb {
return true
}
// if neither consume fd's, assume equal ordering
return false
}
// if 'b' doesnt take a file descriptor
if !fdb {
return false
}
// if 'b' is loopback and both take file descriptors
if lbb {
return false
}
// for the rest, just sort by bytes
return bytes.Compare(a.Bytes(), b.Bytes()) > 0
}
......@@ -3,68 +3,81 @@ package swarm
import (
"fmt"
mconn "github.com/ipfs/go-libp2p/p2p/metrics/conn"
inet "github.com/ipfs/go-libp2p/p2p/net"
conn "github.com/ipfs/go-libp2p/p2p/net/conn"
addrutil "github.com/ipfs/go-libp2p/p2p/net/swarm/addr"
lgbl "github.com/ipfs/go-libp2p/util/eventlog/loggables"
mconn "github.com/ipfs/go-libp2p/util/metrics/conn"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
ps "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
multierr "github.com/ipfs/go-ipfs/thirdparty/multierr"
)
transport "github.com/ipfs/go-libp2p/p2p/net/transport"
lgbl "util/eventlog/loggables"
// Open listeners for each network the swarm should listen on
func (s *Swarm) listen(addrs []ma.Multiaddr) error {
ma "github.com/jbenet/go-multiaddr"
ps "github.com/jbenet/go-peerstream"
context "golang.org/x/net/context"
)
for _, addr := range addrs {
if !addrutil.AddrUsable(addr, true) {
return fmt.Errorf("cannot use addr: %s", addr)
// Open listeners and reuse-dialers for the given addresses
func (s *Swarm) setupInterfaces(addrs []ma.Multiaddr) error {
errs := make([]error, len(addrs))
var succeeded int
for i, a := range addrs {
tpt := s.transportForAddr(a)
if tpt == nil {
errs[i] = fmt.Errorf("no transport for address: %s", a)
continue
}
d, err := tpt.Dialer(a, transport.TimeoutOpt(DialTimeout), transport.ReusePorts)
if err != nil {
errs[i] = err
continue
}
retErr := multierr.New()
s.dialer.AddDialer(d)
// listen on every address
for i, addr := range addrs {
err := s.setupListener(addr)
list, err := tpt.Listen(a)
if err != nil {
if retErr.Errors == nil {
retErr.Errors = make([]error, len(addrs))
errs[i] = err
continue
}
retErr.Errors[i] = err
log.Debugf("Failed to listen on: %s - %s", addr, err)
err = s.addListener(list)
if err != nil {
errs[i] = err
continue
}
succeeded++
}
if retErr.Errors != nil {
return retErr
for i, e := range errs {
if e != nil {
log.Warning("listen on %s failed: %s", addrs[i], errs[i])
}
}
if succeeded == 0 && len(addrs) > 0 {
return fmt.Errorf("failed to listen on any addresses: %s", errs)
}
return nil
}
// Listen for new connections on the given multiaddr
func (s *Swarm) setupListener(maddr ma.Multiaddr) error {
func (s *Swarm) transportForAddr(a ma.Multiaddr) transport.Transport {
for _, t := range s.transports {
if t.Matches(a) {
return t
}
}
// TODO rethink how this has to work. (jbenet)
//
// resolved, err := resolveUnspecifiedAddresses([]ma.Multiaddr{maddr})
// if err != nil {
// return err
// }
// for _, a := range resolved {
// s.peers.AddAddr(s.local, a)
// }
return nil
}
func (s *Swarm) addListener(tptlist transport.Listener) error {
sk := s.peers.PrivKey(s.local)
if sk == nil {
// may be fine for sk to be nil, just log a warning.
log.Warning("Listener not given PrivateKey, so WILL NOT SECURE conns.")
}
log.Debugf("Swarm Listening at %s", maddr)
list, err := conn.Listen(s.Context(), maddr, s.local, sk)
list, err := conn.WrapTransportListener(s.Context(), tptlist, s.local, sk)
if err != nil {
return err
}
......@@ -72,11 +85,15 @@ func (s *Swarm) setupListener(maddr ma.Multiaddr) error {
list.SetAddrFilters(s.Filters)
if cw, ok := list.(conn.ListenerConnWrapper); ok {
cw.SetConnWrapper(func(c manet.Conn) manet.Conn {
cw.SetConnWrapper(func(c transport.Conn) transport.Conn {
return mconn.WrapConn(s.bwc, c)
})
}
return s.addConnListener(list)
}
func (s *Swarm) addConnListener(list conn.Listener) error {
// AddListener to the peerstream Listener. this will begin accepting connections
// and streams!
sl, err := s.swarm.AddListener(list)
......@@ -85,6 +102,8 @@ func (s *Swarm) setupListener(maddr ma.Multiaddr) error {
}
log.Debugf("Swarm Listeners at %s", s.ListenAddresses())
maddr := list.Multiaddr()
// signal to our notifiees on successful conn.
s.notifyAll(func(n inet.Notifiee) {
n.Listen((*Network)(s), maddr)
......@@ -107,7 +126,7 @@ func (s *Swarm) setupListener(maddr ma.Multiaddr) error {
if !more {
return
}
log.Warningf("swarm listener accept error: %s", err)
log.Errorf("swarm listener accept error: %s", err)
case <-ctx.Done():
return
}
......@@ -138,5 +157,8 @@ func (s *Swarm) connHandler(c *ps.Conn) *Conn {
return nil
}
// if a peer dials us, remove from dial backoff.
s.backf.Clear(sc.RemotePeer())
return sc
}
......@@ -5,12 +5,12 @@ import (
peer "github.com/ipfs/go-libp2p/p2p/peer"
metrics "github.com/ipfs/go-libp2p/p2p/metrics"
inet "github.com/ipfs/go-libp2p/p2p/net"
metrics "github.com/ipfs/go-libp2p/util/metrics"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
"github.com/jbenet/goprocess"
context "golang.org/x/net/context"
)
// Network implements the inet.Network interface.
......
......@@ -5,7 +5,7 @@ import (
"testing"
"time"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
context "golang.org/x/net/context"
inet "github.com/ipfs/go-libp2p/p2p/net"
testutil "github.com/ipfs/go-libp2p/p2p/test/util"
)
......
......@@ -4,8 +4,8 @@ import (
"testing"
"time"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
inet "github.com/ipfs/go-libp2p/p2p/net"
peer "github.com/ipfs/go-libp2p/p2p/peer"
......
......@@ -3,7 +3,7 @@ package swarm
import (
inet "github.com/ipfs/go-libp2p/p2p/net"
ps "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream"
ps "github.com/jbenet/go-peerstream"
)
// a Stream is a wrapper around a ps.Stream that exposes a way to get
......
......@@ -9,13 +9,13 @@ import (
"testing"
"time"
testutil "github.com/ipfs/go-ipfs/util/testutil"
metrics "github.com/ipfs/go-libp2p/p2p/metrics"
inet "github.com/ipfs/go-libp2p/p2p/net"
peer "github.com/ipfs/go-libp2p/p2p/peer"
metrics "github.com/ipfs/go-libp2p/util/metrics"
testutil "util/testutil"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
ma "github.com/jbenet/go-multiaddr"
context "golang.org/x/net/context"
)
func EchoStreamHandler(stream inet.Stream) {
......@@ -237,6 +237,15 @@ func TestSwarm(t *testing.T) {
SubtestSwarm(t, swarms, msgs)
}
func TestBasicSwarm(t *testing.T) {
// t.Skip("skipping for another test")
t.Parallel()
msgs := 1
swarms := 2
SubtestSwarm(t, swarms, msgs)
}
func TestConnHandler(t *testing.T) {
// t.Skip("skipping for another test")
t.Parallel()
......
package conn
package transport
import (
"net"
"os"
"strings"
"syscall"
reuseport "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport"
reuseport "github.com/jbenet/go-reuseport"
)
// envReuseport is the env variable name used to turn off reuse port.
......@@ -30,6 +32,34 @@ func init() {
//
// If this becomes a sought after feature, we could add this to the config.
// In the end, reuseport is a stop-gap.
func reuseportIsAvailable() bool {
func ReuseportIsAvailable() bool {
return envReuseportVal && reuseport.Available()
}
// ReuseErrShouldRetry diagnoses whether to retry after a reuse error.
// if we failed to bind, we should retry. if bind worked and this is a
// real dial error (remote end didnt answer) then we should not retry.
func ReuseErrShouldRetry(err error) bool {
if err == nil {
return false // hey, it worked! no need to retry.
}
// if it's a network timeout error, it's a legitimate failure.
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return false
}
errno, ok := err.(syscall.Errno)
if !ok { // not an errno? who knows what this is. retry.
return true
}
switch errno {
case syscall.EADDRINUSE, syscall.EADDRNOTAVAIL:
return true // failure to bind. retry.
case syscall.ECONNREFUSED:
return false // real dial error
default:
return true // optimistically default to retry.
}
}
package transport
import (
"fmt"
"net"
"sync"
"time"
ma "github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-multiaddr-net"
reuseport "github.com/jbenet/go-reuseport"
context "golang.org/x/net/context"
lgbl "util/eventlog/loggables"
)
type TcpTransport struct {
dlock sync.Mutex
dialers map[string]Dialer
llock sync.Mutex
listeners map[string]Listener
}
func NewTCPTransport() *TcpTransport {
return &TcpTransport{
dialers: make(map[string]Dialer),
listeners: make(map[string]Listener),
}
}
func (t *TcpTransport) Dialer(laddr ma.Multiaddr, opts ...DialOpt) (Dialer, error) {
t.dlock.Lock()
defer t.dlock.Unlock()
s := laddr.String()
d, found := t.dialers[s]
if found {
return d, nil
}
var base manet.Dialer
var doReuse bool
for _, o := range opts {
switch o := o.(type) {
case TimeoutOpt:
base.Timeout = time.Duration(o)
case ReuseportOpt:
doReuse = bool(o)
default:
return nil, fmt.Errorf("unrecognized option: %#v", o)
}
}
tcpd, err := t.newTcpDialer(base, laddr, doReuse)
if err != nil {
return nil, err
}
t.dialers[s] = tcpd
return tcpd, nil
}
func (t *TcpTransport) Listen(laddr ma.Multiaddr) (Listener, error) {
t.llock.Lock()
defer t.llock.Unlock()
s := laddr.String()
l, found := t.listeners[s]
if found {
return l, nil
}
list, err := manetListen(laddr)
if err != nil {
return nil, err
}
tlist := &tcpListener{
list: list,
transport: t,
}
t.listeners[s] = tlist
return tlist, nil
}
func manetListen(addr ma.Multiaddr) (manet.Listener, error) {
network, naddr, err := manet.DialArgs(addr)
if err != nil {
return nil, err
}
if ReuseportIsAvailable() {
nl, err := reuseport.Listen(network, naddr)
if err == nil {
// hey, it worked!
return manet.WrapNetListener(nl)
}
// reuseport is available, but we failed to listen. log debug, and retry normally.
log.Debugf("reuseport available, but failed to listen: %s %s, %s", network, naddr, err)
}
// either reuseport not available, or it failed. try normally.
return manet.Listen(addr)
}
func (t *TcpTransport) Matches(a ma.Multiaddr) bool {
return IsTcpMultiaddr(a)
}
type tcpDialer struct {
laddr ma.Multiaddr
doReuse bool
rd reuseport.Dialer
madialer manet.Dialer
transport Transport
}
func (t *TcpTransport) newTcpDialer(base manet.Dialer, laddr ma.Multiaddr, doReuse bool) (*tcpDialer, error) {
// get the local net.Addr manually
la, err := manet.ToNetAddr(laddr)
if err != nil {
return nil, err // something wrong with laddr.
}
if doReuse && ReuseportIsAvailable() {
rd := reuseport.Dialer{
D: net.Dialer{
LocalAddr: la,
Timeout: base.Timeout,
},
}
return &tcpDialer{
doReuse: true,
laddr: laddr,
rd: rd,
madialer: base,
transport: t,
}, nil
}
return &tcpDialer{
doReuse: false,
laddr: laddr,
madialer: base,
transport: t,
}, nil
}
func (d *tcpDialer) Dial(raddr ma.Multiaddr) (Conn, error) {
var c manet.Conn
var err error
if d.doReuse {
c, err = d.reuseDial(raddr)
} else {
c, err = d.madialer.Dial(raddr)
}
if err != nil {
return nil, err
}
return &connWrap{
Conn: c,
transport: d.transport,
}, nil
}
func (d *tcpDialer) reuseDial(raddr ma.Multiaddr) (manet.Conn, error) {
logdial := lgbl.Dial("conn", "", "", d.laddr, raddr)
rpev := log.EventBegin(context.TODO(), "tptDialReusePort", logdial)
network, netraddr, err := manet.DialArgs(raddr)
if err != nil {
return nil, err
}
con, err := d.rd.Dial(network, netraddr)
if err == nil {
logdial["reuseport"] = "success"
rpev.Done()
return manet.WrapNetConn(con)
}
if !ReuseErrShouldRetry(err) {
logdial["reuseport"] = "failure"
logdial["error"] = err
rpev.Done()
return nil, err
}
logdial["reuseport"] = "retry"
logdial["error"] = err
rpev.Done()
return d.madialer.Dial(raddr)
}
func (d *tcpDialer) Matches(a ma.Multiaddr) bool {
return IsTcpMultiaddr(a)
}
type tcpListener struct {
list manet.Listener
transport Transport
}
func (d *tcpListener) Accept() (Conn, error) {
c, err := d.list.Accept()
if err != nil {
return nil, err
}
return &connWrap{
Conn: c,
transport: d.transport,
}, nil
}
func (d *tcpListener) Addr() net.Addr {
return d.list.Addr()
}
func (t *tcpListener) Multiaddr() ma.Multiaddr {
return t.list.Multiaddr()
}
func (t *tcpListener) NetListener() net.Listener {
return t.list.NetListener()
}
func (d *tcpListener) Close() error {
return d.list.Close()
}
package transport
import (
"net"
"time"
logging "QmWRypnfEwrgH4k93KEHN5hng7VjKYkWmzDYRuTZeh2Mgh/go-log"
ma "github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-multiaddr-net"
)
var log = logging.Logger("transport")
type Conn interface {
manet.Conn
Transport() Transport
}
type Transport interface {
Dialer(laddr ma.Multiaddr, opts ...DialOpt) (Dialer, error)
Listen(laddr ma.Multiaddr) (Listener, error)
Matches(ma.Multiaddr) bool
}
type Dialer interface {
Dial(raddr ma.Multiaddr) (Conn, error)
Matches(ma.Multiaddr) bool
}
type Listener interface {
Accept() (Conn, error)
Close() error
Addr() net.Addr
Multiaddr() ma.Multiaddr
}
type connWrap struct {
manet.Conn
transport Transport
}
func (cw *connWrap) Transport() Transport {
return cw.transport
}
type DialOpt interface{}
type TimeoutOpt time.Duration
type ReuseportOpt bool
var ReusePorts ReuseportOpt = true
func IsTcpMultiaddr(a ma.Multiaddr) bool {
p := a.Protocols()
return len(p) == 2 && (p[0].Name == "ip4" || p[0].Name == "ip6") && p[1].Name == "tcp"
}
func IsUtpMultiaddr(a ma.Multiaddr) bool {
p := a.Protocols()
return len(p) == 3 && p[2].Name == "utp"
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment