diff --git a/Makefile b/Makefile index 4b2c49da..3027d85f 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ build: scion-bat \ scion-sensorfetcher scion-sensorserver \ scion-ssh scion-sshd \ example-helloworld \ - example-shttp-client example-shttp-server + example-shttp-client example-shttp-server example-shttp-proxy clean: go clean ./... @@ -97,3 +97,7 @@ example-shttp-client: .PHONY: example-shttp-server example-shttp-server: go build -o $(BIN)/$@ ./_examples/shttp/server + +.PHONY: example-shttp-proxy +example-shttp-proxy: + go build -o $(BIN)/$@ ./_examples/shttp/proxy diff --git a/_examples/shttp/proxy/main.go b/_examples/shttp/proxy/main.go new file mode 100644 index 00000000..3de63adc --- /dev/null +++ b/_examples/shttp/proxy/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "crypto/tls" + "flag" + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/netsec-ethz/scion-apps/pkg/shttp" + "github.com/scionproto/scion/go/lib/snet" +) + +func main() { + + local := flag.String("local", "", "The local HTTP or SCION address on which the server will be listening") + remote := flag.String("remote", "", "The SCION or HTTP address on which the server will be requested") + + flag.Parse() + + mux := http.NewServeMux() + + // parseUDPAddr validates if the address is a SCION address + // which we can use to proxy to SCION + if _, err := snet.ParseUDPAddr(*remote); err == nil { + proxyHandler, err := shttp.NewSingleSCIONHostReverseProxy(*remote, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + log.Fatalf("Failed to create SCION reverse proxy %s", err) + } + + mux.Handle("/", proxyHandler) + log.Printf("Proxy to SCION remote %s\n", *remote) + } else { + u, err := url.Parse(*remote) + if err != nil { + log.Fatal(fmt.Sprintf("Failed parse remote %s, %s", *remote, err)) + } + log.Printf("Proxy to HTTP remote %s\n", *remote) + mux.Handle("/", httputil.NewSingleHostReverseProxy(u)) + } + + if lAddr, err := snet.ParseUDPAddr(*local); err == nil { + log.Printf("Listen on SCION %s\n", *local) + // ListenAndServe does not support listening on a complete SCION Address, + // Consequently, we only use the port (as seen in the server example) + log.Fatalf("%s", shttp.ListenAndServe(fmt.Sprintf(":%d", lAddr.Host.Port), mux, nil)) + } else { + log.Printf("Listen on HTTP %s\n", *local) + log.Fatalf("%s", http.ListenAndServe(*local, mux)) + } + +} diff --git a/_examples/shttp/server/main.go b/_examples/shttp/server/main.go index 40a9a04a..d32fd598 100644 --- a/_examples/shttp/server/main.go +++ b/_examples/shttp/server/main.go @@ -84,5 +84,5 @@ func main() { } }) - log.Fatal(shttp.ListenAndServe(fmt.Sprintf(":%d", *port), m)) + log.Fatal(shttp.ListenAndServe(fmt.Sprintf(":%d", *port), m, nil)) } diff --git a/pkg/shttp/README.md b/pkg/shttp/README.md index 15b2c9b7..046cfa53 100644 --- a/pkg/shttp/README.md +++ b/pkg/shttp/README.md @@ -1,6 +1,6 @@ # HTTP over SCION/QUIC -This package contains a client/server implementation of HTTP/2 over SCION/QUIC. +This package contains a client/server implementation of HTTP/2 over SCION/QUIC as well as a proxy implementation to proxy HTTP/2 requests over SCION to HTTP/1.1 and vice versa. ### The Client is a standard net/http client with a custom RoundTripper implementation. @@ -57,10 +57,25 @@ http.Handle("/download", handler) Finally, start the server: ```Go -err := shttp.ListenAndServe(local, mux) +err := shttp.ListenAndServe(local, mux, nil) if err != nil { log.Fatal(err) } ``` where `local` is the local (UDP)-address of the server. + +### Proxy combines the client and server implementation +The proxy can handle two directions: From HTTP/1.1 to SCION and from SCION to HTTP/1.1. It's idea is to make resources provided over HTTP accessible over the SCION network. + +To use the proxy, consider the proxy example in _examples. This implementation detects from the format of the `remote` and `local` argument if it should listen on SCION/HTTP/1.1 and proxy to SCION/HTTP/1.1. + +Example code can be found here: [_examples/shttp/proxy](../../_examples/shttp/proxy/main.go) + +To proxy from SCION to HTTP/1.1, use +`./proxy --local="19-ffcc:1:aaa,[127.0.0.1]:42424" --remote="http://192.168.0.1:8090"` + +and to proxy to SCION from HTTP/1.1, use +`./proxy --remote="19-ffcc:1:aaa,[127.0.0.1]:42425" --local="192.168.0.1:8091"` + +Furthermore, also proxying from SCION to SCION and from HTTP/1.1 to HTTP/1.1 is possible by entering the correct address formats for SCION and HTTP/1.1 respectively. \ No newline at end of file diff --git a/pkg/shttp/proxy.go b/pkg/shttp/proxy.go new file mode 100644 index 00000000..8df8f029 --- /dev/null +++ b/pkg/shttp/proxy.go @@ -0,0 +1,22 @@ +package shttp + +import ( + "crypto/tls" + "fmt" + "net/http/httputil" + "net/url" +) + +// Proxies the incoming HTTP/1.1 request to the configured remote +// creating a new SCION HTTP/3 request +func NewSingleSCIONHostReverseProxy(remote string, cliTLSCfg *tls.Config) (*httputil.ReverseProxy, error) { + // Enforce HTTPS, otherwise it cannot be parsed to URL + sURL := MangleSCIONAddrURL(fmt.Sprintf("https://%s", remote)) + targetURL, err := url.Parse(sURL) + if err != nil { + return nil, err + } + proxy := httputil.NewSingleHostReverseProxy(targetURL) + proxy.Transport = NewRoundTripper(cliTLSCfg, nil) + return proxy, nil +} diff --git a/pkg/shttp/server.go b/pkg/shttp/server.go index cbdf3f14..0a1b24b4 100644 --- a/pkg/shttp/server.go +++ b/pkg/shttp/server.go @@ -31,7 +31,7 @@ type Server struct { // ListenAndServe listens for HTTPS connections on the SCION address addr and calls Serve // with handler to handle requests -func ListenAndServe(addr string, handler http.Handler) error { +func ListenAndServe(addr string, handler http.Handler, tlsConfig *tls.Config) error { scionServer := &Server{ Server: &http3.Server{ @@ -41,6 +41,7 @@ func ListenAndServe(addr string, handler http.Handler) error { }, }, } + scionServer.TLSConfig = tlsConfig return scionServer.ListenAndServe() }