bootstrap.go 8.69 KB
Newer Older
1 2 3
package commands

import (
4 5
	"bytes"
	"io"
6 7

	cmds "github.com/jbenet/go-ipfs/commands"
8
	repo "github.com/jbenet/go-ipfs/repo"
9
	config "github.com/jbenet/go-ipfs/repo/config"
10
	"github.com/jbenet/go-ipfs/repo/fsrepo"
11
	u "github.com/jbenet/go-ipfs/util"
12
	errors "github.com/jbenet/go-ipfs/util/debugerror"
13 14
)

15 16 17 18 19 20 21 22 23 24 25 26
// 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)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
27 28 29 30
	"/ip4/104.236.76.40/tcp/4001/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64",   // venus (to be venus.i.ipfs.io)
	"/ip4/178.62.158.247/tcp/4001/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd",  // earth (to be earth.i.ipfs.io)
	"/ip4/178.62.61.185/tcp/4001/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3",   // mercury (to be mercury.i.ipfs.io)
	"/ip4/104.236.151.122/tcp/4001/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx", // jupiter (to be jupiter.i.ipfs.io)
31 32
}

33
type BootstrapOutput struct {
34
	Peers []config.BootstrapPeer
35 36
}

37
var peerOptionDesc = "A peer to add to the bootstrap list (in the format '<multiaddr>/<peerID>')"
38

39
var BootstrapCmd = &cmds.Command{
40 41
	Helptext: cmds.HelpText{
		Tagline: "Show or edit the list of bootstrap peers",
42
		Synopsis: `
43 44
ipfs bootstrap list             - Show peers in the bootstrap list
ipfs bootstrap add <peer>...    - Add peers to the bootstrap list
45
ipfs bootstrap rm <peer>... - Removes peers from the bootstrap list
46 47 48
`,
		ShortDescription: `
Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'.
49
` + bootstrapSecurityWarning,
50
	},
51

52 53 54
	Run:        bootstrapListCmd.Run,
	Marshalers: bootstrapListCmd.Marshalers,
	Type:       bootstrapListCmd.Type,
55

56
	Subcommands: map[string]*cmds.Command{
57 58 59
		"list": bootstrapListCmd,
		"add":  bootstrapAddCmd,
		"rm":   bootstrapRemoveCmd,
60 61 62 63
	},
}

var bootstrapAddCmd = &cmds.Command{
64 65 66
	Helptext: cmds.HelpText{
		Tagline: "Add peers to the bootstrap list",
		ShortDescription: `Outputs a list of peers that were added (that weren't already
67 68
in the bootstrap list).
` + bootstrapSecurityWarning,
69
	},
70

71
	Arguments: []cmds.Argument{
72
		cmds.StringArg("peer", false, true, peerOptionDesc),
73
	},
74 75 76 77 78

	Options: []cmds.Option{
		cmds.BoolOption("default", "add default bootstrap nodes"),
	},

79
	Run: func(req cmds.Request) (interface{}, error) {
80
		inputPeers, err := config.ParseBootstrapPeers(req.Arguments())
81
		if err != nil {
82
			return nil, err
83 84
		}

85 86
		r := fsrepo.At(req.Context().ConfigRoot)
		if err := r.Open(); err != nil {
87 88
			return nil, err
		}
89 90
		defer r.Close()
		cfg := r.Config()
91

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
		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...)
		}

107
		added, err := bootstrapAdd(r, cfg, inputPeers)
108
		if err != nil {
109
			return nil, err
110 111
		}

112 113 114 115
		if len(inputPeers) == 0 {
			return nil, cmds.ClientError("no bootstrap peers to add")
		}

116
		return &BootstrapOutput{added}, nil
117
	},
118
	Type: BootstrapOutput{},
119
	Marshalers: cmds.MarshalerMap{
120
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
121 122 123
			v, ok := res.Output().(*BootstrapOutput)
			if !ok {
				return nil, u.ErrCast()
124
			}
125 126 127

			var buf bytes.Buffer
			err := bootstrapWritePeers(&buf, "added ", v.Peers)
128
			return &buf, err
129 130 131 132 133
		},
	},
}

var bootstrapRemoveCmd = &cmds.Command{
134 135 136
	Helptext: cmds.HelpText{
		Tagline: "Removes peers from the bootstrap list",
		ShortDescription: `Outputs the list of peers that were removed.
137
` + bootstrapSecurityWarning,
138
	},
139

140
	Arguments: []cmds.Argument{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
141 142 143 144
		cmds.StringArg("peer", false, true, peerOptionDesc),
	},
	Options: []cmds.Option{
		cmds.BoolOption("all", "Remove all bootstrap peers."),
145
	},
146
	Run: func(req cmds.Request) (interface{}, error) {
147
		input, err := config.ParseBootstrapPeers(req.Arguments())
148
		if err != nil {
149
			return nil, err
150 151
		}

152 153
		r := fsrepo.At(req.Context().ConfigRoot)
		if err := r.Open(); err != nil {
154 155
			return nil, err
		}
156 157
		defer r.Close()
		cfg := r.Config()
158

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159 160 161 162 163
		all, _, err := req.Option("all").Bool()
		if err != nil {
			return nil, err
		}

164
		var removed []config.BootstrapPeer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
165
		if all {
166
			removed, err = bootstrapRemoveAll(r, cfg)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167
		} else {
168
			removed, err = bootstrapRemove(r, cfg, input)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
169
		}
170
		if err != nil {
171
			return nil, err
172 173
		}

174
		return &BootstrapOutput{removed}, nil
175
	},
176
	Type: BootstrapOutput{},
177
	Marshalers: cmds.MarshalerMap{
178
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
179 180 181
			v, ok := res.Output().(*BootstrapOutput)
			if !ok {
				return nil, u.ErrCast()
182
			}
183 184 185

			var buf bytes.Buffer
			err := bootstrapWritePeers(&buf, "removed ", v.Peers)
186
			return &buf, err
187 188 189 190 191
		},
	},
}

var bootstrapListCmd = &cmds.Command{
192 193 194 195
	Helptext: cmds.HelpText{
		Tagline:          "Show peers in the bootstrap list",
		ShortDescription: "Peers are output in the format '<multiaddr>/<peerID>'.",
	},
196

197
	Run: func(req cmds.Request) (interface{}, error) {
198 199 200 201 202 203
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

		peers := cfg.Bootstrap
204
		return &BootstrapOutput{peers}, nil
205
	},
206
	Type: BootstrapOutput{},
207 208
	Marshalers: cmds.MarshalerMap{
		cmds.Text: bootstrapMarshaler,
209 210 211
	},
}

212
func bootstrapMarshaler(res cmds.Response) (io.Reader, error) {
213 214 215 216
	v, ok := res.Output().(*BootstrapOutput)
	if !ok {
		return nil, u.ErrCast()
	}
217

218 219
	var buf bytes.Buffer
	err := bootstrapWritePeers(&buf, "", v.Peers)
220
	return &buf, err
221 222
}

223
func bootstrapWritePeers(w io.Writer, prefix string, peers []config.BootstrapPeer) error {
224

225 226 227 228 229 230 231 232
	for _, peer := range peers {
		s := prefix + peer.Address + "/" + peer.PeerID + "\n"
		_, err := w.Write([]byte(s))
		if err != nil {
			return err
		}
	}
	return nil
233 234
}

235
func bootstrapAdd(r repo.Repo, cfg *config.Config, peers []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
236
	added := make([]config.BootstrapPeer, 0, len(peers))
237 238 239 240

	for _, peer := range peers {
		duplicate := false
		for _, peer2 := range cfg.Bootstrap {
241
			if peer.Address == peer2.Address && peer.PeerID == peer2.PeerID {
242 243 244 245 246 247 248 249 250 251 252
				duplicate = true
				break
			}
		}

		if !duplicate {
			cfg.Bootstrap = append(cfg.Bootstrap, peer)
			added = append(added, peer)
		}
	}

253
	if err := r.SetConfig(cfg); err != nil {
254 255 256 257 258 259
		return nil, err
	}

	return added, nil
}

260
func bootstrapRemove(r repo.Repo, cfg *config.Config, toRemove []config.BootstrapPeer) ([]config.BootstrapPeer, error) {
261 262
	removed := make([]config.BootstrapPeer, 0, len(toRemove))
	keep := make([]config.BootstrapPeer, 0, len(cfg.Bootstrap))
263 264 265

	for _, peer := range cfg.Bootstrap {
		found := false
266
		for _, peer2 := range toRemove {
267 268 269 270 271 272 273 274 275 276 277 278 279
			if peer.Address == peer2.Address && peer.PeerID == peer2.PeerID {
				found = true
				removed = append(removed, peer)
				break
			}
		}

		if !found {
			keep = append(keep, peer)
		}
	}
	cfg.Bootstrap = keep

280
	if err := r.SetConfig(cfg); err != nil {
281 282 283 284 285 286
		return nil, err
	}

	return removed, nil
}

287
func bootstrapRemoveAll(r repo.Repo, cfg *config.Config) ([]config.BootstrapPeer, error) {
288
	removed := make([]config.BootstrapPeer, len(cfg.Bootstrap))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
289 290 291
	copy(removed, cfg.Bootstrap)

	cfg.Bootstrap = nil
292
	if err := r.SetConfig(cfg); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
293 294 295 296 297 298
		return nil, err
	}

	return removed, nil
}

299 300 301 302 303 304 305 306 307 308 309 310
// 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
}

311 312 313 314 315 316 317 318 319
const bootstrapSecurityWarning = `
SECURITY WARNING:

The bootstrap command manipulates the "bootstrap list", which contains
the addresses of bootstrap nodes. These are the *trusted peers* from
which to learn about other peers in the network. Only edit this list
if you understand the risks of adding or removing nodes from this list.

`