From 06cb2fb8e33b38ac7affa49b430e4eab7f8f304a Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 25 Jun 2024 12:27:15 -0700 Subject: [PATCH 01/10] Bump go-multiaddr dep --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 527379744f..7f9b7875ef 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.12.4 + github.com/multiformats/go-multiaddr v0.13.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-fmt v0.1.0 github.com/multiformats/go-multibase v0.2.0 diff --git a/go.sum b/go.sum index 0d5cfeca4c..afb8a89a9c 100644 --- a/go.sum +++ b/go.sum @@ -235,8 +235,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc= -github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= +github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= +github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= From 319517348679c6b128111500d171404cc25376c5 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 25 Jun 2024 12:29:19 -0700 Subject: [PATCH 02/10] Add support for http-path --- p2p/http/libp2phttp.go | 73 +++++++++++++++++++++++++------------ p2p/http/libp2phttp_test.go | 17 ++++----- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index a7607a92ef..a57388b4a7 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -227,7 +227,10 @@ var ErrNoListeners = errors.New("nothing to listen on") func (h *Host) setupListeners(listenerErrCh chan error) error { for _, addr := range h.ListenAddrs { - parsedAddr := parseMultiaddr(addr) + parsedAddr, err := parseMultiaddr(addr) + if err != nil { + return err + } // resolve the host ipaddr, err := net.ResolveIPAddr("ip", parsedAddr.host) if err != nil { @@ -657,12 +660,22 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { return nil, err } addr, isHTTP := normalizeHTTPMultiaddr(addr) + parsed, err := parseMultiaddr(addr) + if err != nil { + return nil, err + } + scheme := "http" + if parsed.useHTTPS { + scheme = "https" + } + u := url.URL{ + Scheme: scheme, + Host: parsed.host + ":" + parsed.port, + Path: parsed.httpPath, + RawPath: parsed.httpPath, + } + r.URL = &u if isHTTP { - parsed := parseMultiaddr(addr) - scheme := "http" - if parsed.useHTTPS { - scheme = "https" - } h.initDefaultRT() rt := h.DefaultClientRoundTripper if parsed.sni != parsed.host { @@ -676,13 +689,6 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { rt.TLSClientConfig.ServerName = parsed.sni } - // TODO add http-path support - url := url.URL{ - Scheme: scheme, - Host: parsed.host + ":" + parsed.port, - } - - r.URL = &url return rt.RoundTrip(r) } @@ -690,14 +696,16 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { return nil, fmt.Errorf("can not do HTTP over streams. Missing StreamHost") } - addr, pid := peer.SplitAddr(addr) - if pid == "" { + if parsed.peer == "" { return nil, fmt.Errorf("no peer ID in multiaddr") } - h.StreamHost.Peerstore().AddAddrs(pid, []ma.Multiaddr{addr}, peerstore.TempAddrTTL) + addr, _ = ma.SplitFunc(addr, func(c ma.Component) bool { + return c.Protocol().Code == ma.P_P2P + }) + h.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{addr}, peerstore.TempAddrTTL) srt := streamRoundTripper{ - server: pid, + server: parsed.peer, skipAddAddrs: true, httpHost: h, h: h.StreamHost, @@ -750,7 +758,10 @@ func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTri // Currently the HTTP transport can not authenticate peer IDs. if !options.serverMustAuthenticatePeerID && len(httpAddrs) > 0 && (options.preferHTTPTransport || (firstAddrIsHTTP && !existingStreamConn)) { - parsed := parseMultiaddr(httpAddrs[0]) + parsed, err := parseMultiaddr(httpAddrs[0]) + if err != nil { + return nil, err + } scheme := "http" if parsed.useHTTPS { scheme = "https" @@ -791,15 +802,18 @@ func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTri return &streamRoundTripper{h: h.StreamHost, server: server.ID, serverAddrs: nonHTTPAddrs, httpHost: h}, nil } -type httpMultiaddr struct { +type explodedMultiaddr struct { useHTTPS bool host string port string sni string + httpPath string + peer peer.ID } -func parseMultiaddr(addr ma.Multiaddr) httpMultiaddr { - out := httpMultiaddr{} +func parseMultiaddr(addr ma.Multiaddr) (explodedMultiaddr, error) { + out := explodedMultiaddr{} + var err error ma.ForEach(addr, func(c ma.Component) bool { switch c.Protocol().Code { case ma.P_IP4, ma.P_IP6, ma.P_DNS, ma.P_DNS4, ma.P_DNS6: @@ -810,15 +824,26 @@ func parseMultiaddr(addr ma.Multiaddr) httpMultiaddr { out.useHTTPS = true case ma.P_SNI: out.sni = c.Value() + case ma.P_HTTP_PATH: + out.httpPath, err = url.QueryUnescape(c.Value()) + if err == nil && out.httpPath[0] != '/' { + out.httpPath = "/" + out.httpPath + } + case ma.P_P2P: + out.peer, err = peer.Decode(c.Value()) + } + if err != nil { + return false } - return out.host == "" || out.port == "" || !out.useHTTPS || out.sni == "" + + return out.host == "" || out.port == "" || !out.useHTTPS || out.sni == "" || out.httpPath == "" || out.peer == "" }) if out.useHTTPS && out.sni == "" { out.sni = out.host } - return out + return out, err } var httpComponent, _ = ma.NewComponent("http", "") @@ -918,7 +943,7 @@ func (h *Host) getAndStorePeerMetadata(ctx context.Context, roundtripper http.Ro } func requestPeerMeta(ctx context.Context, roundtripper http.RoundTripper, wellKnownResource string) (PeerMeta, error) { - req, err := http.NewRequest("GET", wellKnownResource, nil) + req, err := http.NewRequestWithContext(ctx, "GET", wellKnownResource, nil) if err != nil { return nil, err } diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index 48a339fe0b..11f9edbed2 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -772,27 +772,26 @@ func TestHTTPHostAsRoundTripper(t *testing.T) { w.Write([]byte("hello")) })) - // Uncomment when we get the http-path changes in go-multiaddr - // // Different protocol.ID and mounted at a different path - // serverHttpHost.SetHTTPHandlerAtPath("/hello-again", "/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // w.Write([]byte("hello")) - // })) + // Different protocol.ID and mounted at a different path + serverHttpHost.SetHTTPHandlerAtPath("/hello-again", "/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello")) + })) go serverHttpHost.Serve() defer serverHttpHost.Close() - testCases := []string{ - // Version that has an http-path. Will uncomment when we get the http-path changes in go-multiaddr - // "multiaddr:" + serverHost.Addrs()[0].String() + "/http-path/hello", - } + httpPathSuffix := "/http-path/hello2" + var testCases []string for _, a := range serverHttpHost.Addrs() { if _, err := a.ValueForProtocol(ma.P_HTTP); err == nil { testCases = append(testCases, "multiaddr:"+a.String()) + testCases = append(testCases, "multiaddr:"+a.String()+httpPathSuffix) serverPort, err := a.ValueForProtocol(ma.P_TCP) require.NoError(t, err) testCases = append(testCases, "http://127.0.0.1:"+serverPort) } else { testCases = append(testCases, "multiaddr:"+a.String()+"/p2p/"+serverHost.ID().String()) + testCases = append(testCases, "multiaddr:"+a.String()+"/p2p/"+serverHost.ID().String()+httpPathSuffix) } } From d5a86f3d6241865ed3f6d2f97e09f6ea522fe738 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 27 Jun 2024 15:52:39 -0700 Subject: [PATCH 03/10] Support redirects --- p2p/http/libp2phttp.go | 90 ++++++++++++++++++++++++++++++------- p2p/http/libp2phttp_test.go | 81 ++++++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 17 deletions(-) diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index a57388b4a7..d2bbce46fe 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -480,9 +480,59 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) } resp.Body = &streamReadCloser{resp.Body, s} + locUrl, err := resp.Location() + if err == nil { + // Location url in response. Is this a multiaddr uri? and is it relative? + // If it's relative we want to convert it to an absolute multiaddr uri + // so that the next request knows how to reach the endpoint. + if locUrl.Scheme == "multiaddr" && resp.Request.URL.Scheme == "multiaddr" { + // Check if it's a relative URI and turn it into an absolute one + u, err := relativeMultiaddrURIToAbs(resp.Request.URL, locUrl) + if err == nil { + // It was a relative URI and we were able to convert it to an absolute one + // Update the location header to be an absolute multiaddr uri + resp.Header.Set("Location", u.String()) + } + } + } + return resp, nil } +// relativeMultiaddrURIToAbs takes a relative multiaddr URI and turns it into an +// absolute one. Useful, for example, when a server gives us a relative URI for a redirect. +// It allows the following request (the one after redirected) to reach the correct server. +func relativeMultiaddrURIToAbs(original *url.URL, relative *url.URL) (*url.URL, error) { + // Is this a relative uri? We know if it is because non-relative URI's of the form: + // "multiaddr:/ip4/1.2.3.4/tcp/9899" when parsed by Go's url package will have url.OmitHost == true + // But if it is relative (just a path to an http resource e.g. /here-instead) + // a redirect will inherit the multiaddr scheme, but set url.OmitHost == false. It will also stringify as something like + // multiaddr://here-instead. + if relative.OmitHost { + // Not relative (at least we can't tell). Nothing we can do here + return nil, errors.New("not relative") + } + originalStr := original.RawPath + if originalStr == "" { + originalStr = original.Path + } + originalMa, err := ma.NewMultiaddr(originalStr) + if err != nil { + return nil, errors.New("original uri is not a multiaddr") + } + + relativePathComponent, err := ma.NewComponent("http-path", relative.Path) + if err != nil { + return nil, errors.New("relative path is not a valid http-path") + } + + withoutPath, _ := ma.SplitFunc(originalMa, func(c ma.Component) bool { + return c.Protocol().Code == ma.P_HTTP_PATH + }) + withNewPath := withoutPath.Encapsulate(relativePathComponent) + return url.Parse("multiaddr:" + withNewPath.String()) +} + // roundTripperForSpecificServer is an http.RoundTripper targets a specific server. Still reuses the underlying RoundTripper for the requests. // The underlying RoundTripper MUST be an HTTP Transport. type roundTripperForSpecificServer struct { @@ -664,18 +714,19 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { if err != nil { return nil, err } - scheme := "http" - if parsed.useHTTPS { - scheme = "https" - } - u := url.URL{ - Scheme: scheme, - Host: parsed.host + ":" + parsed.port, - Path: parsed.httpPath, - RawPath: parsed.httpPath, - } - r.URL = &u + if isHTTP { + scheme := "http" + if parsed.useHTTPS { + scheme = "https" + } + u := url.URL{ + Scheme: scheme, + Host: parsed.host + ":" + parsed.port, + Path: parsed.httpPath, + } + r.URL = &u + h.initDefaultRT() rt := h.DefaultClientRoundTripper if parsed.sni != parsed.host { @@ -704,6 +755,9 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { }) h.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{addr}, peerstore.TempAddrTTL) + // Set the Opaque field to the http-path so that the HTTP request only makes + // a reference to that path and not the whole multiaddr uri + r.URL.Opaque = parsed.httpPath srt := streamRoundTripper{ server: parsed.peer, skipAddAddrs: true, @@ -833,16 +887,17 @@ func parseMultiaddr(addr ma.Multiaddr) (explodedMultiaddr, error) { out.peer, err = peer.Decode(c.Value()) } - if err != nil { - return false - } - - return out.host == "" || out.port == "" || !out.useHTTPS || out.sni == "" || out.httpPath == "" || out.peer == "" + // stop if there is an error, otherwise iterate over all components in case this is a circuit address + return err == nil }) if out.useHTTPS && out.sni == "" { out.sni = out.host } + + if out.httpPath == "" { + out.httpPath = "/" + } return out, err } @@ -864,6 +919,9 @@ func normalizeHTTPMultiaddr(addr ma.Multiaddr) (ma.Multiaddr, bool) { } return false }) + if beforeHTTPS == nil || !isHTTPMultiaddr { + return addr, false + } if afterIncludingHTTPS == nil { // No HTTPS component, just return the original diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index 11f9edbed2..bfe9b793d3 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -28,6 +28,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" httpping "github.com/libp2p/go-libp2p/p2p/http/ping" + libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" ) @@ -769,11 +770,15 @@ func TestHTTPHostAsRoundTripper(t *testing.T) { } serverHttpHost.SetHTTPHandlerAtPath("/hello", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + return + } w.Write([]byte("hello")) })) // Different protocol.ID and mounted at a different path - serverHttpHost.SetHTTPHandlerAtPath("/hello-again", "/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + serverHttpHost.SetHTTPHandlerAtPath("/hello-again", "/hello2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello")) })) @@ -805,6 +810,7 @@ func TestHTTPHostAsRoundTripper(t *testing.T) { t.Run(tc, func(t *testing.T) { resp, err := client.Get(tc) require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -822,3 +828,76 @@ func TestHTTPHostAsRoundTripperFailsWhenNoStreamHostPresent(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "Missing StreamHost") } + +// TestRedirects tests a client being redirected through multiple HTTP redirects +func TestRedirects(t *testing.T) { + serverHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) + require.NoError(t, err) + serverHttpHost := libp2phttp.Host{ + StreamHost: serverHost, + InsecureAllowHTTP: true, + ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, + } + go serverHttpHost.Serve() + defer serverHttpHost.Close() + + serverHttpHost.SetHTTPHandlerAtPath("/redirect-1/0.0.1", "/a", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "/b/") + w.WriteHeader(http.StatusMovedPermanently) + })) + + serverHttpHost.SetHTTPHandlerAtPath("/redirect-2/0.0.1", "/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "/c/") + w.WriteHeader(http.StatusMovedPermanently) + })) + + serverHttpHost.SetHTTPHandlerAtPath("/redirect-3/0.0.1", "/c", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "/d/") + w.WriteHeader(http.StatusMovedPermanently) + })) + + serverHttpHost.SetHTTPHandlerAtPath("/redirect-4/0.0.1", "/d", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello")) + })) + + clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport)) + require.NoError(t, err) + client := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}} + + type testCase struct { + initialURI string + expectedURI string + } + var testCases []testCase + for _, a := range serverHttpHost.Addrs() { + if _, err := a.ValueForProtocol(ma.P_HTTP); err == nil { + port, err := a.ValueForProtocol(ma.P_TCP) + require.NoError(t, err) + u := fmt.Sprintf("multiaddr:%s/http-path/a%%2f", a) + f := fmt.Sprintf("http://127.0.0.1:%s/d/", port) + testCases = append(testCases, testCase{u, f}) + } else { + u := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/a%%2f", a, serverHost.ID()) + f := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/%%2Fd%%2F", a, serverHost.ID()) + testCases = append(testCases, testCase{u, f}) + } + } + + for _, tc := range testCases { + t.Run(tc.initialURI, func(t *testing.T) { + // u := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/a%%2f", serverHost.Addrs()[0], serverHost.ID()) + resp, err := client.Get(tc.initialURI) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "hello", string(body)) + + // expectedFinalURI := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/%%2Fd%%2F", serverHost.Addrs()[0], serverHost.ID()) + finalReqURL := *resp.Request.URL + finalReqURL.Opaque = "" // Clear the opaque so we can compare the URI + require.Equal(t, tc.expectedURI, finalReqURL.String()) + }) + } +} From 45161090d5c0431da862563fd803b0ce8f32c400 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 27 Jun 2024 15:54:44 -0700 Subject: [PATCH 04/10] Don't split at p2p, split at P_HTTP_PATH --- p2p/http/libp2phttp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index d2bbce46fe..862ae23278 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -750,10 +750,10 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { if parsed.peer == "" { return nil, fmt.Errorf("no peer ID in multiaddr") } - addr, _ = ma.SplitFunc(addr, func(c ma.Component) bool { - return c.Protocol().Code == ma.P_P2P + withoutHTTPPath, _ := ma.SplitFunc(addr, func(c ma.Component) bool { + return c.Protocol().Code == ma.P_HTTP_PATH }) - h.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{addr}, peerstore.TempAddrTTL) + h.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{withoutHTTPPath}, peerstore.TempAddrTTL) // Set the Opaque field to the http-path so that the HTTP request only makes // a reference to that path and not the whole multiaddr uri From 155077b78791a4010e2766351fb72d4acb7c1db6 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 27 Jun 2024 16:00:45 -0700 Subject: [PATCH 05/10] fixup --- p2p/http/libp2phttp_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index bfe9b793d3..bb4593517b 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -885,7 +885,6 @@ func TestRedirects(t *testing.T) { for _, tc := range testCases { t.Run(tc.initialURI, func(t *testing.T) { - // u := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/a%%2f", serverHost.Addrs()[0], serverHost.ID()) resp, err := client.Get(tc.initialURI) require.NoError(t, err) defer resp.Body.Close() @@ -894,7 +893,6 @@ func TestRedirects(t *testing.T) { require.NoError(t, err) require.Equal(t, "hello", string(body)) - // expectedFinalURI := fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/%%2Fd%%2F", serverHost.Addrs()[0], serverHost.ID()) finalReqURL := *resp.Request.URL finalReqURL.Opaque = "" // Clear the opaque so we can compare the URI require.Equal(t, tc.expectedURI, finalReqURL.String()) From 404dd14b7f306584ad4055401170418f253ca61b Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 27 Jun 2024 16:07:49 -0700 Subject: [PATCH 06/10] Fill in host if missing --- p2p/http/libp2phttp.go | 4 +++ p2p/http/libp2phttp_test.go | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index 862ae23278..4fad3e627f 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -758,6 +758,10 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) { // Set the Opaque field to the http-path so that the HTTP request only makes // a reference to that path and not the whole multiaddr uri r.URL.Opaque = parsed.httpPath + if r.Host == "" { + // Fill in the host if it's not already set + r.Host = parsed.host + ":" + parsed.port + } srt := streamRoundTripper{ server: parsed.peer, skipAddAddrs: true, diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index bb4593517b..38cdf16889 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -899,3 +899,56 @@ func TestRedirects(t *testing.T) { }) } } + +func TestImpliedHostIsSet(t *testing.T) { + serverHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) + require.NoError(t, err) + serverHttpHost := libp2phttp.Host{ + StreamHost: serverHost, + InsecureAllowHTTP: true, + ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, + } + go serverHttpHost.Serve() + defer serverHttpHost.Close() + + serverHttpHost.SetHTTPHandlerAtPath("/hi", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.Host, "localhost") || r.URL.Path != "/" { + fmt.Println("Host is", r.Host) + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + })) + + clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport)) + require.NoError(t, err) + client := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}} + + type testCase struct { + uri string + } + var testCases []testCase + for _, a := range serverHttpHost.Addrs() { + if _, err := a.ValueForProtocol(ma.P_HTTP); err == nil { + port, err := a.ValueForProtocol(ma.P_TCP) + require.NoError(t, err) + u := fmt.Sprintf("multiaddr:/dns/localhost/tcp/%s/http", port) + testCases = append(testCases, testCase{u}) + } else { + port, err := a.ValueForProtocol(ma.P_UDP) + require.NoError(t, err) + u := fmt.Sprintf("multiaddr:/dns/localhost/udp/%s/quic-v1/p2p/%s", port, serverHost.ID()) + testCases = append(testCases, testCase{u}) + } + } + + for _, tc := range testCases { + t.Run(tc.uri, func(t *testing.T) { + resp, err := client.Get(tc.uri) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + }) + } + +} From 8d10d156b422b7929078442128efb7af5aa9075b Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 1 Jul 2024 10:29:54 -0700 Subject: [PATCH 07/10] mod tidy --- test-plans/go.mod | 2 +- test-plans/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-plans/go.mod b/test-plans/go.mod index f0cf86aae5..08cdffec11 100644 --- a/test-plans/go.mod +++ b/test-plans/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/go-redis/redis/v8 v8.11.5 github.com/libp2p/go-libp2p v0.0.0 - github.com/multiformats/go-multiaddr v0.12.4 + github.com/multiformats/go-multiaddr v0.13.0 ) require ( diff --git a/test-plans/go.sum b/test-plans/go.sum index e94d7dccde..e90badf12e 100644 --- a/test-plans/go.sum +++ b/test-plans/go.sum @@ -187,8 +187,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc= -github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= +github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= +github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= From 585d29743a1a1a566d0f52805dec0a9c94ec5be1 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 1 Jul 2024 10:52:11 -0700 Subject: [PATCH 08/10] Fix test --- p2p/http/libp2phttp_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index 38cdf16889..07a1bee0ca 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -912,12 +912,11 @@ func TestImpliedHostIsSet(t *testing.T) { defer serverHttpHost.Close() serverHttpHost.SetHTTPHandlerAtPath("/hi", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.Host, "localhost") || r.URL.Path != "/" { - fmt.Println("Host is", r.Host) - w.WriteHeader(http.StatusNotFound) + if strings.HasPrefix(r.Host, "localhost") && r.URL.Path == "/" { + w.WriteHeader(http.StatusOK) return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNotFound) })) clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport)) From 148696291938c6d040f43f932a6cee5b00fac033 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 1 Jul 2024 10:59:47 -0700 Subject: [PATCH 09/10] Add MultiaddrURIRedirect --- p2p/http/libp2phttp_test.go | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go index 07a1bee0ca..fa8687e308 100644 --- a/p2p/http/libp2phttp_test.go +++ b/p2p/http/libp2phttp_test.go @@ -900,6 +900,51 @@ func TestRedirects(t *testing.T) { } } +// TestMultiaddrURIRedirect tests that we can redirect using a multiaddr URI. We +// redirect from the http transport to the stream based transport +func TestMultiaddrURIRedirect(t *testing.T) { + serverHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) + require.NoError(t, err) + serverHttpHost := libp2phttp.Host{ + StreamHost: serverHost, + InsecureAllowHTTP: true, + ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, + } + go serverHttpHost.Serve() + defer serverHttpHost.Close() + + var httpMultiaddr ma.Multiaddr + var streamMultiaddr ma.Multiaddr + for _, a := range serverHttpHost.Addrs() { + if _, err := a.ValueForProtocol(ma.P_HTTP); err == nil { + httpMultiaddr = a + } else { + streamMultiaddr = a + } + } + require.NotNil(t, httpMultiaddr) + require.NotNil(t, streamMultiaddr) + + // Redirect to a whole other transport! + serverHttpHost.SetHTTPHandlerAtPath("/redirect-1/0.0.1", "/a", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", fmt.Sprintf("multiaddr:%s/p2p/%s/http-path/b", streamMultiaddr, serverHost.ID())) + w.WriteHeader(http.StatusMovedPermanently) + })) + + serverHttpHost.SetHTTPHandlerAtPath("/redirect-2/0.0.1", "/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + + clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs, libp2p.Transport(libp2pquic.NewTransport)) + require.NoError(t, err) + client := http.Client{Transport: &libp2phttp.Host{StreamHost: clientStreamHost}} + + resp, err := client.Get(fmt.Sprintf("multiaddr:%s/http-path/a", httpMultiaddr)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.True(t, strings.HasPrefix(resp.Request.URL.RawPath, streamMultiaddr.String()), "expected redirect to stream transport") +} + func TestImpliedHostIsSet(t *testing.T) { serverHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) require.NoError(t, err) From 9cfe1ed49d4bf6ba63a78ece2536955ae3874fcc Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Wed, 10 Jul 2024 17:13:17 -0700 Subject: [PATCH 10/10] Only alloc err once --- p2p/http/libp2phttp.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index 4fad3e627f..d2a544d224 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -499,6 +499,8 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) return resp, nil } +var errNotRelative = errors.New("not relative") + // relativeMultiaddrURIToAbs takes a relative multiaddr URI and turns it into an // absolute one. Useful, for example, when a server gives us a relative URI for a redirect. // It allows the following request (the one after redirected) to reach the correct server. @@ -510,7 +512,7 @@ func relativeMultiaddrURIToAbs(original *url.URL, relative *url.URL) (*url.URL, // multiaddr://here-instead. if relative.OmitHost { // Not relative (at least we can't tell). Nothing we can do here - return nil, errors.New("not relative") + return nil, errNotRelative } originalStr := original.RawPath if originalStr == "" {