master
go 300 lines 10.1 KB
Raw
1 package cli
2
3 import (
4 "encoding/json"
5 "io"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/ipfs/kubo/config"
14 "github.com/ipfs/kubo/test/cli/harness"
15 . "github.com/ipfs/kubo/test/cli/testutils"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 func TestHTTPDelegatedRouting(t *testing.T) {
21 t.Parallel()
22 node := harness.NewT(t).NewNode().Init().StartDaemon()
23
24 fakeServer := func(contentType string, resp ...string) *httptest.Server {
25 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
26 w.Header().Set("Content-Type", contentType)
27 for _, r := range resp {
28 _, err := w.Write([]byte(r))
29 if err != nil {
30 panic(err)
31 }
32 }
33 }))
34 }
35
36 findProvsCID := "baeabep4vu3ceru7nerjjbk37sxb7wmftteve4hcosmyolsbsiubw2vr6pqzj6mw7kv6tbn6nqkkldnklbjgm5tzbi4hkpkled4xlcr7xz4bq"
37 provs := []string{"12D3KooWAobjw92XDcnQ1rRmRJDA3zAQpdPYUpZKrJxH6yccSpje", "12D3KooWARYacCc6eoCqvsS9RW9MA2vo51CV75deoiqssx3YgyYJ"}
38
39 t.Run("default routing config has no routers defined", func(t *testing.T) {
40 assert.Nil(t, node.ReadConfig().Routing.Routers)
41 })
42
43 t.Run("no routers means findprovs returns no results", func(t *testing.T) {
44 res := node.IPFS("routing", "findprovs", findProvsCID).Stdout.String()
45 assert.Empty(t, res)
46 })
47
48 t.Run("no routers means findprovs returns no results", func(t *testing.T) {
49 res := node.IPFS("routing", "findprovs", findProvsCID).Stdout.String()
50 assert.Empty(t, res)
51 })
52
53 node.StopDaemon()
54
55 t.Run("missing method params make the daemon fail", func(t *testing.T) {
56 node.UpdateConfig(func(cfg *config.Config) {
57 cfg.Routing.Type = config.NewOptionalString("custom")
58 cfg.Routing.Methods = config.Methods{
59 "find-peers": {RouterName: "TestDelegatedRouter"},
60 "find-providers": {RouterName: "TestDelegatedRouter"},
61 "get-ipns": {RouterName: "TestDelegatedRouter"},
62 "provide": {RouterName: "TestDelegatedRouter"},
63 }
64 })
65 res := node.RunIPFS("daemon")
66 assert.Equal(t, 1, res.ExitErr.ProcessState.ExitCode())
67 assert.Contains(
68 t,
69 res.Stderr.String(),
70 `method name "put-ipns" is missing from Routing.Methods config param`,
71 )
72 })
73
74 t.Run("having wrong methods makes daemon fail", func(t *testing.T) {
75 node.UpdateConfig(func(cfg *config.Config) {
76 cfg.Routing.Type = config.NewOptionalString("custom")
77 cfg.Routing.Methods = config.Methods{
78 "find-peers": {RouterName: "TestDelegatedRouter"},
79 "find-providers": {RouterName: "TestDelegatedRouter"},
80 "get-ipns": {RouterName: "TestDelegatedRouter"},
81 "provide": {RouterName: "TestDelegatedRouter"},
82 "put-ipns": {RouterName: "TestDelegatedRouter"},
83 "NOT_SUPPORTED": {RouterName: "TestDelegatedRouter"},
84 }
85 })
86 res := node.RunIPFS("daemon")
87 assert.Equal(t, 1, res.ExitErr.ProcessState.ExitCode())
88 assert.Contains(
89 t,
90 res.Stderr.String(),
91 `method name "NOT_SUPPORTED" is not a supported method on Routing.Methods config param`,
92 )
93 })
94
95 t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works", func(t *testing.T) {
96 server := fakeServer("application/json", ToJSONStr(JSONObj{
97 "Providers": []JSONObj{
98 {
99 "Schema": "bitswap", // Legacy bitswap schema.
100 "Protocol": "transport-bitswap",
101 "ID": provs[1],
102 "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
103 },
104 {
105 "Schema": "peer",
106 "Protocols": []string{"transport-bitswap"},
107 "ID": provs[0],
108 "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
109 },
110 },
111 }))
112 t.Cleanup(server.Close)
113
114 node.IPFS("config", "Routing.Type", "custom")
115 node.IPFS("config", "Routing.Routers.TestDelegatedRouter", "--json", ToJSONStr(JSONObj{
116 "Type": "http",
117 "Parameters": JSONObj{
118 "Endpoint": server.URL,
119 },
120 }))
121 node.IPFS("config", "Routing.Methods", "--json", ToJSONStr(JSONObj{
122 "find-peers": JSONObj{"RouterName": "TestDelegatedRouter"},
123 "find-providers": JSONObj{"RouterName": "TestDelegatedRouter"},
124 "get-ipns": JSONObj{"RouterName": "TestDelegatedRouter"},
125 "provide": JSONObj{"RouterName": "TestDelegatedRouter"},
126 "put-ipns": JSONObj{"RouterName": "TestDelegatedRouter"},
127 }))
128
129 res := node.IPFS("config", "Routing.Routers.TestDelegatedRouter.Parameters.Endpoint")
130 assert.Equal(t, res.Stdout.Trimmed(), server.URL)
131
132 node.StartDaemon()
133 res = node.IPFS("routing", "findprovs", findProvsCID)
134 assert.Equal(t, provs[1]+"\n"+provs[0], res.Stdout.Trimmed())
135 })
136
137 node.StopDaemon()
138
139 t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works (streaming)", func(t *testing.T) {
140 server := fakeServer("application/x-ndjson", ToJSONStr(JSONObj{
141 "Schema": "peer",
142 "Protocols": []string{"transport-bitswap"},
143 "ID": provs[0],
144 "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
145 }), ToJSONStr(JSONObj{
146 "Schema": "bitswap", // Legacy bitswap schema.
147 "Protocol": "transport-bitswap",
148 "ID": provs[1],
149 "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
150 }))
151 t.Cleanup(server.Close)
152
153 node.IPFS("config", "Routing.Routers.TestDelegatedRouter", "--json", ToJSONStr(JSONObj{
154 "Type": "http",
155 "Parameters": JSONObj{
156 "Endpoint": server.URL,
157 },
158 }))
159
160 res := node.IPFS("config", "Routing.Routers.TestDelegatedRouter.Parameters.Endpoint")
161 assert.Equal(t, res.Stdout.Trimmed(), server.URL)
162
163 node.StartDaemon()
164 res = node.IPFS("routing", "findprovs", findProvsCID)
165 assert.Equal(t, provs[0]+"\n"+provs[1], res.Stdout.Trimmed())
166 })
167
168 t.Run("HTTP client should emit OpenCensus metrics", func(t *testing.T) {
169 resp := node.APIClient().Get("/debug/metrics/prometheus")
170 assert.Contains(t, resp.Body, "routing_http_client_length_count")
171 })
172 }
173
174 // TestHTTPDelegatedRoutingProviderAddrs verifies that provider records sent to
175 // HTTP routers contain the expected addresses based on Addresses configuration.
176 // See https://github.com/ipfs/kubo/issues/11213
177 func TestHTTPDelegatedRoutingProviderAddrs(t *testing.T) {
178 t.Parallel()
179
180 // captureProviderAddrs returns a mock server and a function to retrieve captured addresses.
181 captureProviderAddrs := func(t *testing.T) (*httptest.Server, func() []string) {
182 t.Helper()
183 var mu sync.Mutex
184 var capturedAddrs []string
185 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
186 if (r.Method == http.MethodPut || r.Method == http.MethodPost) &&
187 strings.HasPrefix(r.URL.Path, "/routing/v1/providers") {
188 body, _ := io.ReadAll(r.Body)
189 var envelope struct {
190 Providers []struct {
191 Payload json.RawMessage `json:"Payload"`
192 } `json:"Providers"`
193 }
194 if json.Unmarshal(body, &envelope) == nil {
195 for _, prov := range envelope.Providers {
196 var payload struct {
197 Addrs []string `json:"Addrs"`
198 }
199 if json.Unmarshal(prov.Payload, &payload) == nil && len(payload.Addrs) > 0 {
200 mu.Lock()
201 capturedAddrs = payload.Addrs
202 mu.Unlock()
203 }
204 }
205 }
206 w.WriteHeader(http.StatusOK)
207 return
208 }
209 if strings.HasPrefix(r.URL.Path, "/routing/v1/") {
210 w.WriteHeader(http.StatusOK)
211 return
212 }
213 w.WriteHeader(http.StatusNotFound)
214 }))
215 t.Cleanup(srv.Close)
216 return srv, func() []string {
217 mu.Lock()
218 defer mu.Unlock()
219 return capturedAddrs
220 }
221 }
222
223 customRoutingConf := func(endpoint string) map[string]any {
224 return map[string]any{
225 "Type": "custom",
226 "Methods": map[string]any{
227 "provide": map[string]any{"RouterName": "TestRouter"},
228 "find-providers": map[string]any{"RouterName": "TestRouter"},
229 "find-peers": map[string]any{"RouterName": "TestRouter"},
230 "get-ipns": map[string]any{"RouterName": "TestRouter"},
231 "put-ipns": map[string]any{"RouterName": "TestRouter"},
232 },
233 "Routers": map[string]any{
234 "TestRouter": map[string]any{
235 "Type": "http",
236 "Parameters": map[string]any{"Endpoint": endpoint},
237 },
238 },
239 }
240 }
241
242 t.Run("provider records respect user-provided Addresses.Announce override", func(t *testing.T) {
243 t.Parallel()
244 srv, getAddrs := captureProviderAddrs(t)
245
246 node := harness.NewT(t).NewNode().Init()
247 node.SetIPFSConfig("Addresses.Announce", []string{"/ip4/1.2.3.4/tcp/4001"})
248 node.SetIPFSConfig("Routing", customRoutingConf(srv.URL))
249 node.StartDaemon()
250 defer node.StopDaemon()
251
252 cidStr := node.IPFSAddStr(time.Now().String())
253 node.IPFS("routing", "provide", cidStr)
254
255 addrs := getAddrs()
256 require.NotEmpty(t, addrs, "provider record should contain addresses")
257 assert.Equal(t, []string{"/ip4/1.2.3.4/tcp/4001"}, addrs)
258 })
259
260 t.Run("provider records respect user-provided Addresses.AppendAnnounce", func(t *testing.T) {
261 t.Parallel()
262 srv, getAddrs := captureProviderAddrs(t)
263
264 node := harness.NewT(t).NewNode().Init()
265 node.SetIPFSConfig("Addresses.AppendAnnounce", []string{"/ip4/5.6.7.8/tcp/4001"})
266 node.SetIPFSConfig("Routing", customRoutingConf(srv.URL))
267 node.StartDaemon()
268 defer node.StopDaemon()
269
270 cidStr := node.IPFSAddStr(time.Now().String())
271 node.IPFS("routing", "provide", cidStr)
272
273 addrs := getAddrs()
274 require.NotEmpty(t, addrs, "provider record should contain addresses")
275 assert.Contains(t, addrs, "/ip4/5.6.7.8/tcp/4001", "AppendAnnounce address should be present")
276 })
277
278 t.Run("provider records resolve 0.0.0.0 Swarm bind to interface addresses", func(t *testing.T) {
279 t.Parallel()
280 srv, getAddrs := captureProviderAddrs(t)
281
282 // Default Addresses.Swarm binds to /ip4/0.0.0.0/... If httpRouterAddrFunc
283 // forwards those verbatim, HTTP routers receive useless unroutable entries.
284 // See https://github.com/ipfs/kubo/issues/11213.
285 node := harness.NewT(t).NewNode().Init()
286 node.SetIPFSConfig("Routing", customRoutingConf(srv.URL))
287 node.StartDaemon()
288 defer node.StopDaemon()
289
290 cidStr := node.IPFSAddStr(time.Now().String())
291 node.IPFS("routing", "provide", cidStr)
292
293 addrs := getAddrs()
294 require.NotEmpty(t, addrs, "provider record should contain addresses")
295 for _, a := range addrs {
296 assert.NotContains(t, a, "/ip4/0.0.0.0/", "unresolved 0.0.0.0 in provider record: %s", a)
297 assert.NotContains(t, a, "/ip6/::/", "unresolved :: in provider record: %s", a)
298 }
299 })
300 }