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

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
4
	"errors"
Steven Allen's avatar
Steven Allen committed
5
	"fmt"
6
	"io"
7
	"sort"
8

9
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
10
	repo "github.com/ipfs/go-ipfs/repo"
Overbool's avatar
Overbool committed
11
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
Jan Winkelmann's avatar
Jan Winkelmann committed
12

Jakub Sztandera's avatar
Jakub Sztandera committed
13 14
	cmds "github.com/ipfs/go-ipfs-cmds"
	config "github.com/ipfs/go-ipfs-config"
Steven Allen's avatar
Steven Allen committed
15 16
	peer "github.com/libp2p/go-libp2p-core/peer"
	ma "github.com/multiformats/go-multiaddr"
17 18 19
)

type BootstrapOutput struct {
20
	Peers []string
21 22
}

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

25
var BootstrapCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
26
	Helptext: cmds.HelpText{
rht's avatar
rht committed
27
		Tagline: "Show or edit the list of bootstrap peers.",
28 29
		ShortDescription: `
Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'.
30
` + bootstrapSecurityWarning,
31
	},
32

Overbool's avatar
Overbool committed
33 34 35
	Run:      bootstrapListCmd.Run,
	Encoders: bootstrapListCmd.Encoders,
	Type:     bootstrapListCmd.Type,
36

37
	Subcommands: map[string]*cmds.Command{
38 39 40
		"list": bootstrapListCmd,
		"add":  bootstrapAddCmd,
		"rm":   bootstrapRemoveCmd,
41 42 43
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
44 45 46 47
const (
	defaultOptionName = "default"
)

48
var bootstrapAddCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
49
	Helptext: cmds.HelpText{
rht's avatar
rht committed
50
		Tagline: "Add peers to the bootstrap list.",
51
		ShortDescription: `Outputs a list of peers that were added (that weren't already
52 53
in the bootstrap list).
` + bootstrapSecurityWarning,
54
	},
55

Steven Allen's avatar
Steven Allen committed
56 57
	Arguments: []cmds.Argument{
		cmds.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
58
	},
59

Steven Allen's avatar
Steven Allen committed
60 61
	Options: []cmds.Option{
		cmds.BoolOption(defaultOptionName, "Add default bootstrap nodes. (Deprecated, use 'default' subcommand instead)"),
62 63 64
	},
	Subcommands: map[string]*cmds.Command{
		"default": bootstrapAddDefaultCmd,
65 66
	},

Overbool's avatar
Overbool committed
67 68
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		deflt, _ := req.Options[defaultOptionName].(bool)
69

Steven Allen's avatar
Steven Allen committed
70 71
		inputPeers := config.DefaultBootstrapAddresses
		if !deflt {
72 73 74 75
			if err := req.ParseBodyArgs(); err != nil {
				return err
			}

Steven Allen's avatar
Steven Allen committed
76
			inputPeers = req.Arguments
77 78
		}

79
		if len(inputPeers) == 0 {
Overbool's avatar
Overbool committed
80
			return errors.New("no bootstrap peers to add")
81 82
		}

83 84 85 86 87 88
		cfgRoot, err := cmdenv.GetConfigRoot(env)
		if err != nil {
			return err
		}

		r, err := fsrepo.Open(cfgRoot)
89
		if err != nil {
Overbool's avatar
Overbool committed
90
			return err
91 92 93 94
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Overbool's avatar
Overbool committed
95
			return err
96 97
		}

98 99
		added, err := bootstrapAdd(r, cfg, inputPeers)
		if err != nil {
Overbool's avatar
Overbool committed
100
			return err
101 102
		}

Steven Allen's avatar
Steven Allen committed
103
		return cmds.EmitOnce(res, &BootstrapOutput{added})
104
	},
105
	Type: BootstrapOutput{},
Overbool's avatar
Overbool committed
106 107 108 109
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *BootstrapOutput) error {
			return bootstrapWritePeers(w, "added ", out.Peers)
		}),
110 111 112
	},
}

113
var bootstrapAddDefaultCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
114
	Helptext: cmds.HelpText{
115 116 117 118
		Tagline: "Add default peers to the bootstrap list.",
		ShortDescription: `Outputs a list of peers that were added (that weren't already
in the bootstrap list).`,
	},
Overbool's avatar
Overbool committed
119
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
120 121 122 123 124 125
		cfgRoot, err := cmdenv.GetConfigRoot(env)
		if err != nil {
			return err
		}

		r, err := fsrepo.Open(cfgRoot)
126
		if err != nil {
Overbool's avatar
Overbool committed
127
			return err
128 129 130 131 132
		}

		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Overbool's avatar
Overbool committed
133
			return err
134 135
		}

Steven Allen's avatar
Steven Allen committed
136
		added, err := bootstrapAdd(r, cfg, config.DefaultBootstrapAddresses)
137
		if err != nil {
Overbool's avatar
Overbool committed
138
			return err
139 140
		}

Steven Allen's avatar
Steven Allen committed
141
		return cmds.EmitOnce(res, &BootstrapOutput{added})
142 143
	},
	Type: BootstrapOutput{},
Overbool's avatar
Overbool committed
144 145 146 147
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *BootstrapOutput) error {
			return bootstrapWritePeers(w, "added ", out.Peers)
		}),
148 149 150
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
151 152 153 154
const (
	bootstrapAllOptionName = "all"
)

155
var bootstrapRemoveCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
156
	Helptext: cmds.HelpText{
157
		Tagline: "Remove peers from the bootstrap list.",
158
		ShortDescription: `Outputs the list of peers that were removed.
159
` + bootstrapSecurityWarning,
160
	},
161

Steven Allen's avatar
Steven Allen committed
162 163
	Arguments: []cmds.Argument{
		cmds.StringArg("peer", false, true, peerOptionDesc).EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
164
	},
Steven Allen's avatar
Steven Allen committed
165 166
	Options: []cmds.Option{
		cmds.BoolOption(bootstrapAllOptionName, "Remove all bootstrap peers. (Deprecated, use 'all' subcommand)"),
167 168 169
	},
	Subcommands: map[string]*cmds.Command{
		"all": bootstrapRemoveAllCmd,
170
	},
Overbool's avatar
Overbool committed
171 172
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		all, _ := req.Options[bootstrapAllOptionName].(bool)
173

174 175 176 177 178 179
		cfgRoot, err := cmdenv.GetConfigRoot(env)
		if err != nil {
			return err
		}

		r, err := fsrepo.Open(cfgRoot)
180
		if err != nil {
Overbool's avatar
Overbool committed
181
			return err
182
		}
183
		defer r.Close()
184 185
		cfg, err := r.Config()
		if err != nil {
Overbool's avatar
Overbool committed
186
			return err
187
		}
188

Steven Allen's avatar
Steven Allen committed
189
		var removed []string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190
		if all {
191
			removed, err = bootstrapRemoveAll(r, cfg)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
192
		} else {
193 194 195
			if err := req.ParseBodyArgs(); err != nil {
				return err
			}
Steven Allen's avatar
Steven Allen committed
196
			removed, err = bootstrapRemove(r, cfg, req.Arguments)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
		}
198
		if err != nil {
Overbool's avatar
Overbool committed
199
			return err
200 201
		}

Steven Allen's avatar
Steven Allen committed
202
		return cmds.EmitOnce(res, &BootstrapOutput{removed})
203
	},
204
	Type: BootstrapOutput{},
Overbool's avatar
Overbool committed
205 206 207 208
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *BootstrapOutput) error {
			return bootstrapWritePeers(w, "removed ", out.Peers)
		}),
209 210 211
	},
}

212
var bootstrapRemoveAllCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
213
	Helptext: cmds.HelpText{
214
		Tagline:          "Remove all peers from the bootstrap list.",
215 216 217
		ShortDescription: `Outputs the list of peers that were removed.`,
	},

Overbool's avatar
Overbool committed
218
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
219 220 221 222 223 224
		cfgRoot, err := cmdenv.GetConfigRoot(env)
		if err != nil {
			return err
		}

		r, err := fsrepo.Open(cfgRoot)
225
		if err != nil {
Overbool's avatar
Overbool committed
226
			return err
227 228 229 230
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
Overbool's avatar
Overbool committed
231
			return err
232 233 234 235
		}

		removed, err := bootstrapRemoveAll(r, cfg)
		if err != nil {
Overbool's avatar
Overbool committed
236
			return err
237 238
		}

Steven Allen's avatar
Steven Allen committed
239
		return cmds.EmitOnce(res, &BootstrapOutput{removed})
240 241
	},
	Type: BootstrapOutput{},
Overbool's avatar
Overbool committed
242 243 244 245
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *BootstrapOutput) error {
			return bootstrapWritePeers(w, "removed ", out.Peers)
		}),
246 247 248
	},
}

249
var bootstrapListCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
250
	Helptext: cmds.HelpText{
rht's avatar
rht committed
251
		Tagline:          "Show peers in the bootstrap list.",
252 253
		ShortDescription: "Peers are output in the format '<multiaddr>/<peerID>'.",
	},
254

Overbool's avatar
Overbool committed
255
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
256 257 258 259 260 261
		cfgRoot, err := cmdenv.GetConfigRoot(env)
		if err != nil {
			return err
		}

		r, err := fsrepo.Open(cfgRoot)
262
		if err != nil {
Overbool's avatar
Overbool committed
263
			return err
264
		}
265
		defer r.Close()
266 267
		cfg, err := r.Config()
		if err != nil {
Overbool's avatar
Overbool committed
268
			return err
269
		}
270

271 272
		peers, err := cfg.BootstrapPeers()
		if err != nil {
Overbool's avatar
Overbool committed
273
			return err
274
		}
Overbool's avatar
Overbool committed
275

276
		return cmds.EmitOnce(res, &BootstrapOutput{config.BootstrapPeerStrings(peers)})
277
	},
278
	Type: BootstrapOutput{},
Overbool's avatar
Overbool committed
279 280 281 282
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *BootstrapOutput) error {
			return bootstrapWritePeers(w, "", out.Peers)
		}),
283 284 285
	},
}

286 287 288
func bootstrapWritePeers(w io.Writer, prefix string, peers []string) error {
	sort.Stable(sort.StringSlice(peers))
	for _, peer := range peers {
289
		_, err := w.Write([]byte(prefix + peer + "\n"))
290 291 292 293 294
		if err != nil {
			return err
		}
	}
	return nil
295 296
}

Steven Allen's avatar
Steven Allen committed
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
func bootstrapAdd(r repo.Repo, cfg *config.Config, peers []string) ([]string, error) {
	for _, p := range peers {
		m, err := ma.NewMultiaddr(p)
		if err != nil {
			return nil, err
		}
		tpt, p2ppart := ma.SplitLast(m)
		if p2ppart == nil || p2ppart.Protocol().Code != ma.P_P2P {
			return nil, fmt.Errorf("invalid bootstrap address: %s", p)
		}
		if tpt == nil {
			return nil, fmt.Errorf("bootstrap address without a transport: %s", p)
		}
	}

312
	addedMap := map[string]struct{}{}
Steven Allen's avatar
Steven Allen committed
313
	addedList := make([]string, 0, len(peers))
314

315 316 317 318 319
	// re-add cfg bootstrap peers to rm dupes
	bpeers := cfg.Bootstrap
	cfg.Bootstrap = nil

	// add new peers
Steven Allen's avatar
Steven Allen committed
320
	for _, s := range peers {
321 322
		if _, found := addedMap[s]; found {
			continue
323 324
		}

325
		cfg.Bootstrap = append(cfg.Bootstrap, s)
Steven Allen's avatar
Steven Allen committed
326
		addedList = append(addedList, s)
327 328 329 330 331 332 333
		addedMap[s] = struct{}{}
	}

	// add back original peers. in this order so that we output them.
	for _, s := range bpeers {
		if _, found := addedMap[s]; found {
			continue
334
		}
335 336 337

		cfg.Bootstrap = append(cfg.Bootstrap, s)
		addedMap[s] = struct{}{}
338 339
	}

340
	if err := r.SetConfig(cfg); err != nil {
341 342 343
		return nil, err
	}

344
	return addedList, nil
345 346
}

Steven Allen's avatar
Steven Allen committed
347 348 349 350 351 352 353 354 355 356 357 358
func bootstrapRemove(r repo.Repo, cfg *config.Config, toRemove []string) ([]string, error) {
	removed := make([]peer.AddrInfo, 0, len(toRemove))
	keep := make([]peer.AddrInfo, 0, len(cfg.Bootstrap))

	toRemoveAddr, err := config.ParseBootstrapPeers(toRemove)
	if err != nil {
		return nil, err
	}
	toRemoveMap := make(map[peer.ID][]ma.Multiaddr, len(toRemoveAddr))
	for _, addr := range toRemoveAddr {
		toRemoveMap[addr.ID] = addr.Addrs
	}
359

360 361 362 363 364
	peers, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}

Steven Allen's avatar
Steven Allen committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	for _, p := range peers {
		addrs, ok := toRemoveMap[p.ID]
		// not in the remove set?
		if !ok {
			keep = append(keep, p)
			continue
		}
		// remove the entire peer?
		if len(addrs) == 0 {
			removed = append(removed, p)
			continue
		}
		var (
			keptAddrs, removedAddrs []ma.Multiaddr
		)
		// remove specific addresses
	filter:
		for _, addr := range p.Addrs {
			for _, addr2 := range addrs {
				if addr.Equal(addr2) {
					removedAddrs = append(removedAddrs, addr)
					continue filter
				}
388
			}
Steven Allen's avatar
Steven Allen committed
389 390 391 392
			keptAddrs = append(keptAddrs, addr)
		}
		if len(removedAddrs) > 0 {
			removed = append(removed, peer.AddrInfo{ID: p.ID, Addrs: removedAddrs})
393 394
		}

Steven Allen's avatar
Steven Allen committed
395 396
		if len(keptAddrs) > 0 {
			keep = append(keep, peer.AddrInfo{ID: p.ID, Addrs: keptAddrs})
397 398
		}
	}
399
	cfg.SetBootstrapPeers(keep)
400

401
	if err := r.SetConfig(cfg); err != nil {
402 403 404
		return nil, err
	}

Steven Allen's avatar
Steven Allen committed
405
	return config.BootstrapPeerStrings(removed), nil
406 407
}

Steven Allen's avatar
Steven Allen committed
408
func bootstrapRemoveAll(r repo.Repo, cfg *config.Config) ([]string, error) {
409 410 411 412
	removed, err := cfg.BootstrapPeers()
	if err != nil {
		return nil, err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
413 414

	cfg.Bootstrap = nil
415
	if err := r.SetConfig(cfg); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
416 417
		return nil, err
	}
Steven Allen's avatar
Steven Allen committed
418
	return config.BootstrapPeerStrings(removed), nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
419 420
}

421 422 423 424 425 426 427 428 429
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.

`