diff --git a/cmd/ipfs/init.go b/cmd/ipfs/init.go index fc0654d7b..fc4563f4e 100644 --- a/cmd/ipfs/init.go +++ b/cmd/ipfs/init.go @@ -12,6 +12,7 @@ import ( cmds "github.com/jbenet/go-ipfs/commands" config "github.com/jbenet/go-ipfs/config" core "github.com/jbenet/go-ipfs/core" + corecmds "github.com/jbenet/go-ipfs/core/commands" imp "github.com/jbenet/go-ipfs/importer" chunk "github.com/jbenet/go-ipfs/importer/chunk" ci "github.com/jbenet/go-ipfs/p2p/crypto" @@ -179,6 +180,11 @@ func initConfig(configFilename string, dspathOverride string, nBitsForKeypair in return nil, err } + bootstrapPeers, err := corecmds.DefaultBootstrapPeers() + if err != nil { + return nil, err + } + conf := &config.Config{ // setup the node's default addresses. @@ -191,39 +197,10 @@ func initConfig(configFilename string, dspathOverride string, nBitsForKeypair in API: "/ip4/127.0.0.1/tcp/5001", }, - Bootstrap: []*config.BootstrapPeer{ - &config.BootstrapPeer{ // Use these hardcoded bootstrap peers for now. - // mars.i.ipfs.io - PeerID: "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - Address: "/ip4/104.131.131.82/tcp/4001", - }, - &config.BootstrapPeer{ - // Neptune - PeerID: "QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z", - Address: "/ip4/104.236.176.52/tcp/4001", - }, - &config.BootstrapPeer{ - // Pluto - PeerID: "QmSoLpPVmHKQ4XTPdz8tjDFgdeRFkpV8JgYq8JVJ69RrZm", - Address: "/ip4/104.236.179.241/tcp/4001", - }, - &config.BootstrapPeer{ - // Uranus - PeerID: "QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm", - Address: "/ip4/162.243.248.213/tcp/4001", - }, - &config.BootstrapPeer{ - // Saturn - PeerID: "QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu", - Address: "/ip4/128.199.219.111/tcp/4001", - }, - }, - + Bootstrap: bootstrapPeers, Datastore: ds, - - Logs: logConfig, - - Identity: identity, + Logs: logConfig, + Identity: identity, // setup the node mount points. Mounts: config.Mounts{ diff --git a/config/config.go b/config/config.go index ed8e66b21..3aca889ef 100644 --- a/config/config.go +++ b/config/config.go @@ -3,8 +3,13 @@ package config import ( "encoding/base64" + "errors" "os" "path/filepath" + "strings" + + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" ic "github.com/jbenet/go-ipfs/p2p/crypto" u "github.com/jbenet/go-ipfs/util" @@ -55,6 +60,49 @@ func (bp *BootstrapPeer) String() string { return bp.Address + "/" + bp.PeerID } +func ParseBootstrapPeer(addr string) (BootstrapPeer, error) { + // to be replaced with just multiaddr parsing, once ptp is a multiaddr protocol + idx := strings.LastIndex(addr, "/") + if idx == -1 { + return BootstrapPeer{}, errors.New("invalid address") + } + addrS := addr[:idx] + peeridS := addr[idx+1:] + + // make sure addrS parses as a multiaddr. + if len(addrS) > 0 { + maddr, err := ma.NewMultiaddr(addrS) + if err != nil { + return BootstrapPeer{}, err + } + + addrS = maddr.String() + } + + // make sure idS parses as a peer.ID + _, err := mh.FromB58String(peeridS) + if err != nil { + return BootstrapPeer{}, err + } + + return BootstrapPeer{ + Address: addrS, + PeerID: peeridS, + }, nil +} + +func ParseBootstrapPeers(addrs []string) ([]BootstrapPeer, error) { + peers := make([]BootstrapPeer, len(addrs)) + var err error + for i, addr := range addrs { + peers[i], err = ParseBootstrapPeer(addr) + if err != nil { + return nil, err + } + } + return peers, nil +} + // Tour stores the ipfs tour read-list and resume point type Tour struct { Last string // last tour topic read @@ -63,14 +111,14 @@ type Tour struct { // Config is used to load IPFS config files. type Config struct { - Identity Identity // local node's peer identity - Datastore Datastore // local node's storage - Addresses Addresses // local node's addresses - Mounts Mounts // local node's mount points - Version Version // local node's version management - Bootstrap []*BootstrapPeer // local nodes's bootstrap peers - Tour Tour // local node's tour position - Logs Logs // local node's event log configuration + Identity Identity // local node's peer identity + Datastore Datastore // local node's storage + Addresses Addresses // local node's addresses + Mounts Mounts // local node's mount points + Version Version // local node's version management + Bootstrap []BootstrapPeer // local nodes's bootstrap peers + Tour Tour // local node's tour position + Logs Logs // local node's event log configuration } // DefaultPathRoot is the path to the default config dir location. diff --git a/core/bootstrap.go b/core/bootstrap.go index 2da57fb39..342902f41 100644 --- a/core/bootstrap.go +++ b/core/bootstrap.go @@ -29,7 +29,7 @@ func superviseConnections(parent context.Context, h host.Host, route *dht.IpfsDHT, // TODO depend on abstract interface for testing purposes store peer.Peerstore, - peers []*config.BootstrapPeer) error { + peers []config.BootstrapPeer) error { for { ctx, _ := context.WithTimeout(parent, connectiontimeout) @@ -51,7 +51,7 @@ func bootstrap(ctx context.Context, h host.Host, r *dht.IpfsDHT, ps peer.Peerstore, - boots []*config.BootstrapPeer) error { + boots []config.BootstrapPeer) error { connectedPeers := h.Network().Peers() if len(connectedPeers) >= recoveryThreshold { @@ -137,7 +137,7 @@ func connect(ctx context.Context, ps peer.Peerstore, r *dht.IpfsDHT, peers []pee return nil } -func toPeer(bootstrap *config.BootstrapPeer) (p peer.PeerInfo, err error) { +func toPeer(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) { id, err := peer.IDB58Decode(bootstrap.PeerID) if err != nil { return diff --git a/core/commands/bootstrap.go b/core/commands/bootstrap.go index 10c189af3..0be6a1295 100644 --- a/core/commands/bootstrap.go +++ b/core/commands/bootstrap.go @@ -3,18 +3,29 @@ package commands import ( "bytes" "io" - "strings" - - ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" - mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" cmds "github.com/jbenet/go-ipfs/commands" config "github.com/jbenet/go-ipfs/config" u "github.com/jbenet/go-ipfs/util" + errors "github.com/jbenet/go-ipfs/util/debugerror" ) +// DefaultBootstrapAddresses are the hardcoded bootstrap addresses +// for ipfs. they are nodes run by the ipfs team. docs on these later. +// As with all p2p networks, bootstrap is an important security concern. +// +// Note: this is here -- and not inside cmd/ipfs/init.go -- because of an +// import dependency issue. TODO: move this into a config/default/ package. +var DefaultBootstrapAddresses = []string{ + "/ip4/104.131.131.82/tcp/4001/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", // mars.i.ipfs.io + "/ip4/104.236.176.52/tcp/4001/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z", // neptune (to be neptune.i.ipfs.io) + "/ip4/104.236.179.241/tcp/4001/QmSoLpPVmHKQ4XTPdz8tjDFgdeRFkpV8JgYq8JVJ69RrZm", // pluto (to be pluto.i.ipfs.io) + "/ip4/162.243.248.213/tcp/4001/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm", // uranus (to be uranus.i.ipfs.io) + "/ip4/128.199.219.111/tcp/4001/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu", // saturn (to be saturn.i.ipfs.io) +} + type BootstrapOutput struct { - Peers []*config.BootstrapPeer + Peers []config.BootstrapPeer } var peerOptionDesc = "A peer to add to the bootstrap list (in the format '/')" @@ -52,10 +63,15 @@ in the bootstrap list). }, Arguments: []cmds.Argument{ - cmds.StringArg("peer", true, true, peerOptionDesc), + cmds.StringArg("peer", false, true, peerOptionDesc), }, + + Options: []cmds.Option{ + cmds.BoolOption("default", "add default bootstrap nodes"), + }, + Run: func(req cmds.Request) (interface{}, error) { - input, err := bootstrapInputToPeers(req.Arguments()) + inputPeers, err := config.ParseBootstrapPeers(req.Arguments()) if err != nil { return nil, err } @@ -70,11 +86,30 @@ in the bootstrap list). return nil, err } - added, err := bootstrapAdd(filename, cfg, input) + deflt, _, err := req.Option("default").Bool() if err != nil { return nil, err } + if deflt { + // parse separately for meaningful, correct error. + defltPeers, err := DefaultBootstrapPeers() + if err != nil { + return nil, err + } + + inputPeers = append(inputPeers, defltPeers...) + } + + added, err := bootstrapAdd(filename, cfg, inputPeers) + if err != nil { + return nil, err + } + + if len(inputPeers) == 0 { + return nil, cmds.ClientError("no bootstrap peers to add") + } + return &BootstrapOutput{added}, nil }, Type: &BootstrapOutput{}, @@ -106,7 +141,7 @@ var bootstrapRemoveCmd = &cmds.Command{ cmds.BoolOption("all", "Remove all bootstrap peers."), }, Run: func(req cmds.Request) (interface{}, error) { - input, err := bootstrapInputToPeers(req.Arguments()) + input, err := config.ParseBootstrapPeers(req.Arguments()) if err != nil { return nil, err } @@ -126,7 +161,7 @@ var bootstrapRemoveCmd = &cmds.Command{ return nil, err } - var removed []*config.BootstrapPeer + var removed []config.BootstrapPeer if all { removed, err = bootstrapRemoveAll(filename, cfg) } else { @@ -185,7 +220,7 @@ func bootstrapMarshaler(res cmds.Response) ([]byte, error) { return buf.Bytes(), err } -func bootstrapWritePeers(w io.Writer, prefix string, peers []*config.BootstrapPeer) error { +func bootstrapWritePeers(w io.Writer, prefix string, peers []config.BootstrapPeer) error { for _, peer := range peers { s := prefix + peer.Address + "/" + peer.PeerID + "\n" @@ -197,46 +232,8 @@ func bootstrapWritePeers(w io.Writer, prefix string, peers []*config.BootstrapPe return nil } -func bootstrapInputToPeers(input []string) ([]*config.BootstrapPeer, error) { - split := func(addr string) (string, string) { - idx := strings.LastIndex(addr, "/") - if idx == -1 { - return "", addr - } - return addr[:idx], addr[idx+1:] - } - - peers := []*config.BootstrapPeer{} - for _, addr := range input { - addrS, peeridS := split(addr) - - // make sure addrS parses as a multiaddr. - if len(addrS) > 0 { - maddr, err := ma.NewMultiaddr(addrS) - if err != nil { - return nil, err - } - - addrS = maddr.String() - } - - // make sure idS parses as a peer.ID - _, err := mh.FromB58String(peeridS) - if err != nil { - return nil, err - } - - // construct config entry - peers = append(peers, &config.BootstrapPeer{ - Address: addrS, - PeerID: peeridS, - }) - } - return peers, nil -} - -func bootstrapAdd(filename string, cfg *config.Config, peers []*config.BootstrapPeer) ([]*config.BootstrapPeer, error) { - added := make([]*config.BootstrapPeer, 0, len(peers)) +func bootstrapAdd(filename string, cfg *config.Config, peers []config.BootstrapPeer) ([]config.BootstrapPeer, error) { + added := make([]config.BootstrapPeer, 0, len(peers)) for _, peer := range peers { duplicate := false @@ -261,9 +258,9 @@ func bootstrapAdd(filename string, cfg *config.Config, peers []*config.Bootstrap return added, nil } -func bootstrapRemove(filename string, cfg *config.Config, toRemove []*config.BootstrapPeer) ([]*config.BootstrapPeer, error) { - removed := make([]*config.BootstrapPeer, 0, len(toRemove)) - keep := make([]*config.BootstrapPeer, 0, len(cfg.Bootstrap)) +func bootstrapRemove(filename string, cfg *config.Config, toRemove []config.BootstrapPeer) ([]config.BootstrapPeer, error) { + removed := make([]config.BootstrapPeer, 0, len(toRemove)) + keep := make([]config.BootstrapPeer, 0, len(cfg.Bootstrap)) for _, peer := range cfg.Bootstrap { found := false @@ -289,8 +286,8 @@ func bootstrapRemove(filename string, cfg *config.Config, toRemove []*config.Boo return removed, nil } -func bootstrapRemoveAll(filename string, cfg *config.Config) ([]*config.BootstrapPeer, error) { - removed := make([]*config.BootstrapPeer, len(cfg.Bootstrap)) +func bootstrapRemoveAll(filename string, cfg *config.Config) ([]config.BootstrapPeer, error) { + removed := make([]config.BootstrapPeer, len(cfg.Bootstrap)) copy(removed, cfg.Bootstrap) cfg.Bootstrap = nil @@ -302,6 +299,18 @@ func bootstrapRemoveAll(filename string, cfg *config.Config) ([]*config.Bootstra return removed, nil } +// DefaultBootstrapPeers returns the (parsed) set of default bootstrap peers. +// if it fails, it returns a meaningful error for the user. +// This is here (and not inside cmd/ipfs/init) because of module dependency problems. +func DefaultBootstrapPeers() ([]config.BootstrapPeer, error) { + ps, err := config.ParseBootstrapPeers(DefaultBootstrapAddresses) + if err != nil { + return nil, errors.Errorf(`failed to parse hardcoded bootstrap peers: %s +This is a problem with the ipfs codebase. Please report it to the dev team.`, err) + } + return ps, nil +} + const bootstrapSecurityWarning = ` SECURITY WARNING: