Feature: Support multiple listeners in configuration

* add ipv6 listening addresses to the default config

* ipfsproxy: support multiple listeners. Add default ipv6.

* mm

* restapi: support multiple listen addresses. enable ipv6

* cluster_config: format default listen addresses

* commands: update for multiple listeners. Fix randomports for udp and ipv6.

* ipfs-cluster-service: fix randomports test

* multiple listeners: fix remaining tests

* golint

* Disable ipv6 in defaults

It is not supported by docker by default. It is not supported in travis-CI
build environments. User can enable it now manually.

* proxy: disable ipv6 in test

* ipfshttp: fix test

Co-authored-by: @RubenKelevra <cyrond@gmail.com>
This commit is contained in:
Hector Sanjuan 2020-02-28 11:16:16 -05:00 committed by GitHub
parent 7986d94242
commit 531379b1d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 302 additions and 180 deletions

View File

@ -7,6 +7,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
ipfsconfig "github.com/ipfs/go-ipfs-config"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
@ -19,9 +20,13 @@ const (
minMaxHeaderBytes = 4096 minMaxHeaderBytes = 4096
) )
// DefaultListenAddrs contains the default listeners for the proxy.
var DefaultListenAddrs = []string{
"/ip4/127.0.0.1/tcp/9095",
}
// Default values for Config. // Default values for Config.
const ( const (
DefaultListenAddr = "/ip4/127.0.0.1/tcp/9095"
DefaultNodeAddr = "/ip4/127.0.0.1/tcp/5001" DefaultNodeAddr = "/ip4/127.0.0.1/tcp/5001"
DefaultNodeHTTPS = false DefaultNodeHTTPS = false
DefaultReadTimeout = 0 DefaultReadTimeout = 0
@ -39,7 +44,7 @@ type Config struct {
config.Saver config.Saver
// Listen parameters for the IPFS Proxy. // Listen parameters for the IPFS Proxy.
ListenAddr ma.Multiaddr ListenAddr []ma.Multiaddr
// Host/Port for the IPFS daemon. // Host/Port for the IPFS daemon.
NodeAddr ma.Multiaddr NodeAddr ma.Multiaddr
@ -93,7 +98,7 @@ type Config struct {
} }
type jsonConfig struct { type jsonConfig struct {
ListenMultiaddress string `json:"listen_multiaddress"` ListenMultiaddress ipfsconfig.Strings `json:"listen_multiaddress"`
NodeMultiaddress string `json:"node_multiaddress"` NodeMultiaddress string `json:"node_multiaddress"`
NodeHTTPS bool `json:"node_https,omitempty"` NodeHTTPS bool `json:"node_https,omitempty"`
@ -131,10 +136,14 @@ func (cfg *Config) ConfigKey() string {
// Default sets the fields of this Config to sensible default values. // Default sets the fields of this Config to sensible default values.
func (cfg *Config) Default() error { func (cfg *Config) Default() error {
proxy, err := ma.NewMultiaddr(DefaultListenAddr) proxy := make([]ma.Multiaddr, 0, len(DefaultListenAddrs))
for _, def := range DefaultListenAddrs {
a, err := ma.NewMultiaddr(def)
if err != nil { if err != nil {
return err return err
} }
proxy = append(proxy, a)
}
node, err := ma.NewMultiaddr(DefaultNodeAddr) node, err := ma.NewMultiaddr(DefaultNodeAddr)
if err != nil { if err != nil {
return err return err
@ -174,7 +183,7 @@ func (cfg *Config) ApplyEnvVars() error {
// at least in appearance. // at least in appearance.
func (cfg *Config) Validate() error { func (cfg *Config) Validate() error {
var err error var err error
if cfg.ListenAddr == nil { if len(cfg.ListenAddr) == 0 {
err = errors.New("ipfsproxy.listen_multiaddress not set") err = errors.New("ipfsproxy.listen_multiaddress not set")
} }
if cfg.NodeAddr == nil { if cfg.NodeAddr == nil {
@ -230,12 +239,15 @@ func (cfg *Config) LoadJSON(raw []byte) error {
} }
func (cfg *Config) applyJSONConfig(jcfg *jsonConfig) error { func (cfg *Config) applyJSONConfig(jcfg *jsonConfig) error {
if jcfg.ListenMultiaddress != "" { if addresses := jcfg.ListenMultiaddress; len(addresses) > 0 {
proxyAddr, err := ma.NewMultiaddr(jcfg.ListenMultiaddress) cfg.ListenAddr = make([]ma.Multiaddr, 0, len(addresses))
for _, a := range addresses {
proxyAddr, err := ma.NewMultiaddr(a)
if err != nil { if err != nil {
return fmt.Errorf("error parsing proxy listen_multiaddress: %s", err) return fmt.Errorf("error parsing proxy listen_multiaddress: %s", err)
} }
cfg.ListenAddr = proxyAddr cfg.ListenAddr = append(cfg.ListenAddr, proxyAddr)
}
} }
if jcfg.NodeMultiaddress != "" { if jcfg.NodeMultiaddress != "" {
nodeAddr, err := ma.NewMultiaddr(jcfg.NodeMultiaddress) nodeAddr, err := ma.NewMultiaddr(jcfg.NodeMultiaddress)
@ -295,8 +307,13 @@ func (cfg *Config) toJSONConfig() (jcfg *jsonConfig, err error) {
jcfg = &jsonConfig{} jcfg = &jsonConfig{}
addresses := make([]string, 0, len(cfg.ListenAddr))
for _, a := range cfg.ListenAddr {
addresses = append(addresses, a.String())
}
// Set all configuration fields // Set all configuration fields
jcfg.ListenMultiaddress = cfg.ListenAddr.String() jcfg.ListenMultiaddress = addresses
jcfg.NodeMultiaddress = cfg.NodeAddr.String() jcfg.NodeMultiaddress = cfg.NodeAddr.String()
jcfg.ReadTimeout = cfg.ReadTimeout.String() jcfg.ReadTimeout = cfg.ReadTimeout.String()
jcfg.ReadHeaderTimeout = cfg.ReadHeaderTimeout.String() jcfg.ReadHeaderTimeout = cfg.ReadHeaderTimeout.String()

View File

@ -40,7 +40,7 @@ func TestLoadJSON(t *testing.T) {
j := &jsonConfig{} j := &jsonConfig{}
json.Unmarshal(cfgJSON, j) json.Unmarshal(cfgJSON, j)
j.ListenMultiaddress = "abc" j.ListenMultiaddress = []string{"abc"}
tst, _ := json.Marshal(j) tst, _ := json.Marshal(j)
err = cfg.LoadJSON(tst) err = cfg.LoadJSON(tst)
if err == nil { if err == nil {

View File

@ -58,7 +58,7 @@ type Server struct {
rpcClient *rpc.Client rpcClient *rpc.Client
rpcReady chan struct{} rpcReady chan struct{}
listener net.Listener // proxy listener listeners []net.Listener // proxy listener
server *http.Server // proxy server server *http.Server // proxy server
ipfsRoundTripper http.RoundTripper // allows to talk to IPFS ipfsRoundTripper http.RoundTripper // allows to talk to IPFS
@ -126,7 +126,9 @@ func New(cfg *Config) (*Server, error) {
return nil, err return nil, err
} }
proxyNet, proxyAddr, err := manet.DialArgs(cfg.ListenAddr) var listeners []net.Listener
for _, addr := range cfg.ListenAddr {
proxyNet, proxyAddr, err := manet.DialArgs(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -135,6 +137,8 @@ func New(cfg *Config) (*Server, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
listeners = append(listeners, l)
}
nodeScheme := "http" nodeScheme := "http"
if cfg.NodeHTTPS { if cfg.NodeHTTPS {
@ -197,7 +201,7 @@ func New(cfg *Config) (*Server, error) {
nodeAddr: nodeHTTPAddr, nodeAddr: nodeHTTPAddr,
nodeScheme: nodeScheme, nodeScheme: nodeScheme,
rpcReady: make(chan struct{}, 1), rpcReady: make(chan struct{}, 1),
listener: l, listeners: listeners,
server: s, server: s,
ipfsRoundTripper: reverseProxy.Transport, ipfsRoundTripper: reverseProxy.Transport,
} }
@ -284,7 +288,9 @@ func (proxy *Server) Shutdown(ctx context.Context) error {
proxy.cancel() proxy.cancel()
close(proxy.rpcReady) close(proxy.rpcReady)
proxy.server.SetKeepAlivesEnabled(false) proxy.server.SetKeepAlivesEnabled(false)
proxy.listener.Close() for _, l := range proxy.listeners {
l.Close()
}
proxy.wg.Wait() proxy.wg.Wait()
proxy.shutdown = true proxy.shutdown = true
@ -301,19 +307,27 @@ func (proxy *Server) run() {
defer proxy.shutdownLock.Unlock() defer proxy.shutdownLock.Unlock()
// This launches the proxy // This launches the proxy
proxy.wg.Add(1) proxy.wg.Add(len(proxy.listeners))
go func() { for _, l := range proxy.listeners {
go func(l net.Listener) {
defer proxy.wg.Done() defer proxy.wg.Done()
maddr, err := manet.FromNetAddr(l.Addr())
if err != nil {
logger.Error(err)
}
logger.Infof( logger.Infof(
"IPFS Proxy: %s -> %s", "IPFS Proxy: %s -> %s",
proxy.config.ListenAddr, maddr,
proxy.config.NodeAddr, proxy.config.NodeAddr,
) )
err := proxy.server.Serve(proxy.listener) // hangs here err = proxy.server.Serve(l) // hangs here
if err != nil && !strings.Contains(err.Error(), "closed network connection") { if err != nil && !strings.Contains(err.Error(), "closed network connection") {
logger.Error(err) logger.Error(err)
} }
}() }(l)
}
} }
// ipfsErrorResponder writes an http error response just like IPFS would. // ipfsErrorResponder writes an http error response just like IPFS would.

View File

@ -34,7 +34,7 @@ func testIPFSProxyWithConfig(t *testing.T, cfg *Config) (*Server, *test.IpfsMock
proxyMAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0") proxyMAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
cfg.NodeAddr = nodeMAddr cfg.NodeAddr = nodeMAddr
cfg.ListenAddr = proxyMAddr cfg.ListenAddr = []ma.Multiaddr{proxyMAddr}
cfg.ExtractHeadersExtra = []string{ cfg.ExtractHeadersExtra = []string{
test.IpfsCustomHeaderName, test.IpfsCustomHeaderName,
test.IpfsTimeHeaderName, test.IpfsTimeHeaderName,
@ -716,7 +716,7 @@ func TestProxyError(t *testing.T) {
} }
func proxyURL(c *Server) string { func proxyURL(c *Server) string {
addr := c.listener.Addr() addr := c.listeners[0].Addr()
return fmt.Sprintf("http://%s/api/v0", addr.String()) return fmt.Sprintf("http://%s/api/v0", addr.String())
} }

View File

@ -22,7 +22,7 @@ func testAPI(t *testing.T) *rest.API {
cfg := &rest.Config{} cfg := &rest.Config{}
cfg.Default() cfg.Default()
cfg.HTTPListenAddr = apiMAddr cfg.HTTPListenAddr = []ma.Multiaddr{apiMAddr}
var secret [32]byte var secret [32]byte
prot, err := pnet.NewV1ProtectorFromBytes(&secret) prot, err := pnet.NewV1ProtectorFromBytes(&secret)
if err != nil { if err != nil {
@ -54,8 +54,8 @@ func shutdown(a *rest.API) {
} }
func apiMAddr(a *rest.API) ma.Multiaddr { func apiMAddr(a *rest.API) ma.Multiaddr {
listen, _ := a.HTTPAddress() listen, _ := a.HTTPAddresses()
hostPort := strings.Split(listen, ":") hostPort := strings.Split(listen[0], ":")
addr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%s", hostPort[1])) addr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%s", hostPort[1]))
return addr return addr

View File

@ -10,14 +10,15 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/ipfs/ipfs-cluster/config" ipfsconfig "github.com/ipfs/go-ipfs-config"
crypto "github.com/libp2p/go-libp2p-core/crypto" crypto "github.com/libp2p/go-libp2p-core/crypto"
peer "github.com/libp2p/go-libp2p-core/peer" peer "github.com/libp2p/go-libp2p-core/peer"
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/ipfs/ipfs-cluster/config"
) )
const configKey = "restapi" const configKey = "restapi"
@ -25,9 +26,13 @@ const envConfigKey = "cluster_restapi"
const minMaxHeaderBytes = 4096 const minMaxHeaderBytes = 4096
// DefaultHTTPListenAddrs contains default listen addresses for the HTTP API.
var DefaultHTTPListenAddrs = []string{
"/ip4/127.0.0.1/tcp/9094",
}
// These are the default values for Config // These are the default values for Config
const ( const (
DefaultHTTPListenAddr = "/ip4/127.0.0.1/tcp/9094"
DefaultReadTimeout = 0 DefaultReadTimeout = 0
DefaultReadHeaderTimeout = 5 * time.Second DefaultReadHeaderTimeout = 5 * time.Second
DefaultWriteTimeout = 0 DefaultWriteTimeout = 0
@ -66,7 +71,7 @@ type Config struct {
config.Saver config.Saver
// Listen address for the HTTP REST API endpoint. // Listen address for the HTTP REST API endpoint.
HTTPListenAddr ma.Multiaddr HTTPListenAddr []ma.Multiaddr
// TLS configuration for the HTTP listener // TLS configuration for the HTTP listener
TLS *tls.Config TLS *tls.Config
@ -97,7 +102,7 @@ type Config struct {
MaxHeaderBytes int MaxHeaderBytes int
// Listen address for the Libp2p REST API endpoint. // Listen address for the Libp2p REST API endpoint.
Libp2pListenAddr ma.Multiaddr Libp2pListenAddr []ma.Multiaddr
// ID and PrivateKey are used to create a libp2p host if we // ID and PrivateKey are used to create a libp2p host if we
// want the API component to do it (not by default). // want the API component to do it (not by default).
@ -131,7 +136,7 @@ type Config struct {
} }
type jsonConfig struct { type jsonConfig struct {
HTTPListenMultiaddress string `json:"http_listen_multiaddress"` HTTPListenMultiaddress ipfsconfig.Strings `json:"http_listen_multiaddress"`
SSLCertFile string `json:"ssl_cert_file,omitempty"` SSLCertFile string `json:"ssl_cert_file,omitempty"`
SSLKeyFile string `json:"ssl_key_file,omitempty"` SSLKeyFile string `json:"ssl_key_file,omitempty"`
ReadTimeout string `json:"read_timeout"` ReadTimeout string `json:"read_timeout"`
@ -140,7 +145,7 @@ type jsonConfig struct {
IdleTimeout string `json:"idle_timeout"` IdleTimeout string `json:"idle_timeout"`
MaxHeaderBytes int `json:"max_header_bytes"` MaxHeaderBytes int `json:"max_header_bytes"`
Libp2pListenMultiaddress string `json:"libp2p_listen_multiaddress,omitempty"` Libp2pListenMultiaddress ipfsconfig.Strings `json:"libp2p_listen_multiaddress,omitempty"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
PrivateKey string `json:"private_key,omitempty"` PrivateKey string `json:"private_key,omitempty"`
@ -179,8 +184,15 @@ func (cfg *Config) ConfigKey() string {
// Default initializes this Config with working values. // Default initializes this Config with working values.
func (cfg *Config) Default() error { func (cfg *Config) Default() error {
// http // http
httpListen, _ := ma.NewMultiaddr(DefaultHTTPListenAddr) addrs := make([]ma.Multiaddr, 0, len(DefaultHTTPListenAddrs))
cfg.HTTPListenAddr = httpListen for _, def := range DefaultHTTPListenAddrs {
httpListen, err := ma.NewMultiaddr(def)
if err != nil {
return err
}
addrs = append(addrs, httpListen)
}
cfg.HTTPListenAddr = addrs
cfg.pathSSLCertFile = "" cfg.pathSSLCertFile = ""
cfg.pathSSLKeyFile = "" cfg.pathSSLKeyFile = ""
cfg.ReadTimeout = DefaultReadTimeout cfg.ReadTimeout = DefaultReadTimeout
@ -225,7 +237,6 @@ func (cfg *Config) ApplyEnvVars() error {
if err != nil { if err != nil {
return err return err
} }
return cfg.applyJSONConfig(jcfg) return cfg.applyJSONConfig(jcfg)
} }
@ -255,9 +266,9 @@ func (cfg *Config) Validate() error {
} }
func (cfg *Config) validateLibp2p() error { func (cfg *Config) validateLibp2p() error {
if cfg.ID != "" || cfg.PrivateKey != nil || cfg.Libp2pListenAddr != nil { if cfg.ID != "" || cfg.PrivateKey != nil || len(cfg.Libp2pListenAddr) > 0 {
// if one is set, all should be // if one is set, all should be
if cfg.ID == "" || cfg.PrivateKey == nil || cfg.Libp2pListenAddr == nil { if cfg.ID == "" || cfg.PrivateKey == nil || len(cfg.Libp2pListenAddr) == 0 {
return errors.New("all ID, private_key and libp2p_listen_multiaddress should be set") return errors.New("all ID, private_key and libp2p_listen_multiaddress should be set")
} }
if !cfg.ID.MatchesPrivateKey(cfg.PrivateKey) { if !cfg.ID.MatchesPrivateKey(cfg.PrivateKey) {
@ -288,6 +299,7 @@ func (cfg *Config) applyJSONConfig(jcfg *jsonConfig) error {
if err != nil { if err != nil {
return err return err
} }
err = cfg.loadLibp2pOptions(jcfg) err = cfg.loadLibp2pOptions(jcfg)
if err != nil { if err != nil {
return err return err
@ -302,13 +314,16 @@ func (cfg *Config) applyJSONConfig(jcfg *jsonConfig) error {
} }
func (cfg *Config) loadHTTPOptions(jcfg *jsonConfig) error { func (cfg *Config) loadHTTPOptions(jcfg *jsonConfig) error {
if httpListen := jcfg.HTTPListenMultiaddress; httpListen != "" { if addresses := jcfg.HTTPListenMultiaddress; len(addresses) > 0 {
httpAddr, err := ma.NewMultiaddr(httpListen) cfg.HTTPListenAddr = make([]ma.Multiaddr, 0, len(addresses))
for _, addr := range addresses {
httpAddr, err := ma.NewMultiaddr(addr)
if err != nil { if err != nil {
err = fmt.Errorf("error parsing restapi.http_listen_multiaddress: %s", err) err = fmt.Errorf("error parsing restapi.http_listen_multiaddress: %s", err)
return err return err
} }
cfg.HTTPListenAddr = httpAddr cfg.HTTPListenAddr = append(cfg.HTTPListenAddr, httpAddr)
}
} }
err := cfg.tlsOptions(jcfg) err := cfg.tlsOptions(jcfg)
@ -373,13 +388,16 @@ func (cfg *Config) tlsOptions(jcfg *jsonConfig) error {
} }
func (cfg *Config) loadLibp2pOptions(jcfg *jsonConfig) error { func (cfg *Config) loadLibp2pOptions(jcfg *jsonConfig) error {
if libp2pListen := jcfg.Libp2pListenMultiaddress; libp2pListen != "" { if addresses := jcfg.Libp2pListenMultiaddress; len(addresses) > 0 {
libp2pAddr, err := ma.NewMultiaddr(libp2pListen) cfg.Libp2pListenAddr = make([]ma.Multiaddr, 0, len(addresses))
for _, addr := range addresses {
libp2pAddr, err := ma.NewMultiaddr(addr)
if err != nil { if err != nil {
err = fmt.Errorf("error parsing restapi.libp2p_listen_multiaddress: %s", err) err = fmt.Errorf("error parsing restapi.libp2p_listen_multiaddress: %s", err)
return err return err
} }
cfg.Libp2pListenAddr = libp2pAddr cfg.Libp2pListenAddr = append(cfg.Libp2pListenAddr, libp2pAddr)
}
} }
if jcfg.PrivateKey != "" { if jcfg.PrivateKey != "" {
@ -424,8 +442,18 @@ func (cfg *Config) toJSONConfig() (jcfg *jsonConfig, err error) {
} }
}() }()
httpAddresses := make([]string, 0, len(cfg.HTTPListenAddr))
for _, addr := range cfg.HTTPListenAddr {
httpAddresses = append(httpAddresses, addr.String())
}
libp2pAddresses := make([]string, 0, len(cfg.Libp2pListenAddr))
for _, addr := range cfg.Libp2pListenAddr {
libp2pAddresses = append(libp2pAddresses, addr.String())
}
jcfg = &jsonConfig{ jcfg = &jsonConfig{
HTTPListenMultiaddress: cfg.HTTPListenAddr.String(), HTTPListenMultiaddress: httpAddresses,
SSLCertFile: cfg.pathSSLCertFile, SSLCertFile: cfg.pathSSLCertFile,
SSLKeyFile: cfg.pathSSLKeyFile, SSLKeyFile: cfg.pathSSLKeyFile,
ReadTimeout: cfg.ReadTimeout.String(), ReadTimeout: cfg.ReadTimeout.String(),
@ -454,8 +482,8 @@ func (cfg *Config) toJSONConfig() (jcfg *jsonConfig, err error) {
jcfg.PrivateKey = pKey jcfg.PrivateKey = pKey
} }
} }
if cfg.Libp2pListenAddr != nil { if len(libp2pAddresses) > 0 {
jcfg.Libp2pListenMultiaddress = cfg.Libp2pListenAddr.String() jcfg.Libp2pListenMultiaddress = libp2pAddresses
} }
return return

View File

@ -58,7 +58,7 @@ func TestLoadJSON(t *testing.T) {
j := &jsonConfig{} j := &jsonConfig{}
json.Unmarshal(cfgJSON, j) json.Unmarshal(cfgJSON, j)
j.HTTPListenMultiaddress = "abc" j.HTTPListenMultiaddress = []string{"abc"}
tst, _ := json.Marshal(j) tst, _ := json.Marshal(j)
err = cfg.LoadJSON(tst) err = cfg.LoadJSON(tst)
if err == nil { if err == nil {
@ -103,7 +103,7 @@ func TestLoadJSON(t *testing.T) {
j = &jsonConfig{} j = &jsonConfig{}
json.Unmarshal(cfgJSON, j) json.Unmarshal(cfgJSON, j)
j.Libp2pListenMultiaddress = "abc" j.Libp2pListenMultiaddress = []string{"abc"}
tst, _ = json.Marshal(j) tst, _ = json.Marshal(j)
err = cfg.LoadJSON(tst) err = cfg.LoadJSON(tst)
if err == nil { if err == nil {
@ -178,8 +178,8 @@ func TestLibp2pConfig(t *testing.T) {
cfg.ID = pid cfg.ID = pid
cfg.PrivateKey = priv cfg.PrivateKey = priv
addr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0") addr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
cfg.HTTPListenAddr = addr cfg.HTTPListenAddr = []ma.Multiaddr{addr}
cfg.Libp2pListenAddr = addr cfg.Libp2pListenAddr = []ma.Multiaddr{addr}
err = cfg.Validate() err = cfg.Validate()
if err != nil { if err != nil {

View File

@ -38,7 +38,6 @@ import (
libp2pquic "github.com/libp2p/go-libp2p-quic-transport" libp2pquic "github.com/libp2p/go-libp2p-quic-transport"
secio "github.com/libp2p/go-libp2p-secio" secio "github.com/libp2p/go-libp2p-secio"
libp2ptls "github.com/libp2p/go-libp2p-tls" libp2ptls "github.com/libp2p/go-libp2p-tls"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr-net" manet "github.com/multiformats/go-multiaddr-net"
handlers "github.com/gorilla/handlers" handlers "github.com/gorilla/handlers"
@ -91,7 +90,7 @@ type API struct {
server *http.Server server *http.Server
host host.Host host host.Host
httpListener net.Listener httpListeners []net.Listener
libp2pListener net.Listener libp2pListener net.Listener
shutdownLock sync.Mutex shutdownLock sync.Mutex
@ -187,19 +186,19 @@ func NewAPIWithHost(ctx context.Context, cfg *Config, h host.Host) (*API, error)
} }
api.addRoutes(router) api.addRoutes(router)
// Set up api.httpListener if enabled // Set up api.httpListeners if enabled
err = api.setupHTTP() err = api.setupHTTP()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Set up api.libp2pListener if enabled // Set up api.libp2pListeners if enabled
err = api.setupLibp2p() err = api.setupLibp2p()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if api.httpListener == nil && api.libp2pListener == nil { if len(api.httpListeners) == 0 && api.libp2pListener == nil {
return nil, ErrNoEndpointsEnabled return nil, ErrNoEndpointsEnabled
} }
@ -208,11 +207,12 @@ func NewAPIWithHost(ctx context.Context, cfg *Config, h host.Host) (*API, error)
} }
func (api *API) setupHTTP() error { func (api *API) setupHTTP() error {
if api.config.HTTPListenAddr == nil { if len(api.config.HTTPListenAddr) == 0 {
return nil return nil
} }
n, addr, err := manet.DialArgs(api.config.HTTPListenAddr) for _, listenMAddr := range api.config.HTTPListenAddr {
n, addr, err := manet.DialArgs(listenMAddr)
if err != nil { if err != nil {
return err return err
} }
@ -226,21 +226,22 @@ func (api *API) setupHTTP() error {
if err != nil { if err != nil {
return err return err
} }
api.httpListener = l api.httpListeners = append(api.httpListeners, l)
}
return nil return nil
} }
func (api *API) setupLibp2p() error { func (api *API) setupLibp2p() error {
// Make new host. Override any provided existing one // Make new host. Override any provided existing one
// if we have config for a custom one. // if we have config for a custom one.
if api.config.Libp2pListenAddr != nil { if len(api.config.Libp2pListenAddr) > 0 {
// We use a new host context. We will call // We use a new host context. We will call
// Close() on shutdown(). Avoids things like: // Close() on shutdown(). Avoids things like:
// https://github.com/ipfs/ipfs-cluster/issues/853 // https://github.com/ipfs/ipfs-cluster/issues/853
h, err := libp2p.New( h, err := libp2p.New(
context.Background(), context.Background(),
libp2p.Identity(api.config.PrivateKey), libp2p.Identity(api.config.PrivateKey),
libp2p.ListenAddrs([]ma.Multiaddr{api.config.Libp2pListenAddr}...), libp2p.ListenAddrs(api.config.Libp2pListenAddr...),
libp2p.Security(libp2ptls.ID, libp2ptls.New), libp2p.Security(libp2ptls.ID, libp2ptls.New),
libp2p.Security(secio.ID, secio.New), libp2p.Security(secio.ID, secio.New),
libp2p.Transport(libp2pquic.NewTransport), libp2p.Transport(libp2pquic.NewTransport),
@ -264,15 +265,20 @@ func (api *API) setupLibp2p() error {
return nil return nil
} }
// HTTPAddress returns the HTTP(s) listening address // HTTPAddresses returns the HTTP(s) listening address
// in host:port format. Useful when configured to start // in host:port format. Useful when configured to start
// on a random port (0). Returns error when the HTTP endpoint // on a random port (0). Returns error when the HTTP endpoint
// is not enabled. // is not enabled.
func (api *API) HTTPAddress() (string, error) { func (api *API) HTTPAddresses() ([]string, error) {
if api.httpListener == nil { if len(api.httpListeners) == 0 {
return "", ErrHTTPEndpointNotEnabled return nil, ErrHTTPEndpointNotEnabled
} }
return api.httpListener.Addr().String(), nil var addrs []string
for _, l := range api.httpListeners {
addrs = append(addrs, l.Addr().String())
}
return addrs, nil
} }
// Host returns the libp2p Host used by the API, if any. // Host returns the libp2p Host used by the API, if any.
@ -479,28 +485,38 @@ func (api *API) routes() []route {
} }
func (api *API) run(ctx context.Context) { func (api *API) run(ctx context.Context) {
if api.httpListener != nil { api.wg.Add(len(api.httpListeners))
api.wg.Add(1) for _, l := range api.httpListeners {
go api.runHTTPServer(ctx) go func(l net.Listener) {
defer api.wg.Done()
api.runHTTPServer(ctx, l)
}(l)
} }
if api.libp2pListener != nil { if api.libp2pListener != nil {
api.wg.Add(1) api.wg.Add(1)
go api.runLibp2pServer(ctx) go func() {
defer api.wg.Done()
api.runLibp2pServer(ctx)
}()
} }
} }
// runs in goroutine from run() // runs in goroutine from run()
func (api *API) runHTTPServer(ctx context.Context) { func (api *API) runHTTPServer(ctx context.Context, l net.Listener) {
defer api.wg.Done()
select { select {
case <-api.rpcReady: case <-api.rpcReady:
case <-api.ctx.Done(): case <-api.ctx.Done():
return return
} }
logger.Infof("REST API (HTTP): %s", api.config.HTTPListenAddr) maddr, err := manet.FromNetAddr(l.Addr())
err := api.server.Serve(api.httpListener) if err != nil {
logger.Error(err)
}
logger.Infof("REST API (HTTP): %s", maddr)
err = api.server.Serve(l)
if err != nil && !strings.Contains(err.Error(), "closed network connection") { if err != nil && !strings.Contains(err.Error(), "closed network connection") {
logger.Error(err) logger.Error(err)
} }
@ -508,8 +524,6 @@ func (api *API) runHTTPServer(ctx context.Context) {
// runs in goroutine from run() // runs in goroutine from run()
func (api *API) runLibp2pServer(ctx context.Context) { func (api *API) runLibp2pServer(ctx context.Context) {
defer api.wg.Done()
select { select {
case <-api.rpcReady: case <-api.rpcReady:
case <-api.ctx.Done(): case <-api.ctx.Done():
@ -550,9 +564,10 @@ func (api *API) Shutdown(ctx context.Context) error {
// Cancel any outstanding ops // Cancel any outstanding ops
api.server.SetKeepAlivesEnabled(false) api.server.SetKeepAlivesEnabled(false)
if api.httpListener != nil { for _, l := range api.httpListeners {
api.httpListener.Close() l.Close()
} }
if api.libp2pListener != nil { if api.libp2pListener != nil {
api.libp2pListener.Close() api.libp2pListener.Close()
} }

View File

@ -50,7 +50,7 @@ func testAPIwithConfig(t *testing.T, cfg *Config, name string) *API {
t.Fatal(err) t.Fatal(err)
} }
cfg.HTTPListenAddr = apiMAddr cfg.HTTPListenAddr = []ma.Multiaddr{apiMAddr}
rest, err := NewAPIWithHost(ctx, cfg, h) rest, err := NewAPIWithHost(ctx, cfg, h)
if err != nil { if err != nil {
@ -174,8 +174,8 @@ func makeHost(t *testing.T, rest *API) host.Host {
type urlF func(a *API) string type urlF func(a *API) string
func httpURL(a *API) string { func httpURL(a *API) string {
u, _ := a.HTTPAddress() u, _ := a.HTTPAddresses()
return fmt.Sprintf("http://%s", u) return fmt.Sprintf("http://%s", u[0])
} }
func p2pURL(a *API) string { func p2pURL(a *API) string {
@ -183,8 +183,8 @@ func p2pURL(a *API) string {
} }
func httpsURL(a *API) string { func httpsURL(a *API) string {
u, _ := a.HTTPAddress() u, _ := a.HTTPAddresses()
return fmt.Sprintf("https://%s", u) return fmt.Sprintf("https://%s", u[0])
} }
func isHTTPS(url string) bool { func isHTTPS(url string) bool {

View File

@ -22,8 +22,11 @@ import (
const configKey = "cluster" const configKey = "cluster"
// DefaultListenAddrs contains TCP and QUIC listen addresses // DefaultListenAddrs contains TCP and QUIC listen addresses.
var DefaultListenAddrs = []string{"/ip4/0.0.0.0/tcp/9096", "/ip4/0.0.0.0/udp/9096/quic"} var DefaultListenAddrs = []string{
"/ip4/0.0.0.0/tcp/9096",
"/ip4/0.0.0.0/udp/9096/quic",
}
// Configuration defaults // Configuration defaults
const ( const (

View File

@ -22,6 +22,7 @@ import (
"github.com/ipfs/ipfs-cluster/monitor/pubsubmon" "github.com/ipfs/ipfs-cluster/monitor/pubsubmon"
"github.com/ipfs/ipfs-cluster/observations" "github.com/ipfs/ipfs-cluster/observations"
"github.com/ipfs/ipfs-cluster/pintracker/stateless" "github.com/ipfs/ipfs-cluster/pintracker/stateless"
"github.com/multiformats/go-multiaddr"
"github.com/pkg/errors" "github.com/pkg/errors"
cli "github.com/urfave/cli/v2" cli "github.com/urfave/cli/v2"
) )
@ -301,7 +302,7 @@ func runCmd(c *cli.Context) error {
if err != nil { if err != nil {
return cli.Exit(err, 1) return cli.Exit(err, 1)
} }
apiCfg.HTTPListenAddr = listenSocket apiCfg.HTTPListenAddr = []multiaddr.Multiaddr{listenSocket}
// Allow customization via env vars // Allow customization via env vars
err = apiCfg.ApplyEnvVars() err = apiCfg.ApplyEnvVars()
if err != nil { if err != nil {

View File

@ -328,12 +328,11 @@ the peer IDs in the given multiaddresses.
// Generate defaults for all registered components // Generate defaults for all registered components
err := cfgHelper.Manager().Default() err := cfgHelper.Manager().Default()
checkErr("generating default configuration", err) checkErr("generating default configuration", err)
if c.Bool("randomports") { if c.Bool("randomports") {
cfgs := cfgHelper.Configs() cfgs := cfgHelper.Configs()
for i := range cfgs.Cluster.ListenAddr { cfgs.Cluster.ListenAddr, err = cmdutils.RandomizePorts(cfgs.Cluster.ListenAddr)
cfgs.Cluster.ListenAddr[i], err = cmdutils.RandomizePorts(cfgs.Cluster.ListenAddr[i])
}
checkErr("randomizing ports", err) checkErr("randomizing ports", err)
cfgs.Restapi.HTTPListenAddr, err = cmdutils.RandomizePorts(cfgs.Restapi.HTTPListenAddr) cfgs.Restapi.HTTPListenAddr, err = cmdutils.RandomizePorts(cfgs.Restapi.HTTPListenAddr)
checkErr("randomizing ports", err) checkErr("randomizing ports", err)

View File

@ -9,25 +9,30 @@ import (
) )
func TestRandomPorts(t *testing.T) { func TestRandomPorts(t *testing.T) {
port := "9096"
m1, _ := ma.NewMultiaddr("/ip4/0.0.0.0/tcp/9096") m1, _ := ma.NewMultiaddr("/ip4/0.0.0.0/tcp/9096")
m2, _ := ma.NewMultiaddr("/ip4/0.0.0.0/tcp/9096") m2, _ := ma.NewMultiaddr("/ip6/::/udp/9096")
m1, err := cmdutils.RandomizePorts(m1) addresses, err := cmdutils.RandomizePorts([]ma.Multiaddr{m1, m2})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
v1, err := m1.ValueForProtocol(ma.P_TCP) v1, err := addresses[0].ValueForProtocol(ma.P_TCP)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
v2, err := m2.ValueForProtocol(ma.P_TCP) v2, err := addresses[1].ValueForProtocol(ma.P_UDP)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if v1 == v2 { if v1 == port {
t.Error("expected different ports") t.Error("expected different ipv4 ports")
}
if v2 == port {
t.Error("expected different ipv6 ports")
} }
} }

View File

@ -5,9 +5,11 @@ package cmdutils
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
@ -20,10 +22,12 @@ import (
) )
// RandomizePorts replaces TCP and UDP ports with random, but valid port // RandomizePorts replaces TCP and UDP ports with random, but valid port
// values. // values, on the given multiaddresses
func RandomizePorts(m ma.Multiaddr) (ma.Multiaddr, error) { func RandomizePorts(addrs []ma.Multiaddr) ([]ma.Multiaddr, error) {
var prev string results := make([]ma.Multiaddr, 0, len(addrs))
for _, m := range addrs {
var prev string
var err error var err error
components := []ma.Multiaddr{} components := []ma.Multiaddr{}
ma.ForEach(m, func(c ma.Component) bool { ma.ForEach(m, func(c ma.Component) bool {
@ -35,15 +39,26 @@ func RandomizePorts(m ma.Multiaddr) (ma.Multiaddr, error) {
return true return true
} }
var ln net.Listener var ln io.Closer
ln, err = net.Listen(c.Protocol().Name, prev+":") var port int
ip := prev
if strings.Contains(ip, ":") { // ipv6 needs bracketing
ip = "[" + ip + "]"
}
if c.Protocol().Code == ma.P_UDP {
ln, port, err = listenUDP(c.Protocol().Name, ip)
} else {
ln, port, err = listenTCP(c.Protocol().Name, ip)
}
if err != nil { if err != nil {
return false return false
} }
defer ln.Close() defer ln.Close()
var c1 *ma.Component var c1 *ma.Component
c1, err = ma.NewComponent(c.Protocol().Name, fmt.Sprintf("%d", getPort(ln, code))) c1, err = ma.NewComponent(c.Protocol().Name, fmt.Sprintf("%d", port))
if err != nil { if err != nil {
return false return false
} }
@ -53,18 +68,33 @@ func RandomizePorts(m ma.Multiaddr) (ma.Multiaddr, error) {
return true return true
}) })
if err != nil {
return results, err
}
results = append(results, ma.Join(components...))
}
return ma.Join(components...), err return results, nil
} }
func getPort(ln net.Listener, code int) int { // returns the listener so it can be closed later and port
if code == ma.P_TCP { func listenTCP(name, ip string) (io.Closer, int, error) {
return ln.Addr().(*net.TCPAddr).Port ln, err := net.Listen(name, ip+":0")
if err != nil {
return nil, 0, err
} }
if code == ma.P_UDP {
return ln.Addr().(*net.UDPAddr).Port return ln, ln.Addr().(*net.TCPAddr).Port, nil
}
// returns the listener so it can be cloesd later and port
func listenUDP(name, ip string) (io.Closer, int, error) {
ln, err := net.ListenPacket(name, ip+":0")
if err != nil {
return nil, 0, err
} }
return 0
return ln, ln.LocalAddr().(*net.UDPAddr).Port, nil
} }
// HandleSignals orderly shuts down an IPFS Cluster peer // HandleSignals orderly shuts down an IPFS Cluster peer

View File

@ -181,9 +181,9 @@ func createComponents(
clusterCfg.LeaveOnShutdown = false clusterCfg.LeaveOnShutdown = false
clusterCfg.SetBaseDir(filepath.Join(testsFolder, host.ID().Pretty())) clusterCfg.SetBaseDir(filepath.Join(testsFolder, host.ID().Pretty()))
apiCfg.HTTPListenAddr = apiAddr apiCfg.HTTPListenAddr = []ma.Multiaddr{apiAddr}
ipfsproxyCfg.ListenAddr = proxyAddr ipfsproxyCfg.ListenAddr = []ma.Multiaddr{proxyAddr}
ipfsproxyCfg.NodeAddr = nodeAddr ipfsproxyCfg.NodeAddr = nodeAddr
ipfshttpCfg.NodeAddr = nodeAddr ipfshttpCfg.NodeAddr = nodeAddr

View File

@ -56,8 +56,8 @@ func TestIPFSID(t *testing.T) {
if id.ID != test.PeerID1 { if id.ID != test.PeerID1 {
t.Error("expected testPeerID") t.Error("expected testPeerID")
} }
if len(id.Addresses) != 1 { if len(id.Addresses) != 2 {
t.Error("expected 1 address") t.Error("expected 2 address")
} }
if id.Error != "" { if id.Error != "" {
t.Error("expected no error") t.Error("expected no error")

View File

@ -3,7 +3,10 @@
"peername": "testname", "peername": "testname",
"secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641", "secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641",
"leave_on_shutdown": false, "leave_on_shutdown": false,
"listen_multiaddress": "/ip4/0.0.0.0/tcp/9096", "listen_multiaddress": [
"/ip4/0.0.0.0/tcp/9096",
"/ip6/::/tcp/9096"
],
"state_sync_interval": "1m0s", "state_sync_interval": "1m0s",
"replication_factor": -1, "replication_factor": -1,
"monitor_ping_interval": "15s" "monitor_ping_interval": "15s"

View File

@ -3,7 +3,10 @@
"peername": "testname", "peername": "testname",
"secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641", "secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641",
"leave_on_shutdown": false, "leave_on_shutdown": false,
"listen_multiaddress": "/ip4/0.0.0.0/tcp/9096", "listen_multiaddress": [
"/ip4/0.0.0.0/tcp/9096",
"/ip6/::/tcp/9096"
],
"state_sync_interval": "1m0s", "state_sync_interval": "1m0s",
"replication_factor": -1, "replication_factor": -1,
"monitor_ping_interval": "15s" "monitor_ping_interval": "15s"

View File

@ -5,7 +5,10 @@
"private_key": "CAASqAkwggSkAgEAAoIBAQC/ZmfWDbwyI0nJdRxgHcTdEaBFQo8sky9E+OOvtwZa5WKoLdHyHOLWxCAdpIHUBbhxz5rkMEWLwPI6ykqLIJToMPO8lJbKVzphOjv4JwpiAPdmeSiYMKLjx5V8MpqU2rwj/Uf3sRL8Gg9/Tei3PZ8cftxN1rkQQeeaOtk0CBxUFZSHEsyut1fbgIeL7TAY+4vCmXW0DBr4wh9fnoES/YivOvSiN9rScgWg6N65LfkI78hzaOJ4Nok2S4vYFCxjTAI9NWFUbhP5eJIFzTU+bZuQZxOn2qsoyw8pNZwuF+JClA/RcgBcCvVZcDH2ueVq/zT++bGCN+EWsAEdvJqJ5bsjAgMBAAECggEAaGDUZ6t94mnUJ4UyQEh7v4OJP7wYkFqEAL0qjfzl/lPyBX1XbQ3Ltwul6AR6uMGV4JszARZCFwDWGLGRDWZrTmTDxyfRQ+9l6vfzFFVWGDQmtz+Dn9uGOWnyX5TJMDxJNec+hBmRHOKpaOd37dYxGz0jr19V9UO7piRJp1J1AHUCypUGv5x1IekioSCu5fEyc7dyWwnmITHBjD08st+bCcjrIUFeXSdJKC8SymYeXdaVE3xH3zVEISKnrfT7bhuKZY1iibZIlXbVLNpyX36LkYJOiCqsMum3u70LH0VvTypkqiDbD4S6qfJ4vvUakpmKpOPutikiP7jkSP+AkaO0AQKBgQDkTuhnDK6+Y0a/HgpHJisji0coO+g2gsIszargHk8nNY2AB8t+EUn7C+Qu8cmrem5V8EXcdxS6z7iAXpJmY1Xepnsz+JP7Q91Lgt3OoqK5EybzUXXKkmNCD65n70Xxn2fEFzm6+GJP3c/HymlDKU2KBCYIyuUeaREjT0Fu3v6tgQKBgQDWnXppJwn4LJHhzFOCeO4zomDJDbLTZCabdKZoFP9r+vtEHAnclDDKx4AYbomSqgERe+DX6HR/tPHRVizP63RYPf7al2mJmPzt1nTkoc1/q5hQoD+oE154dADsW1pUp7AQjwCtys4iq5S0qAwIDpuY8M8bOHwZ+QmBvHYAigJCowKBgQC3HH6TX/2rH463bE2MARXqXSPGJj45sigwrQfW1xhe9zm1LQtN4mn2mvP5nt1D1l82OA6gIzYSGtX8x10eF5/ggqAf78goZ6bOkHh76b8fNzgvQO97eGt5qYAVRjhP8azU/lfEGMEpE1s5/6LrRe41utwSg0C+YkBnlIKDfQDAgQKBgDoBTCF5hK9H1JHzuKpt5uubuo78ndWWnvyrNYKyEirsJddNwLiWcO2NqChyT8qNGkbQdX/Fex89F5KduPTlTYfAEc6g18xxxgK+UM+uj60vArbf6PSTb5gculcnha2VuPdwvx050Cb8uu9s7/uJfzKB+2f/B0O51ID1H+ubYWsDAoGBAKrwGKHyqFTHSPg3XuRA1FgDAoOsfzP9ZJvMEXUWyu/VxjNt+0mRlyGeZ5qb9UZG+K/In4FbC/ux2P/PucCUIbgy/XGPtPXVavMwNbx0MquAcU0FihKXP0CUpi8zwiYc42MF7n/SztQnismxigBMSuJEDurcXXazjfcSRTypduNn", "private_key": "CAASqAkwggSkAgEAAoIBAQC/ZmfWDbwyI0nJdRxgHcTdEaBFQo8sky9E+OOvtwZa5WKoLdHyHOLWxCAdpIHUBbhxz5rkMEWLwPI6ykqLIJToMPO8lJbKVzphOjv4JwpiAPdmeSiYMKLjx5V8MpqU2rwj/Uf3sRL8Gg9/Tei3PZ8cftxN1rkQQeeaOtk0CBxUFZSHEsyut1fbgIeL7TAY+4vCmXW0DBr4wh9fnoES/YivOvSiN9rScgWg6N65LfkI78hzaOJ4Nok2S4vYFCxjTAI9NWFUbhP5eJIFzTU+bZuQZxOn2qsoyw8pNZwuF+JClA/RcgBcCvVZcDH2ueVq/zT++bGCN+EWsAEdvJqJ5bsjAgMBAAECggEAaGDUZ6t94mnUJ4UyQEh7v4OJP7wYkFqEAL0qjfzl/lPyBX1XbQ3Ltwul6AR6uMGV4JszARZCFwDWGLGRDWZrTmTDxyfRQ+9l6vfzFFVWGDQmtz+Dn9uGOWnyX5TJMDxJNec+hBmRHOKpaOd37dYxGz0jr19V9UO7piRJp1J1AHUCypUGv5x1IekioSCu5fEyc7dyWwnmITHBjD08st+bCcjrIUFeXSdJKC8SymYeXdaVE3xH3zVEISKnrfT7bhuKZY1iibZIlXbVLNpyX36LkYJOiCqsMum3u70LH0VvTypkqiDbD4S6qfJ4vvUakpmKpOPutikiP7jkSP+AkaO0AQKBgQDkTuhnDK6+Y0a/HgpHJisji0coO+g2gsIszargHk8nNY2AB8t+EUn7C+Qu8cmrem5V8EXcdxS6z7iAXpJmY1Xepnsz+JP7Q91Lgt3OoqK5EybzUXXKkmNCD65n70Xxn2fEFzm6+GJP3c/HymlDKU2KBCYIyuUeaREjT0Fu3v6tgQKBgQDWnXppJwn4LJHhzFOCeO4zomDJDbLTZCabdKZoFP9r+vtEHAnclDDKx4AYbomSqgERe+DX6HR/tPHRVizP63RYPf7al2mJmPzt1nTkoc1/q5hQoD+oE154dADsW1pUp7AQjwCtys4iq5S0qAwIDpuY8M8bOHwZ+QmBvHYAigJCowKBgQC3HH6TX/2rH463bE2MARXqXSPGJj45sigwrQfW1xhe9zm1LQtN4mn2mvP5nt1D1l82OA6gIzYSGtX8x10eF5/ggqAf78goZ6bOkHh76b8fNzgvQO97eGt5qYAVRjhP8azU/lfEGMEpE1s5/6LrRe41utwSg0C+YkBnlIKDfQDAgQKBgDoBTCF5hK9H1JHzuKpt5uubuo78ndWWnvyrNYKyEirsJddNwLiWcO2NqChyT8qNGkbQdX/Fex89F5KduPTlTYfAEc6g18xxxgK+UM+uj60vArbf6PSTb5gculcnha2VuPdwvx050Cb8uu9s7/uJfzKB+2f/B0O51ID1H+ubYWsDAoGBAKrwGKHyqFTHSPg3XuRA1FgDAoOsfzP9ZJvMEXUWyu/VxjNt+0mRlyGeZ5qb9UZG+K/In4FbC/ux2P/PucCUIbgy/XGPtPXVavMwNbx0MquAcU0FihKXP0CUpi8zwiYc42MF7n/SztQnismxigBMSuJEDurcXXazjfcSRTypduNn",
"secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641", "secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641",
"leave_on_shutdown": false, "leave_on_shutdown": false,
"listen_multiaddress": "/ip4/0.0.0.0/tcp/9096", "listen_multiaddress": [
"/ip4/0.0.0.0/tcp/9096",
"/ip6/::/tcp/9096"
],
"state_sync_interval": "1m0s", "state_sync_interval": "1m0s",
"replication_factor": -1, "replication_factor": -1,
"monitor_ping_interval": "15s" "monitor_ping_interval": "15s"

View File

@ -180,6 +180,7 @@ func (m *IpfsMock) handler(w http.ResponseWriter, r *http.Request) {
ID: PeerID1.Pretty(), ID: PeerID1.Pretty(),
Addresses: []string{ Addresses: []string{
"/ip4/0.0.0.0/tcp/1234", "/ip4/0.0.0.0/tcp/1234",
"/ip6/::/tcp/1234",
}, },
} }
j, _ := json.Marshal(resp) j, _ := json.Marshal(resp)