author | Alberto Bertogli
<albertito@blitiri.com.ar> 2017-08-07 20:39:22 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2017-08-07 20:57:33 UTC |
parent | 26144fa86eac9328459025270759b9ba353d5a8a |
config/config.go | +9 | -1 |
config/config_test.go | +14 | -0 |
gofer.conf.example | +17 | -2 |
gofer.go | +4 | -0 |
proxy/{http_test.go => proxy_test.go} | +30 | -13 |
proxy/raw.go | +67 | -0 |
diff --git a/config/config.go b/config/config.go index 6fee49e..3fc3c8d 100644 --- a/config/config.go +++ b/config/config.go @@ -14,8 +14,9 @@ type Config struct { HTTP []*HTTP HTTPS []*HTTPS + Raw []Raw - // Map of name -> routes. + // Map of name -> routes for HTTP(S). Routes map[string]RouteTable } @@ -30,6 +31,13 @@ type HTTPS struct { Certs string } +type Raw struct { + Addr string + Certs string + To string + ToTLS bool `toml:"to_tls",omitempty` +} + type RouteTable map[string]string // mergeRoutes merges the table src into dst, by adding the entries in src diff --git a/config/config_test.go b/config/config_test.go index d414e11..f9bb769 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -10,6 +10,12 @@ func TestSimple(t *testing.T) { const contents = ` control_addr = "127.0.0.1:9081" +[[raw]] +addr = ":995" +certs = "/etc/letsencrypt/live/" +to = "blerg.com:1995" +to_tls = true + [[http]] addr = ":http" base_routes = "default" @@ -33,6 +39,14 @@ base_routes = "default" expected := Config{ ControlAddr: "127.0.0.1:9081", + Raw: []Raw{ + Raw{ + Addr: ":995", + Certs: "/etc/letsencrypt/live/", + To: "blerg.com:1995", + ToTLS: true, + }, + }, HTTP: []*HTTP{ &HTTP{ Addr: ":http", diff --git a/gofer.conf.example b/gofer.conf.example index 6fa0d96..2c09f9b 100644 --- a/gofer.conf.example +++ b/gofer.conf.example @@ -10,13 +10,28 @@ certs = "/etc/letsencrypt/live/" # Take the routes from the definition below as a baseline. base_routes = "default" -# Extend the base routes. -routes = { "/local/" = "http://localhost:99/" } + # Extend the base routes. + [https.routes] + "/local/" = "http://localhost:99/" + [[http]] addr = ":http" base_routes = "default" + [routes.default] "/" = "http://localhost:8080/" + +[[raw]] +addr = ":995" + +# If this is present, we will listen on a TLS socket; otherwise it will be a +# plain socket. +certs = "/etc/letsencrypt/live/" + +# Where to connect to. If to_tls is true, then we will do TLS against the +# backend. +to = "example.com:1995" +to_tls = true diff --git a/gofer.go b/gofer.go index 46861a8..f625877 100644 --- a/gofer.go +++ b/gofer.go @@ -34,6 +34,10 @@ func main() { go proxy.HTTP(*http) } + for _, raw := range conf.Raw { + go proxy.Raw(raw) + } + // Monitoring server. if conf.ControlAddr != "" { mux := http.NewServeMux() diff --git a/proxy/http_test.go b/proxy/proxy_test.go similarity index 75% rename from proxy/http_test.go rename to proxy/proxy_test.go index 8df0d40..772b1fc 100644 --- a/proxy/http_test.go +++ b/proxy/proxy_test.go @@ -14,11 +14,6 @@ import ( "blitiri.com.ar/go/gofer/config" ) -const configTemplate = ` -[[http]] -addr = "$FRONTEND_ADDR" -routes = { "/be/" = "$BACKEND_URL" } -` const backendResponse = "backend response\n" func TestSimple(t *testing.T) { @@ -28,28 +23,50 @@ func TestSimple(t *testing.T) { })) defer backend.Close() - feAddr := getFreePort() + // We have two frontends: one raw and one http. + rawAddr := getFreePort() + httpAddr := getFreePort() + const configTemplate = ` +[[raw]] +addr = "$RAW_ADDR" +to = "$BACKEND_ADDR" + +[[http]] +addr = "$HTTP_ADDR" +routes = { "/be/" = "$BACKEND_URL" } +` configStr := strings.NewReplacer( - "$FRONTEND_ADDR", feAddr, + "$RAW_ADDR", rawAddr, + "$HTTP_ADDR", httpAddr, "$BACKEND_URL", backend.URL, + "$BACKEND_ADDR", backend.Listener.Addr().String(), ).Replace(configTemplate) conf, err := config.LoadString(configStr) if err != nil { log.Fatal(err) } + t.Logf("conf.Raw[0]: %#v", conf.Raw[0]) t.Logf("conf.HTTP[0]: %#v", *conf.HTTP[0]) + go Raw(conf.Raw[0]) go HTTP(*conf.HTTP[0]) - waitForHTTPServer(feAddr) + waitForHTTPServer(httpAddr) + waitForHTTPServer(rawAddr) + + // Test the raw proxy. + testGet(t, "http://"+rawAddr+"/be", 200) + + // Test the HTTP proxy. Try a combination of URLs and error responses just + // to exercise a bit more of the path handling and error checking code. + testGet(t, "http://"+httpAddr+"/be", 200) + testGet(t, "http://"+httpAddr+"/be/", 200) + testGet(t, "http://"+httpAddr+"/be/2", 200) + testGet(t, "http://"+httpAddr+"/be/3", 200) + testGet(t, "http://"+httpAddr+"/x", 404) - testGet(t, "http://"+feAddr+"/be", 200) - testGet(t, "http://"+feAddr+"/be/", 200) - testGet(t, "http://"+feAddr+"/be/2", 200) - testGet(t, "http://"+feAddr+"/be/3", 200) - testGet(t, "http://"+feAddr+"/x", 404) } func testGet(t *testing.T, url string, expectedStatus int) { diff --git a/proxy/raw.go b/proxy/raw.go new file mode 100644 index 0000000..d57902b --- /dev/null +++ b/proxy/raw.go @@ -0,0 +1,67 @@ +package proxy + +import ( + "crypto/tls" + "net" + + "blitiri.com.ar/go/gofer/config" + "blitiri.com.ar/go/gofer/util" +) + +func Raw(conf config.Raw) { + var err error + + var tlsConfig *tls.Config + if conf.Certs != "" { + tlsConfig, err = util.LoadCerts(conf.Certs) + if err != nil { + util.Log.Fatalf("error loading certs: %v", err) + } + } + + var lis net.Listener + if tlsConfig != nil { + lis, err = tls.Listen("tcp", conf.Addr, tlsConfig) + } else { + lis, err = net.Listen("tcp", conf.Addr) + } + if err != nil { + util.Log.Fatalf("error listening: %v", err) + } + + util.Log.Printf("Raw proxy on %q", conf.Addr) + for { + conn, err := lis.Accept() + if err != nil { + util.Log.Fatalf("%s error accepting: %v", conf.Addr, err) + } + + go forward(conn, conf.To, conf.ToTLS) + } +} + +func forward(src net.Conn, dstAddr string, dstTLS bool) { + defer src.Close() + + var dst net.Conn + var err error + if dstTLS { + dst, err = tls.Dial("tcp", dstAddr, nil) + } else { + dst, err = net.Dial("tcp", dstAddr) + } + + if err != nil { + util.Log.Printf("%s error dialing back: %v", src.LocalAddr(), err) + return + } + defer dst.Close() + + util.Log.Printf("%s raw %s -> %s: open", + src.RemoteAddr(), src.LocalAddr(), dst.RemoteAddr()) + + util.BidirCopy(src, dst) + + util.Log.Printf("%s raw %s -> %s: close", + src.RemoteAddr(), src.LocalAddr(), dst.RemoteAddr()) +}