Skip to content
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

Add SCION HTTP Proxy to shttp lib #142

Merged
merged 12 commits into from
Jun 16, 2020
33 changes: 33 additions & 0 deletions _examples/shttp/proxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"flag"
"github.com/netsec-ethz/scion-apps/pkg/shttp"
"log"
)

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 address on which the server will be requested")
direction := flag.String("direction", "", "From normal to scion or from scion to normal")
tlsCert := flag.String("cert", "tls.pem", "Path to TLS pemfile")
tlsKey := flag.String("key", "tls.key", "Path to TLS keyfile")

flag.Parse()

scionProxy, err := shttp.NewSCIONHTTPProxy(shttp.ProxyArgs{
Direction: *direction,
Remote: *remote,
Local: *local,
TlsCert: tlsCert,
TlsKey: tlsKey,
})

if err != nil {
log.Fatal("Failed to setup SCION HTTP Proxy")
}

scionProxy.Start()

}
2 changes: 1 addition & 1 deletion _examples/shttp/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
38 changes: 36 additions & 2 deletions pkg/shttp/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -57,10 +57,44 @@ 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

```Go
local := flag.String("local", "", "The local HTTP or SCION address on which the server will be listening")
remote := flag.String("remote", "", "The SCION address on which the server will be requested")
direction := flag.String("direction", "", "From normal to scion or from scion to normal")
tlsCert := flag.String("cert", "tls.pem", "Path to TLS pemfile")
tlsKey := flag.String("key", "tls.key", "Path to TLS keyfile")

flag.Parse()

scionProxy, err := shttp.NewSCIONHTTPProxy(shttp.ProxyArgs{
Direction: *direction, // "fromScion" | "toSCION"
Remote: *remote,
Local: *local,
TlsCert: tlsCert,
TlsKey: tlsKey,
})

if err != nil {
log.Fatal("Failed to setup SCION HTTP Proxy")
}

scionProxy.Start()

```

To proxy from SCION to HTTP/1.1, use
`./scionhttpproxy --local=":42424" --remote="http://192.168.0.1:8090" --direction=fromScion --cert cert.pem --key key.pem`

and to proxy to SCION from HTTP/1.1, use
`./scionhttpproxy --remote="19-ffcc:1:aaa,[127.0.0.1]:42425" --local="192.168.0.1:8091" --direction=toScion --cert cert.pem --key key.pem`
181 changes: 181 additions & 0 deletions pkg/shttp/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package shttp

import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
)

// Args to create proxy instance
type ProxyArgs struct {
Direction string
Local string
Remote string
TlsCert *string
TlsKey *string
}

// Can be overwritte by api calls
type ProxyConfig struct {
Remote string `json:"remote"`
}

type SCIONHTTPProxy struct {
config *ProxyConfig
client *http.Client
direction string
local string
// Don't verify the server's cert, as we are not using the TLS PKI.
cert *tls.Certificate
cliTLSCfg *tls.Config
srvTLSCfg *tls.Config
}

// Start listen according to the passed direction
func (s SCIONHTTPProxy) Start() {
if s.direction == "toScion" {
s.client = &http.Client{
Transport: NewRoundTripper(s.cliTLSCfg, nil),
}

http.HandleFunc("/__api/setconfig", s.setProxyConfig)
http.HandleFunc("/", s.proxyToScion)
log.Fatal(http.ListenAndServe(s.local, nil))
} else {
mux := http.NewServeMux()
mux.HandleFunc("/__api/setconfig", s.setProxyConfig)
mux.HandleFunc("/", s.proxyFromScion)
log.Fatal(ListenAndServe(s.local, mux, s.srvTLSCfg))
}
}

// Create new SCION HTTP Proxy instance
// TLS cert/key can be passed optionally
func NewSCIONHTTPProxy(args ProxyArgs) (*SCIONHTTPProxy, error) {

config := &ProxyConfig{
Remote: args.Remote,
}

proxy := &SCIONHTTPProxy{
config: config,
direction: args.Direction,
local: args.Local,
}

// Allow use of external certificates instead of dummy certs
if args.TlsCert != nil && args.TlsKey != nil {
cert, err := tls.LoadX509KeyPair(*args.TlsCert, *args.TlsKey)
if err != nil {
return nil, err
}

proxy.cliTLSCfg = &tls.Config{InsecureSkipVerify: true, NextProtos: []string{"h3-24"}}
proxy.srvTLSCfg = &tls.Config{InsecureSkipVerify: true, NextProtos: []string{"h3-24"}}
proxy.srvTLSCfg.Certificates = []tls.Certificate{cert}
proxy.cert = &cert
}

return proxy, nil
}

// API Endpoint, updates the proxy config to set new values
// Currently, only changing the remote is possible
func (s SCIONHTTPProxy) setProxyConfig(wr http.ResponseWriter, r *http.Request) {
var newConfig ProxyConfig

// Try to decode the request body into the struct. If there is an error,
// respond to the client with the error message and a 400 status code.
err := json.NewDecoder(r.Body).Decode(&newConfig)
if err != nil {
http.Error(wr, err.Error(), http.StatusBadRequest)
return
}

log.Println("Perform config update")
s.config = &newConfig
}

// Proxies the incoming HTTP/1.1 request to the configured remote
// creating a new SCION HTTP/3 request
func (s SCIONHTTPProxy) proxyToScion(wr http.ResponseWriter, r *http.Request) {

// Enforce HTTPS
baseStr := "%s%s"
if !strings.Contains(s.config.Remote, "https://") {
baseStr = "https://%s%s"
}
url := fmt.Sprintf(baseStr, s.config.Remote, r.URL.Path)
log.Println(fmt.Sprintf("Proxy to SCION, do %s request to url %s", r.Method, url))

req, err := http.NewRequest(r.Method, url, nil)
if err != nil {
log.Println("request creation failed: ", err)
http.Error(wr, err.Error(), http.StatusInternalServerError)
return
}

for k, v := range r.Header {
req.Header.Set(k, v[0])
}

resp, err := s.client.Do(req)

if err != nil {
log.Println("request failed: ", err)
http.Error(wr, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()

for k, v := range resp.Header {
wr.Header().Set(k, v[0])
}

wr.WriteHeader(resp.StatusCode)
io.Copy(wr, resp.Body)
}

// Proxies the incoming SCION HTTP/3 request to the configured remote
// creating a new HTTP/1.1 request
func (s SCIONHTTPProxy) proxyFromScion(wr http.ResponseWriter, r *http.Request) {
var resp *http.Response
var err error
var req *http.Request
client := &http.Client{}

// No HTTPS replacement, because we do not know if the remote
// uses HTTPS
remoteUrl := fmt.Sprintf("%s%s", s.config.Remote, r.URL.Path)
log.Println(fmt.Sprintf("Proxy from SCION, do %s request to url %s", r.Method, remoteUrl))
req, err = http.NewRequest(r.Method, remoteUrl, nil)

if err != nil {
log.Println("request creation failed: ", err)
http.Error(wr, err.Error(), http.StatusInternalServerError)
return
}

for name, value := range r.Header {
req.Header.Set(name, value[0])
}

resp, err = client.Do(req)

if err != nil {
log.Println("request failed: ", err)
http.Error(wr, err.Error(), http.StatusInternalServerError)
return
}

defer resp.Body.Close()
for k, v := range resp.Header {
wr.Header().Set(k, v[0])
}
wr.WriteHeader(resp.StatusCode)
io.Copy(wr, resp.Body)
}
4 changes: 3 additions & 1 deletion pkg/shttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package shttp

import (
"crypto/tls"
"net"
"net/http"

Expand All @@ -30,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: &h2quic.Server{
Expand All @@ -40,6 +41,7 @@ func ListenAndServe(addr string, handler http.Handler) error {
},
},
}
scionServer.TLSConfig = tlsConfig
return scionServer.ListenAndServe()
}

Expand Down