-
Notifications
You must be signed in to change notification settings - Fork 7
Add libp2phttp command #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| package vole | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/ecdsa" | ||
| "crypto/elliptic" | ||
| "crypto/rand" | ||
| "crypto/tls" | ||
| "crypto/x509" | ||
| "crypto/x509/pkix" | ||
| "fmt" | ||
| "math/big" | ||
| "net" | ||
| "net/http" | ||
| "net/http/httputil" | ||
| "net/url" | ||
| "strconv" | ||
| "time" | ||
|
|
||
| "github.com/libp2p/go-libp2p/core/host" | ||
| "github.com/libp2p/go-libp2p/core/peer" | ||
| libp2phttp "github.com/libp2p/go-libp2p/p2p/http" | ||
| "github.com/multiformats/go-multiaddr" | ||
| ) | ||
|
|
||
| func Libp2pHTTPSocketProxy(ctx context.Context, p multiaddr.Multiaddr, unixSocketPath string) error { | ||
| h, err := libp2pHost() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| httpHost := libp2phttp.Host{StreamHost: h} | ||
|
|
||
| ai, err := peer.AddrInfoFromP2pAddr(p) | ||
| if err == peer.ErrInvalidAddr { | ||
| ai = &peer.AddrInfo{Addrs: []multiaddr.Multiaddr{p}} // No peer id | ||
| err = nil | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| hasTLS := false | ||
| hasHTTP := false | ||
| host := "" | ||
| port := 0 | ||
| multiaddr.ForEach(p, func(c multiaddr.Component) bool { | ||
| switch c.Protocol().Code { | ||
| case multiaddr.P_TLS: | ||
| hasTLS = true | ||
| case multiaddr.P_HTTP: | ||
| hasHTTP = true | ||
| case multiaddr.P_HTTPS: | ||
| hasHTTP = true | ||
| hasTLS = true | ||
| case multiaddr.P_IP4, multiaddr.P_IP6, multiaddr.P_DNS4, multiaddr.P_DNS6, multiaddr.P_DNS: | ||
| host = c.Value() | ||
| case multiaddr.P_TCP, multiaddr.P_UDP: | ||
| port, err = strconv.Atoi(c.Value()) | ||
| return false | ||
| } | ||
| return true | ||
| }) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if port == 0 && hasHTTP { | ||
| port = 80 | ||
| if hasTLS { | ||
| port = 443 | ||
| } | ||
| } | ||
|
|
||
| rt, err := httpHost.NewConstrainedRoundTripper(*ai) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var rp http.Handler | ||
| if hasTLS && hasHTTP { | ||
| u, err := url.Parse("https://" + host + ":" + strconv.Itoa(port) + "/") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| revProxy := httputil.NewSingleHostReverseProxy(u) | ||
| rp = revProxy | ||
| } else { | ||
| rp = &httputil.ReverseProxy{ | ||
| Transport: rt, | ||
| Director: func(r *http.Request) {}, | ||
| } | ||
| } | ||
|
|
||
| // Serves an HTTP server on the given path using unix sockets | ||
| server := &http.Server{ | ||
| Handler: rp, | ||
| } | ||
|
|
||
| l, err := net.Listen("unix", unixSocketPath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| go func() { | ||
| <-ctx.Done() | ||
| server.Close() | ||
| }() | ||
|
|
||
| if hasTLS && hasHTTP { | ||
| c, err := selfSignedTLSConfig() | ||
| if err != nil { | ||
|
|
||
| return err | ||
| } | ||
| server.TLSConfig = c | ||
|
|
||
| fmt.Println("Endpoint is an HTTPS endpoint. Using a self signed cert locally to proxy.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this is obvious, but can you give me some context on the value of "if the endpoint is HTTPS, let's add a self-signed cert on the unix socket proxy" rather not doing it at all or having the self-signed cert being a separate flag
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imagine you run: What do you expect the curl request to look like? I would expect: Not
The only way we can do that is if our proxy itself is an https server, and thus we need a cert. Self signed is easiest. So we actually need to add a -k to the curl command.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I guess this is probably my issue. Because I'm having trouble seeing the value of the HTTP -> HTTP proxy other than to demonstrate that it's possible via libp2p multiaddrs the need to drop an On the other hand it doesn't really hurt, so as long as it's not causing trouble and you think it's useful I don't really mind
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is not the main use case, but it's nice that it works consistently. You can make the exact same curl request to a standard HTTP server as you would to a libp2p node over a stream. |
||
| fmt.Println("Curl will only work with -k flag. This is only for debugging. Do *not* use this in production.") | ||
|
|
||
| return server.ServeTLS(l, "", "") | ||
| } | ||
|
|
||
| return server.Serve(l) | ||
| } | ||
|
|
||
| // libp2pHTTPServer serves an libp2p enabled HTTP server | ||
| func libp2pHTTPServer() (host.Host, *libp2phttp.Host, error) { | ||
| h, err := libp2pHost() | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| httpHost := &libp2phttp.Host{StreamHost: h} | ||
| return h, httpHost, nil | ||
| } | ||
|
|
||
| func selfSignedTLSConfig() (*tls.Config, error) { | ||
| priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| notBefore := time.Now() | ||
| notAfter := notBefore.Add(365 * 24 * time.Hour) | ||
|
|
||
| serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | ||
| serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| certTemplate := x509.Certificate{ | ||
| SerialNumber: serialNumber, | ||
| Subject: pkix.Name{ | ||
| Organization: []string{"Test"}, | ||
| }, | ||
| NotBefore: notBefore, | ||
| NotAfter: notAfter, | ||
| KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
| ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
| BasicConstraintsValid: true, | ||
| } | ||
|
|
||
| derBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &priv.PublicKey, priv) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| cert := tls.Certificate{ | ||
| Certificate: [][]byte{derBytes}, | ||
| PrivateKey: priv, | ||
| } | ||
|
|
||
| tlsConfig := &tls.Config{ | ||
| Certificates: []tls.Certificate{cert}, | ||
| } | ||
| return tlsConfig, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package vole | ||
|
|
||
| import ( | ||
| "context" | ||
| "net" | ||
| "net/http" | ||
| "os" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/multiformats/go-multiaddr" | ||
| ) | ||
|
|
||
| func TestHTTPProxyAndServer(t *testing.T) { | ||
| // Start libp2p HTTP server | ||
| h, hh, err := libp2pHTTPServer() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| hh.SetHTTPHandlerAtPath("/hello", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(http.StatusOK) | ||
| })) | ||
|
|
||
| go hh.Serve() | ||
| defer hh.Close() | ||
|
|
||
| serverAddr := h.Addrs()[0].Encapsulate(multiaddr.StringCast("/p2p/" + h.ID().String())) | ||
| port, err := serverAddr.ValueForProtocol(multiaddr.P_TCP) | ||
| if err != nil || port == "" { | ||
| port, err = serverAddr.ValueForProtocol(multiaddr.P_UDP) | ||
| if err != nil || port == "" { | ||
| t.Fatal("could not get port from server address") | ||
| } | ||
| } | ||
|
|
||
| ctx := context.Background() | ||
| ctx, cancel := context.WithCancel(ctx) | ||
| defer cancel() | ||
|
|
||
| socketFile, err := os.CreateTemp("", "libp2phttp-*.sock") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| socketFile.Close() | ||
| os.Remove(socketFile.Name()) | ||
|
|
||
| go func() { | ||
| err := Libp2pHTTPSocketProxy(ctx, serverAddr, socketFile.Name()) | ||
| if err != http.ErrServerClosed && err != nil { | ||
| panic(err) | ||
| } | ||
| }() | ||
|
|
||
| // Wait a bit to let the proxy start up. | ||
| for i := 0; i < 10; i++ { | ||
| time.Sleep(100 * time.Millisecond) | ||
| _, err := os.Stat(socketFile.Name()) | ||
| if err == nil { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| client := http.Client{ | ||
| Transport: &http.Transport{ | ||
| DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
| return net.Dial("unix", socketFile.Name()) | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| resp, err := client.Get("http://127.0.0.1:" + port + "/") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer resp.Body.Close() | ||
| if resp.StatusCode != http.StatusOK { | ||
| t.Fatalf("unexpected status code: %d", resp.StatusCode) | ||
| } | ||
| } | ||
|
|
||
| func TestHTTPProxyAndServerOverHTTPTransport(t *testing.T) { | ||
| // Start a basic http server | ||
| s := &http.Server{ | ||
| Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(http.StatusOK) | ||
| }), | ||
| } | ||
| l, err := net.Listen("tcp", "localhost:0") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| go s.Serve(l) | ||
| defer s.Close() | ||
|
|
||
| // get port of listener | ||
| _, port, err := net.SplitHostPort(l.Addr().String()) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| serverAddr := multiaddr.StringCast("/ip4/127.0.0.1/tcp/" + port + "/http") | ||
|
|
||
| ctx := context.Background() | ||
| ctx, cancel := context.WithCancel(ctx) | ||
| defer cancel() | ||
|
|
||
| socketFile, err := os.CreateTemp("", "libp2phttp-*.sock") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| socketFile.Close() | ||
| os.Remove(socketFile.Name()) | ||
|
|
||
| go func() { | ||
| err := Libp2pHTTPSocketProxy(ctx, serverAddr, socketFile.Name()) | ||
| if err != http.ErrServerClosed && err != nil { | ||
| panic(err) | ||
| } | ||
| }() | ||
|
|
||
| // Wait a bit to let the proxy start up. | ||
| for i := 0; i < 10; i++ { | ||
| time.Sleep(100 * time.Millisecond) | ||
| _, err := os.Stat(socketFile.Name()) | ||
| if err == nil { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| client := http.Client{ | ||
| Transport: &http.Transport{ | ||
| DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
| return net.Dial("unix", socketFile.Name()) | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| resp, err := client.Get("http://127.0.0.1:" + port + "/") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer resp.Body.Close() | ||
| if resp.StatusCode != http.StatusOK { | ||
| t.Fatalf("unexpected status code: %d", resp.StatusCode) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this logic get into trouble if say
/ws/tlsis being used for a relay and combined with/http?Maybe not relevant though since I'm not sure if there are multiaddrs where
/httpis valid when combined with/p2p/.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/httprepresents the HTTP transport. It shouldn't be combined with/ws.Not yet, but these could be combined. I just need to finish peer id auth over http and we could have a valid multiaddr that looks like
/.../http/p2p/12Foo