diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index c50551f27bbf86cf55e9f4b45e76ce0822ded897..a49282949e69bf7089bef3d7fcfe07bc45ed88fa 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -6,6 +6,7 @@ import ( "net/http" _ "net/http/pprof" "os" + "sort" "strings" "sync" @@ -18,6 +19,7 @@ import ( commands "github.com/ipfs/go-ipfs/core/commands" corehttp "github.com/ipfs/go-ipfs/core/corehttp" "github.com/ipfs/go-ipfs/core/corerouting" + conn "github.com/ipfs/go-ipfs/p2p/net/conn" peer "github.com/ipfs/go-ipfs/p2p/peer" fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo" util "github.com/ipfs/go-ipfs/util" @@ -31,7 +33,8 @@ const ( writableKwd = "writable" ipfsMountKwd = "mount-ipfs" ipnsMountKwd = "mount-ipns" - unrestrictedApiAccess = "unrestricted-api" + unrestrictedApiAccessKwd = "unrestricted-api" + unencryptTransportKwd = "disable-transport-encryption" // apiAddrKwd = "address-api" // swarmAddrKwd = "address-swarm" ) @@ -75,7 +78,8 @@ the port as you would other services or database (firewall, authenticated proxy, cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"), cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount)"), cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount)"), - cmds.BoolOption(unrestrictedApiAccess, "Allow API access to unlisted hashes"), + cmds.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes"), + cmds.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)"), // TODO: add way to override addresses. tricky part: updating the config if also --init. // cmds.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"), @@ -109,6 +113,14 @@ func daemonFunc(req cmds.Request, res cmds.Response) { } }() + // check transport encryption flag. + unencrypted, _, _ := req.Option(unencryptTransportKwd).Bool() + if unencrypted { + log.Warningf(`Running with --%s: All connections are UNENCRYPTED. + You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd) + conn.EncryptConnections = false + } + // first, whether user has provided the initialization flag. we may be // running in an uninitialized state. initialize, _, err := req.Option(initOptionKwd).Bool() @@ -179,6 +191,8 @@ func daemonFunc(req cmds.Request, res cmds.Response) { return } + printSwarmAddrs(node) + defer func() { // We wait for the node to close first, as the node has children // that it will wait for before closing, such as the API server. @@ -256,9 +270,9 @@ func serveHTTPApi(req cmds.Request) (error, <-chan error) { apiMaddr = apiLis.Multiaddr() fmt.Printf("API server listening on %s\n", apiMaddr) - unrestricted, _, err := req.Option(unrestrictedApiAccess).Bool() + unrestricted, _, err := req.Option(unrestrictedApiAccessKwd).Bool() if err != nil { - return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccess, err), nil + return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil } apiGw := corehttp.NewGateway(corehttp.GatewayConfig{ @@ -305,6 +319,19 @@ func serveHTTPApi(req cmds.Request) (error, <-chan error) { return nil, errc } +// printSwarmAddrs prints the addresses of the host +func printSwarmAddrs(node *core.IpfsNode) { + var addrs []string + for _, addr := range node.PeerHost.Addrs() { + addrs = append(addrs, addr.String()) + } + sort.Sort(sort.StringSlice(addrs)) + + for _, addr := range addrs { + fmt.Printf("Swarm listening on %s\n", addr) + } +} + // serveHTTPGateway collects options, creates listener, prints status message and starts serving requests func serveHTTPGateway(req cmds.Request) (error, <-chan error) { cfg, err := req.Context().GetConfig() diff --git a/core/commands/id.go b/core/commands/id.go index ea5e4cd549a02b784a972a23f8f4ab29fb80ce66..31af08067735fe8317fc01805e8bf2bd0449035d 100644 --- a/core/commands/id.go +++ b/core/commands/id.go @@ -46,6 +46,7 @@ ipfs id supports the format option for output with the following keys: <aver>: agent version <pver>: protocol version <pubkey>: public key +<addrs>: addresses (newline delimited) `, }, Arguments: []cmds.Argument{ @@ -119,6 +120,9 @@ ipfs id supports the format option for output with the following keys: output = strings.Replace(output, "<aver>", val.AgentVersion, -1) output = strings.Replace(output, "<pver>", val.ProtocolVersion, -1) output = strings.Replace(output, "<pubkey>", val.PublicKey, -1) + output = strings.Replace(output, "<addrs>", strings.Join(val.Addresses, "\n"), -1) + output = strings.Replace(output, "\\n", "\n", -1) + output = strings.Replace(output, "\\t", "\t", -1) return strings.NewReader(output), nil } else { diff --git a/core/commands/swarm.go b/core/commands/swarm.go index bd7b943243a461c652256fbde64e68875a30a4cf..efb6fc1e7a6083110032b960fb91e9c0b7b9fbc7 100644 --- a/core/commands/swarm.go +++ b/core/commands/swarm.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "path" "sort" cmds "github.com/ipfs/go-ipfs/commands" @@ -91,6 +92,9 @@ var swarmAddrsCmd = &cmds.Command{ ipfs swarm addrs lists all addresses this node is aware of. `, }, + Subcommands: map[string]*cmds.Command{ + "local": swarmAddrsLocalCmd, + }, Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() @@ -144,6 +148,50 @@ ipfs swarm addrs lists all addresses this node is aware of. Type: addrMap{}, } +var swarmAddrsLocalCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "List local addresses.", + ShortDescription: ` +ipfs swarm addrs local lists all local addresses the node is listening on. +`, + }, + Options: []cmds.Option{ + cmds.BoolOption("id", "Show peer ID in addresses"), + }, + Run: func(req cmds.Request, res cmds.Response) { + + n, err := req.Context().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + if n.PeerHost == nil { + res.SetError(errNotOnline, cmds.ErrClient) + return + } + + showid, _, _ := req.Option("id").Bool() + id := n.Identity.Pretty() + + var addrs []string + for _, addr := range n.PeerHost.Addrs() { + saddr := addr.String() + if showid { + saddr = path.Join(saddr, "ipfs", id) + } + addrs = append(addrs, saddr) + } + sort.Sort(sort.StringSlice(addrs)) + + res.SetOutput(&stringList{addrs}) + }, + Type: stringList{}, + Marshalers: cmds.MarshalerMap{ + cmds.Text: stringListMarshaler, + }, +} + var swarmConnectCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Open connection to a given address", diff --git a/p2p/net/conn/dial.go b/p2p/net/conn/dial.go index 43831c3efef74c759575b4fbb24d30f3d46c4ae1..a9a1a7aaf939a33be42a252addcd27e00ae9d68b 100644 --- a/p2p/net/conn/dial.go +++ b/p2p/net/conn/dial.go @@ -60,7 +60,7 @@ func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) ( return } - if d.PrivateKey == nil { + if d.PrivateKey == nil || EncryptConnections == false { log.Warning("dialer %s dialing INSECURELY %s at %s!", d, remote, raddr) connOut = c return diff --git a/p2p/net/conn/interface.go b/p2p/net/conn/interface.go index 3a61911af82250e2dceb4f7b58af522d5d10a0f2..82008593057dd919124472696b270b43078e377d 100644 --- a/p2p/net/conn/interface.go +++ b/p2p/net/conn/interface.go @@ -93,3 +93,11 @@ type Listener interface { // Any blocked Accept operations will be unblocked and return errors. Close() error } + +// EncryptConnections is a global parameter because it should either be +// enabled or _completely disabled_. I.e. a node should only be able to talk +// to proper (encrypted) networks if it is encrypting all its transports. +// Running a node with disabled transport encryption is useful to debug the +// protocols, achieve implementation interop, or for private networks which +// -- for whatever reason -- _must_ run unencrypted. +var EncryptConnections = true diff --git a/p2p/net/conn/listen.go b/p2p/net/conn/listen.go index ea91e5a56d4ccfaf2abc606bdb9ece3876c1a43b..71b89d76703eddb30e86aa8553e37be970717436 100644 --- a/p2p/net/conn/listen.go +++ b/p2p/net/conn/listen.go @@ -107,7 +107,7 @@ func (l *listener) Accept() (net.Conn, error) { return nil, err } - if l.privk == nil { + if l.privk == nil || EncryptConnections == false { log.Warning("listener %s listening INSECURELY!", l) return c, nil } diff --git a/p2p/net/mock/mock_notif_test.go b/p2p/net/mock/mock_notif_test.go index 1ef9fc6aaf3c681e686b6c4f54fb9909ffe99b02..d91403f8b2c76f2c95032b4495b0c046a013f53c 100644 --- a/p2p/net/mock/mock_notif_test.go +++ b/p2p/net/mock/mock_notif_test.go @@ -43,24 +43,30 @@ func TestNotifications(t *testing.T) { for i, s := range nets { n := notifiees[i] for _, s2 := range nets { - cos := s.ConnsToPeer(s2.LocalPeer()) - func() { - for i := 0; i < len(cos); i++ { - var c inet.Conn - select { - case c = <-n.connected: - case <-time.After(timeout): - t.Fatal("timeout") - } - for _, c2 := range cos { - if c == c2 { - t.Log("got notif for conn") - return - } + var actual []inet.Conn + for len(s.ConnsToPeer(s2.LocalPeer())) != len(actual) { + select { + case c := <-n.connected: + actual = append(actual, c) + case <-time.After(timeout): + t.Fatal("timeout") + } + } + + expect := s.ConnsToPeer(s2.LocalPeer()) + for _, c1 := range actual { + found := false + for _, c2 := range expect { + if c1 == c2 { + found = true + break } + } + if !found { t.Error("connection not found") } - }() + } + } } diff --git a/p2p/net/swarm/swarm_notif_test.go b/p2p/net/swarm/swarm_notif_test.go index d8b9bc2d2b78ffafd952aaa15a45917109ca0d86..b44b030d80fc16a579467906451891bcf299f867 100644 --- a/p2p/net/swarm/swarm_notif_test.go +++ b/p2p/net/swarm/swarm_notif_test.go @@ -11,8 +11,6 @@ import ( ) func TestNotifications(t *testing.T) { - t.Parallel() - ctx := context.Background() swarms := makeSwarms(ctx, t, 5) defer func() { @@ -44,24 +42,30 @@ func TestNotifications(t *testing.T) { continue } - cos := s.ConnectionsToPeer(s2.LocalPeer()) - func() { - for i := 0; i < len(cos); i++ { - var c inet.Conn - select { - case c = <-n.connected: - case <-time.After(timeout): - t.Fatal("timeout") - } - for _, c2 := range cos { - if c == c2 { - t.Log("got notif for conn", c) - return - } + var actual []inet.Conn + for len(s.ConnectionsToPeer(s2.LocalPeer())) != len(actual) { + select { + case c := <-n.connected: + actual = append(actual, c) + case <-time.After(timeout): + t.Fatal("timeout") + } + } + + expect := s.ConnectionsToPeer(s2.LocalPeer()) + for _, c1 := range actual { + found := false + for _, c2 := range expect { + if c1 == c2 { + found = true + break } - t.Error("connection not found", c) } - }() + if !found { + t.Error("connection not found") + } + } + } } diff --git a/p2p/net/swarm/swarm_test.go b/p2p/net/swarm/swarm_test.go index 4c12a1b5d3b4ac42950164babc8ca02ee0e85732..6e53cc339ed696579f02675ea986fc067006864e 100644 --- a/p2p/net/swarm/swarm_test.go +++ b/p2p/net/swarm/swarm_test.go @@ -325,6 +325,6 @@ func TestFilterBounds(t *testing.T) { case <-time.After(time.Second): t.Fatal("should have gotten connection") case <-conns: - fmt.Println("got connect") + t.Log("got connect") } } diff --git a/test/sharness/lib/test-lib.sh b/test/sharness/lib/test-lib.sh index 73b55b343c8a37031a91eddcd797d56000395757..e257ba1e5c8a8c251c696cd7cf86fe240ddd3caf 100644 --- a/test/sharness/lib/test-lib.sh +++ b/test/sharness/lib/test-lib.sh @@ -193,8 +193,10 @@ test_config_ipfs_gateway_writable() { test_launch_ipfs_daemon() { + args=$1 + test_expect_success "'ipfs daemon' succeeds" ' - ipfs daemon >actual_daemon 2>daemon_err & + ipfs daemon $args >actual_daemon 2>daemon_err & ' # we say the daemon is ready when the API server is ready. diff --git a/test/sharness/t0060-daemon.sh b/test/sharness/t0060-daemon.sh index e0aff7b5edf6b8bab7fe6ee45b7cb1ab30042cea..13a56fc1c28162f879d36a927d6dfc5049aa4a86 100755 --- a/test/sharness/t0060-daemon.sh +++ b/test/sharness/t0060-daemon.sh @@ -8,6 +8,8 @@ test_description="Test daemon command" . lib/test-lib.sh +# TODO: randomize ports here once we add --config to ipfs daemon + # this needs to be in a different test than "ipfs daemon --init" below test_expect_success "setup IPFS_PATH" ' IPFS_PATH="$(pwd)/.ipfs" && @@ -17,7 +19,7 @@ test_expect_success "setup IPFS_PATH" ' # NOTE: this should remove bootstrap peers (needs a flag) # TODO(cryptix): # - we won't see daemon startup failure because we put the daemon in the background - fix: fork with exit code after api listen -# - also default ports: might clash with local clients. Failure in that case isn't clear as well because pollEndpoint just uses the already running node +# - also default ports: might clash with local clients. Failure in that case isn't clear as well because pollEndpoint just uses the already running node test_expect_success "ipfs daemon --init launches" ' ipfs daemon --init >actual_daemon 2>daemon_err & ' @@ -34,6 +36,11 @@ test_expect_success "'ipfs config Identity.PeerID' works" ' ipfs config Identity.PeerID >config_peerId ' +test_expect_success "'ipfs swarm addrs local' works" ' + ipfs swarm addrs local >local_addrs +' + + # this is lifted straight from t0020-init.sh test_expect_success "ipfs peer id looks good" ' PEERID=$(cat config_peerId) && @@ -58,6 +65,7 @@ test_expect_success "ipfs daemon output looks good" ' echo "peer identity: $PEERID" >>expected_daemon && echo "to get started, enter:" >>expected_daemon && printf "\\n\\t$STARTFILE\\n\\n" >>expected_daemon && + cat local_addrs | sed "s/^/Swarm listening on /" >>expected_daemon && echo "API server listening on /ip4/127.0.0.1/tcp/5001" >>expected_daemon && echo "Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080" >>expected_daemon && test_cmp expected_daemon actual_daemon @@ -91,6 +99,15 @@ test_expect_success "ipfs help output looks good" ' test_fsh cat help.txt ' +# check transport is encrypted + +test_expect_success 'transport should be encrypted' ' + nc -w 5 localhost 4001 >swarmnc && + grep -q "AES-256,AES-128" swarmnc && + test_must_fail grep -q "/ipfs/identify" swarmnc || + test_fsh cat swarmnc +' + # end same as in t0010 test_expect_success "daemon is still running" ' diff --git a/test/sharness/t0061-daemon-opts.sh b/test/sharness/t0061-daemon-opts.sh new file mode 100755 index 0000000000000000000000000000000000000000..549fa3e8fd3925a6120687045134d67dbe9edc70 --- /dev/null +++ b/test/sharness/t0061-daemon-opts.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2014 Juan Batiz-Benet +# MIT Licensed; see the LICENSE file in this repository. +# + +test_description="Test daemon command" + +. lib/test-lib.sh + + +test_init_ipfs + +test_launch_ipfs_daemon '--unrestricted-api --disable-transport-encryption' + +gwyport=$PORT_GWAY +apiport=$PORT_API + +test_expect_success 'api gateway should be unrestricted' ' + echo "hello mars :$gwyport :$apiport" >expected && + HASH=$(ipfs add -q expected) && + curl -sfo actual1 "http://127.0.0.1:$gwyport/ipfs/$HASH" && + curl -sfo actual2 "http://127.0.0.1:$apiport/ipfs/$HASH" && + test_cmp expected actual1 && + test_cmp expected actual2 +' + +# Odd. this fails here, but the inverse works on t0060-daemon. +test_expect_success 'transport should be unencrypted' ' + go-sleep 0.5s | nc localhost "$PORT_SWARM" >swarmnc && + test_must_fail grep -q "AES-256,AES-128" swarmnc && + grep -q "/ipfs/identify" swarmnc || + test_fsh cat swarmnc +' + +test_kill_ipfs_daemon + +test_done diff --git a/test/sharness/t0110-gateway.sh b/test/sharness/t0110-gateway.sh index 36a94c671c1eea9bdea19df3b41382cf4fd479e0..fbaa4cd2e7a96bb285753f670080bdf771673c32 100755 --- a/test/sharness/t0110-gateway.sh +++ b/test/sharness/t0110-gateway.sh @@ -33,6 +33,10 @@ test_expect_success "GET IPFS path output looks good" ' rm actual ' +test_expect_success "GET IPFS path on API forbidden" ' + test_curl_resp_http_code "http://127.0.0.1:$apiport/ipfs/$HASH" "HTTP/1.1 403 Forbidden" +' + test_expect_success "GET IPFS directory path succeeds" ' mkdir dir && echo "12345" >dir/test && diff --git a/test/sharness/t0140-swarm.sh b/test/sharness/t0140-swarm.sh new file mode 100755 index 0000000000000000000000000000000000000000..229ba1676bb867cb2c1fcb77104b7c6729374c25 --- /dev/null +++ b/test/sharness/t0140-swarm.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Copyright (c) 2014 Jeromy Johnson +# MIT Licensed; see the LICENSE file in this repository. +# + +test_description="Test ipfs swarm command" + +. lib/test-lib.sh + +test_init_ipfs + +test_launch_ipfs_daemon + +test_expect_success 'disconnected: peers is empty' ' + ipfs swarm peers >actual && + test_must_be_empty actual +' + +test_expect_success 'disconnected: addrs local has localhost' ' + ipfs swarm addrs local >actual && + grep "/ip4/127.0.0.1" actual +' + +test_expect_success 'disconnected: addrs local matches ipfs id' ' + ipfs id -f="<addrs>\\n" | sort >expected && + ipfs swarm addrs local --id | sort >actual && + test_cmp expected actual +' + +test_kill_ipfs_daemon + +test_done