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