Merge pull request #498 from ipfs/fix/client-resolve-panic
Feat/rest/client: auto-handling of libp2p endpoints
This commit is contained in:
commit
9baffd3d4f
|
@ -2,7 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,6 +20,7 @@ var (
|
||||||
DefaultAPIAddr = "/ip4/127.0.0.1/tcp/9094"
|
DefaultAPIAddr = "/ip4/127.0.0.1/tcp/9094"
|
||||||
DefaultLogLevel = "info"
|
DefaultLogLevel = "info"
|
||||||
DefaultProxyPort = 9095
|
DefaultProxyPort = 9095
|
||||||
|
DefaultPort = 9094
|
||||||
)
|
)
|
||||||
|
|
||||||
var loggingFacility = "apiclient"
|
var loggingFacility = "apiclient"
|
||||||
|
@ -39,20 +39,20 @@ type Config struct {
|
||||||
Password string
|
Password string
|
||||||
|
|
||||||
// The ipfs-cluster REST API endpoint in multiaddress form
|
// The ipfs-cluster REST API endpoint in multiaddress form
|
||||||
// (takes precedence over host:port). Only valid without PeerAddr.
|
// (takes precedence over host:port). It this address contains
|
||||||
|
// an /ipfs/, /p2p/ or /dnsaddr, the API will be contacted
|
||||||
|
// through a libp2p tunnel, thus getting encryption for
|
||||||
|
// free. Using the libp2p tunnel will ignore any configurations.
|
||||||
APIAddr ma.Multiaddr
|
APIAddr ma.Multiaddr
|
||||||
|
|
||||||
|
// PeerAddr is deprecated. It's aliased to APIAddr
|
||||||
|
PeerAddr ma.Multiaddr
|
||||||
|
|
||||||
// REST API endpoint host and port. Only valid without
|
// REST API endpoint host and port. Only valid without
|
||||||
// APIAddr and PeerAddr
|
// APIAddr and PeerAddr
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
|
|
||||||
// The ipfs-cluster REST API peer address (usually
|
|
||||||
// the same as the cluster peer). This will use libp2p
|
|
||||||
// to tunnel HTTP requests, thus getting encryption for
|
|
||||||
// free. It overseeds APIAddr, Host/Port, and SSL configurations.
|
|
||||||
PeerAddr ma.Multiaddr
|
|
||||||
|
|
||||||
// If PeerAddr is provided, and the peer uses private networks
|
// If PeerAddr is provided, and the peer uses private networks
|
||||||
// (pnet), then we need to provide the key. If the peer is the
|
// (pnet), then we need to provide the key. If the peer is the
|
||||||
// cluster peer, this corresponds to the cluster secret.
|
// cluster peer, this corresponds to the cluster secret.
|
||||||
|
@ -60,7 +60,7 @@ type Config struct {
|
||||||
|
|
||||||
// ProxyAddr is used to obtain a go-ipfs-api Shell instance pointing
|
// ProxyAddr is used to obtain a go-ipfs-api Shell instance pointing
|
||||||
// to the ipfs proxy endpoint of ipfs-cluster. If empty, the location
|
// to the ipfs proxy endpoint of ipfs-cluster. If empty, the location
|
||||||
// will be guessed from one of PeerAddr/APIAddr/Host,
|
// will be guessed from one of APIAddr/Host,
|
||||||
// and the port used will be ipfs-cluster's proxy default port (9095)
|
// and the port used will be ipfs-cluster's proxy default port (9095)
|
||||||
ProxyAddr ma.Multiaddr
|
ProxyAddr ma.Multiaddr
|
||||||
|
|
||||||
|
@ -100,7 +100,25 @@ func NewClient(cfg *Config) (*Client, error) {
|
||||||
client.config.Timeout = DefaultTimeout
|
client.config.Timeout = DefaultTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.setupHTTPClient()
|
if paddr := client.config.PeerAddr; paddr != nil {
|
||||||
|
client.config.APIAddr = paddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.config.Port == "" {
|
||||||
|
client.config.Port = fmt.Sprintf("%d", DefaultPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := client.setupAPIAddr()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.resolveAPIAddr()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.setupHTTPClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -124,11 +142,44 @@ func NewClient(cfg *Config) (*Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) setupAPIAddr() error {
|
||||||
|
var addr ma.Multiaddr
|
||||||
|
var err error
|
||||||
|
if c.config.APIAddr == nil {
|
||||||
|
if c.config.Host == "" { //default
|
||||||
|
addr, err = ma.NewMultiaddr(DefaultAPIAddr)
|
||||||
|
} else {
|
||||||
|
addrStr := fmt.Sprintf("/dns4/%s/tcp/%s", c.config.Host, c.config.Port)
|
||||||
|
addr, err = ma.NewMultiaddr(addrStr)
|
||||||
|
}
|
||||||
|
c.config.APIAddr = addr
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) resolveAPIAddr() error {
|
||||||
|
resolveCtx, cancel := context.WithTimeout(c.ctx, c.config.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
resolved, err := madns.Resolve(resolveCtx, c.config.APIAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resolved) == 0 {
|
||||||
|
return fmt.Errorf("resolving %s returned 0 results", c.config.APIAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.config.APIAddr = resolved[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) setupHTTPClient() error {
|
func (c *Client) setupHTTPClient() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case c.config.PeerAddr != nil:
|
case IsPeerAddress(c.config.APIAddr):
|
||||||
err = c.enableLibp2p()
|
err = c.enableLibp2p()
|
||||||
case c.config.SSL:
|
case c.config.SSL:
|
||||||
err = c.enableTLS()
|
err = c.enableTLS()
|
||||||
|
@ -148,43 +199,14 @@ func (c *Client) setupHTTPClient() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) setupHostname() error {
|
func (c *Client) setupHostname() error {
|
||||||
// When no host/port/multiaddress defined, we set the default
|
// Extract host:port form APIAddr or use Host:Port.
|
||||||
if c.config.APIAddr == nil && c.config.Host == "" && c.config.Port == "" {
|
// For libp2p, hostname is set in enableLibp2p()
|
||||||
var err error
|
if !IsPeerAddress(c.config.APIAddr) {
|
||||||
c.config.APIAddr, err = ma.NewMultiaddr(DefaultAPIAddr)
|
_, hostname, err := manet.DialArgs(c.config.APIAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
c.hostname = hostname
|
||||||
|
|
||||||
// PeerAddr takes precedence over APIAddr. APIAddr takes precedence
|
|
||||||
// over Host/Port. APIAddr is resolved and dial args
|
|
||||||
// extracted.
|
|
||||||
switch {
|
|
||||||
case c.config.PeerAddr != nil:
|
|
||||||
// Taken care of in setupHTTPClient
|
|
||||||
case c.config.APIAddr != nil:
|
|
||||||
// Resolve multiaddress just in case and extract host:port
|
|
||||||
resolveCtx, cancel := context.WithTimeout(c.ctx, c.config.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
resolved, err := madns.Resolve(resolveCtx, c.config.APIAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.config.APIAddr = resolved[0]
|
|
||||||
_, c.hostname, err = manet.DialArgs(c.config.APIAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
c.hostname = fmt.Sprintf("%s:%s", c.config.Host, c.config.Port)
|
|
||||||
apiAddr, err := ma.NewMultiaddr(
|
|
||||||
fmt.Sprintf("/ip4/%s/tcp/%s", c.config.Host, c.config.Port),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.config.APIAddr = apiAddr
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -194,29 +216,12 @@ func (c *Client) setupProxy() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guess location from PeerAddr or APIAddr
|
// Guess location from APIAddr
|
||||||
port, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", DefaultProxyPort))
|
port, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", DefaultProxyPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var paddr ma.Multiaddr
|
c.config.ProxyAddr = ma.Split(c.config.APIAddr)[0].Encapsulate(port)
|
||||||
switch {
|
|
||||||
case c.config.PeerAddr != nil:
|
|
||||||
paddr = ma.Split(c.config.PeerAddr)[0].Encapsulate(port)
|
|
||||||
case c.config.APIAddr != nil: // Host/Port setupHostname sets APIAddr
|
|
||||||
paddr = ma.Split(c.config.APIAddr)[0].Encapsulate(port)
|
|
||||||
default:
|
|
||||||
return errors.New("cannot find proxy address")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(c.ctx, c.config.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
resolved, err := madns.Resolve(ctx, paddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.config.ProxyAddr = resolved[0]
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,3 +232,14 @@ func (c *Client) setupProxy() error {
|
||||||
func (c *Client) IPFS() *shell.Shell {
|
func (c *Client) IPFS() *shell.Shell {
|
||||||
return shell.NewShellWithClient(c.config.ProxyAddr.String(), c.client)
|
return shell.NewShellWithClient(c.config.ProxyAddr.String(), c.client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPeerAddress detects if the given multiaddress identifies a libp2p peer,
|
||||||
|
// either because it has the /p2p/ protocol or because it uses /dnsaddr/
|
||||||
|
func IsPeerAddress(addr ma.Multiaddr) bool {
|
||||||
|
if addr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pid, err := addr.ValueForProtocol(ma.P_IPFS)
|
||||||
|
dnsaddr, err2 := addr.ValueForProtocol(madns.DnsaddrProtocol.Code)
|
||||||
|
return (pid != "" && err == nil) || (dnsaddr != "" && err2 == nil)
|
||||||
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ func testClientHTTP(t *testing.T, api *rest.API) *Client {
|
||||||
|
|
||||||
func testClientLibp2p(t *testing.T, api *rest.API) *Client {
|
func testClientLibp2p(t *testing.T, api *rest.API) *Client {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
PeerAddr: peerMAddr(api),
|
APIAddr: peerMAddr(api),
|
||||||
ProtectorKey: make([]byte, 32),
|
ProtectorKey: make([]byte, 32),
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
@ -187,14 +187,12 @@ func TestDNSMultiaddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerAddress(t *testing.T) {
|
func TestPeerAddress(t *testing.T) {
|
||||||
addr2, _ := ma.NewMultiaddr("/dns4/localhost/tcp/1234")
|
|
||||||
peerAddr, _ := ma.NewMultiaddr("/dns4/localhost/tcp/1234/ipfs/QmP7R7gWEnruNePxmCa9GBa4VmUNexLVnb1v47R8Gyo3LP")
|
peerAddr, _ := ma.NewMultiaddr("/dns4/localhost/tcp/1234/ipfs/QmP7R7gWEnruNePxmCa9GBa4VmUNexLVnb1v47R8Gyo3LP")
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
APIAddr: addr2,
|
APIAddr: peerAddr,
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
Port: "9094",
|
Port: "9094",
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
PeerAddr: peerAddr,
|
|
||||||
}
|
}
|
||||||
c, err := NewClient(cfg)
|
c, err := NewClient(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (c *Client) defaultTransport() {
|
||||||
func (c *Client) enableLibp2p() error {
|
func (c *Client) enableLibp2p() error {
|
||||||
c.defaultTransport()
|
c.defaultTransport()
|
||||||
|
|
||||||
pid, addr, err := api.Libp2pMultiaddrSplit(c.config.PeerAddr)
|
pid, addr, err := api.Libp2pMultiaddrSplit(c.config.APIAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,24 +140,16 @@ requires authorization. implies --https, which you can disable with --force-http
|
||||||
addr, err := ma.NewMultiaddr(c.String("host"))
|
addr, err := ma.NewMultiaddr(c.String("host"))
|
||||||
checkErr("parsing host multiaddress", err)
|
checkErr("parsing host multiaddress", err)
|
||||||
|
|
||||||
// Is this a peer address?
|
cfg.APIAddr = addr
|
||||||
pid, err := addr.ValueForProtocol(ma.P_IPFS)
|
if hexSecret := c.String("secret"); hexSecret != "" {
|
||||||
if pid != "" && err == nil {
|
secret, err := hex.DecodeString(hexSecret)
|
||||||
logger.Debugf("Using libp2p-http to %s", addr)
|
checkErr("parsing secret", err)
|
||||||
cfg.PeerAddr = addr
|
cfg.ProtectorKey = secret
|
||||||
if hexSecret := c.String("secret"); hexSecret != "" {
|
|
||||||
secret, err := hex.DecodeString(hexSecret)
|
|
||||||
checkErr("parsing secret", err)
|
|
||||||
cfg.ProtectorKey = secret
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Debugf("Using http(s) to %s", addr)
|
|
||||||
cfg.APIAddr = addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Timeout = time.Duration(c.Int("timeout")) * time.Second
|
cfg.Timeout = time.Duration(c.Int("timeout")) * time.Second
|
||||||
|
|
||||||
if cfg.PeerAddr != nil && c.Bool("https") {
|
if client.IsPeerAddress(cfg.APIAddr) && c.Bool("https") {
|
||||||
logger.Warning("Using libp2p-http. SSL flags will be ignored")
|
logger.Warning("Using libp2p-http. SSL flags will be ignored")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user