@cryptotaxi247 / kubo / commits / 460c2119e

daemon: support unix domain sockets for the API/gateway

All the work was client-side. Unix domain socket multiaddrs already worked server-side. fixes #4218

Steven Allen committed Sep 25, 2019 at 18:41 UTC 460c2119e4ccbe0d08e64a04be4445c78a72c851
10 files changed +92 -58
cmd/ipfs/daemon.go
+6 -2
@@ -416,7 +416,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
416 // Setting to 1 lets us multiply it with other stats to add the version labels
417 ipfsInfoMetric.With(prometheus.Labels{
418 "version": version.CurrentVersionNumber,
419 - "commit": version.CurrentCommit,
419 + "commit": version.CurrentCommit,
420 }).Set(1)
421
422 // initialize metrics collector
@@ -492,7 +492,11 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error
492 for _, listener := range listeners {
493 // we might have listened to /tcp/0 - lets see what we are listing on
494 fmt.Printf("API server listening on %s\n", listener.Multiaddr())
495 - fmt.Printf("WebUI: http://%s/webui\n", listener.Addr())
495 + // Browsers require TCP.
496 + switch listener.Addr().Network() {
497 + case "tcp", "tcp4", "tcp6":
498 + fmt.Printf("WebUI: http://%s/webui\n", listener.Addr())
499 + }
500 }
501
502 // by default, we don't let you load arbitrary ipfs objects through the api,
cmd/ipfs/main.go
+24 -6
@@ -6,6 +6,8 @@ import (
6 "errors"
7 "fmt"
8 "math/rand"
9 + "net"
10 + "net/http"
11 "os"
12 "runtime/pprof"
13 "strings"
@@ -22,7 +24,7 @@ import (
24
25 "github.com/ipfs/go-ipfs-cmds"
26 "github.com/ipfs/go-ipfs-cmds/cli"
25 - "github.com/ipfs/go-ipfs-cmds/http"
27 + cmdhttp "github.com/ipfs/go-ipfs-cmds/http"
28 "github.com/ipfs/go-ipfs-config"
29 u "github.com/ipfs/go-ipfs-util"
30 logging "github.com/ipfs/go-log"
@@ -249,23 +251,39 @@ func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) {
251 if err != nil {
252 return nil, err
253 }
252 - _, host, err := manet.DialArgs(apiAddr)
254 + network, host, err := manet.DialArgs(apiAddr)
255 if err != nil {
256 return nil, err
257 }
258
259 // Construct the executor.
258 - opts := []http.ClientOpt{
259 - http.ClientWithAPIPrefix(corehttp.APIPath),
260 + opts := []cmdhttp.ClientOpt{
261 + cmdhttp.ClientWithAPIPrefix(corehttp.APIPath),
262 }
263
264 // Fallback on a local executor if we (a) have a repo and (b) aren't
265 // forcing a daemon.
266 if !daemonRequested && fsrepo.IsInitialized(cctx.ConfigRoot) {
265 - opts = append(opts, http.ClientWithFallback(exe))
267 + opts = append(opts, cmdhttp.ClientWithFallback(exe))
268 + }
269 +
270 + switch network {
271 + case "tcp", "tcp4", "tcp6":
272 + case "unix":
273 + path := host
274 + host = "unix"
275 + opts = append(opts, cmdhttp.ClientWithHTTPClient(&http.Client{
276 + Transport: &http.Transport{
277 + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
278 + return net.Dial("unix", path)
279 + },
280 + },
281 + }))
282 + default:
283 + return nil, fmt.Errorf("unsupported API address: %s", apiAddr)
284 }
285
268 - return http.NewClient(host, opts...), nil
286 + return cmdhttp.NewClient(host, opts...), nil
287 }
288
289 // commandDetails returns a command's details for the command given by |path|.
docs/config.md
+16
@@ -77,16 +77,32 @@ Contains information about various listener addresses to be used by this node.
77 - `API`
78 Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on.
79
80 +Supported Transports:
81 +
82 +* tcp/ip{4,6} - `/ipN/.../tcp/...`
83 +* unix - `/unix/path/to/socket`
84 +
85 Default: `/ip4/127.0.0.1/tcp/5001`
86
87 - `Gateway`
88 Multiaddr or array of multiaddrs describing the address to serve the local gateway on.
89
90 +Supported Transports:
91 +
92 +* tcp/ip{4,6} - `/ipN/.../tcp/...`
93 +* unix - `/unix/path/to/socket`
94 +
95 Default: `/ip4/127.0.0.1/tcp/8080`
96
97 - `Swarm`
98 Array of multiaddrs describing which addresses to listen on for p2p swarm connections.
99
100 +Supported Transports:
101 +
102 +* tcp/ip{4,6} - `/ipN/.../tcp/...`
103 +* websocket - `/ipN/.../tcp/.../ws`
104 +* quic - `/ipN/.../udp/.../quic`
105 +
106 Default:
107 ```json
108 [
go.mod
+1 -1
@@ -29,7 +29,7 @@ require (
29 github.com/ipfs/go-fs-lock v0.0.1
30 github.com/ipfs/go-ipfs-blockstore v0.1.0
31 github.com/ipfs/go-ipfs-chunker v0.0.1
32 - github.com/ipfs/go-ipfs-cmds v0.1.0
32 + github.com/ipfs/go-ipfs-cmds v0.1.1
33 github.com/ipfs/go-ipfs-config v0.0.11
34 github.com/ipfs/go-ipfs-ds-help v0.0.1
35 github.com/ipfs/go-ipfs-exchange-interface v0.0.1
go.sum
+4 -4
@@ -183,8 +183,8 @@ github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IW
183 github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk=
184 github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE27SEw=
185 github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw=
186 -github.com/ipfs/go-ipfs-cmds v0.1.0 h1:0CEde9EcxByej8+L6d1PST57J4ambRPyCTjLG5Ymou8=
187 -github.com/ipfs/go-ipfs-cmds v0.1.0/go.mod h1:TiK4e7/V31tuEb8YWDF8lN3qrnDH+BS7ZqWIeYJlAs8=
186 +github.com/ipfs/go-ipfs-cmds v0.1.1 h1:H9/BLf5rcsULHMj/x8gC0e5o+raYhqk1OQsfzbGMNM4=
187 +github.com/ipfs/go-ipfs-cmds v0.1.1/go.mod h1:k1zMXcOLtljA9iAnZHddbH69yVm5+weRL0snmMD/rK0=
188 github.com/ipfs/go-ipfs-config v0.0.11 h1:5/4nas2CQXiKr2/MLxU24GDGTBvtstQIQezuk7ltOQQ=
189 github.com/ipfs/go-ipfs-config v0.0.11/go.mod h1:wveA8UT5ywN26oKStByzmz1CO6cXwLKKM6Jn/Hfw08I=
190 github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
@@ -596,8 +596,8 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
596 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
597 github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
598 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
599 -github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
600 -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
599 +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
600 +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
601 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
602 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
603 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
test/dependencies/pollEndpoint/main.go
+8 -42
@@ -3,10 +3,6 @@ package main
3
4 import (
5 "flag"
6 - "fmt"
7 - "io/ioutil"
8 - "net/http"
9 - "net/url"
6 "os"
7 "time"
8
@@ -16,11 +12,10 @@ import (
12 )
13
14 var (
19 - host = flag.String("host", "/ip4/127.0.0.1/tcp/5001", "the multiaddr host to dial on")
20 - endpoint = flag.String("ep", "/version", "which http endpoint path to hit")
21 - tries = flag.Int("tries", 10, "how many tries to make before failing")
22 - timeout = flag.Duration("tout", time.Second, "how long to wait between attempts")
23 - verbose = flag.Bool("v", false, "verbose logging")
15 + host = flag.String("host", "/ip4/127.0.0.1/tcp/5001", "the multiaddr host to dial on")
16 + tries = flag.Int("tries", 10, "how many tries to make before failing")
17 + timeout = flag.Duration("tout", time.Second, "how long to wait between attempts")
18 + verbose = flag.Bool("v", false, "verbose logging")
19 )
20
21 var log = logging.Logger("pollEndpoint")
@@ -33,37 +28,23 @@ func main() {
28 if err != nil {
29 log.Fatal("NewMultiaddr() failed: ", err)
30 }
36 - p := addr.Protocols()
37 - if len(p) < 2 {
38 - log.Fatal("need two protocols in host flag (/ip/tcp): ", addr)
39 - }
40 - _, host, err := manet.DialArgs(addr)
41 - if err != nil {
42 - log.Fatal("manet.DialArgs() failed: ", err)
43 - }
31
32 if *verbose { // lower log level
33 logging.SetDebugLogging()
34 }
35
49 - // construct url to dial
50 - var u url.URL
51 - u.Scheme = "http"
52 - u.Host = host
53 - u.Path = *endpoint
54 -
36 // show what we got
37 start := time.Now()
57 - log.Debugf("starting at %s, tries: %d, timeout: %s, url: %s", start, *tries, *timeout, u)
38 + log.Debugf("starting at %s, tries: %d, timeout: %s, addr: %s", start, *tries, *timeout, addr)
39
40 for *tries > 0 {
60 -
61 - err := checkOK(http.Get(u.String()))
41 + c, err := manet.Dial(addr)
42 if err == nil {
43 log.Debugf("ok - endpoint reachable with %d tries remaining, took %s", *tries, time.Since(start))
44 + c.Close()
45 os.Exit(0)
46 }
66 - log.Debug("get failed: ", err)
47 + log.Debug("connect failed: ", err)
48 time.Sleep(*timeout)
49 *tries--
50 }
@@ -71,18 +52,3 @@ func main() {
52 log.Error("failed.")
53 os.Exit(1)
54 }
74 -
75 -func checkOK(resp *http.Response, err error) error {
76 - if err == nil { // request worked
77 - defer resp.Body.Close()
78 - if resp.StatusCode == http.StatusOK {
79 - return nil
80 - }
81 - body, err := ioutil.ReadAll(resp.Body)
82 - if err != nil {
83 - fmt.Fprintf(os.Stderr, "pollEndpoint: ioutil.ReadAll() Error: %s", err)
84 - }
85 - return fmt.Errorf("response not OK. %d %s %q", resp.StatusCode, resp.Status, string(body))
86 - }
87 - return err
88 -}
test/sharness/lib/test-lib.sh
+1 -1
@@ -230,7 +230,7 @@ test_launch_ipfs_daemon() {
230
231 # we say the daemon is ready when the API server is ready.
232 test_expect_success "'ipfs daemon' is ready" '
233 - pollEndpoint -ep=/version -host=$API_MADDR -v -tout=1s -tries=60 2>poll_apierr > poll_apiout ||
233 + pollEndpoint -host=$API_MADDR -v -tout=1s -tries=60 2>poll_apierr > poll_apiout ||
234 test_fsh cat actual_daemon || test_fsh cat daemon_err || test_fsh cat poll_apierr || test_fsh cat poll_apiout
235 '
236 }
test/sharness/t0060-daemon.sh
+1 -1
@@ -170,7 +170,7 @@ test_expect_success "'ipfs daemon' should be able to run with a pipe attached to
170 '
171
172 test_expect_success "daemon with pipe eventually becomes live" '
173 - pollEndpoint -host='$API_MADDR' -ep=/version -v -tout=1s -tries=10 >stdin_poll_apiout 2>stdin_poll_apierr &&
173 + pollEndpoint -host='$API_MADDR' -v -tout=1s -tries=10 >stdin_poll_apiout 2>stdin_poll_apierr &&
174 test_kill_repeat_10_sec $DAEMON_PID ||
175 test_fsh cat stdin_daemon_out || test_fsh cat stdin_daemon_err || test_fsh cat stdin_poll_apiout || test_fsh cat stdin_poll_apierr
176 '
test/sharness/t0067-unix-api.sh new
+30
@@ -0,0 +1,30 @@
1 +#!/usr/bin/env bash
2 +#
3 +# MIT Licensed; see the LICENSE file in this repository.
4 +#
5 +
6 +test_description="Test unix API transport"
7 +
8 +. lib/test-lib.sh
9 +
10 +test_init_ipfs
11 +
12 +# We can't use the trash dir as the full name must be longer less than 108 bytes
13 +# long (because that's the max unix domain socket path length).
14 +SOCKDIR="$(mktemp -d "${TMPDIR:-/tmp}/unix-api-sharness.XXXXXX")"
15 +
16 +test_expect_success "configure" '
17 + peerid=$(ipfs config Identity.PeerID) &&
18 + ipfs config Addresses.API "/unix/$SOCKDIR/sock"
19 +'
20 +
21 +test_launch_ipfs_daemon
22 +
23 +test_expect_success "client works" '
24 + printf "$peerid" >expected &&
25 + ipfs --api="/unix/$SOCKDIR/sock" id -f="<id>" >actual &&
26 + test_cmp expected actual
27 +'
28 +
29 +test_kill_ipfs_daemon
30 +test_done
test/sharness/t0111-gateway-writeable.sh
+1 -1
@@ -30,7 +30,7 @@ test_launch_ipfs_daemon
30 port=$GWAY_PORT
31
32 test_expect_success "ipfs daemon up" '
33 - pollEndpoint -host $GWAY_MADDR -ep=/version -v -tout=1s -tries=60 2>poll_apierr > poll_apiout ||
33 + pollEndpoint -host $GWAY_MADDR -v -tout=1s -tries=60 2>poll_apierr > poll_apiout ||
34 test_fsh cat poll_apierr || test_fsh cat poll_apiout
35 '
36