diff --git a/README.md b/README.md index 84cb3004..d54168e8 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ With the executable: ### API -When `gvproxy` is started with the `--listen` option, it exposes a HTTP API on the host. +When `gvproxy` is started with the `--listen` or `--services` option, it exposes a HTTP API on the host. This API can be used with curl. ``` @@ -127,6 +127,8 @@ $ curl --unix-socket /tmp/network.sock http:/unix/stats ... ``` +N.B: The `--services` option exposes the same HTTP API as the `--listen` option, but without the `/connect` endpoint. This is useful for scenarios where the `gvforwarder`/`vm` tool is not run on the guest but you still want to expose services and stats endpoints. + ### Gateway The executable running on the host runs a virtual gateway that can be used by the VM. @@ -144,7 +146,7 @@ nameserver 192.168.127.1 ### Port forwarding Dynamic port forwarding is supported over the host HTTP API when `gvproxy` was -started with `--listen`, but also in the VM over http://192.168.127.1:80. +started with `--listen` or `--services`, but also in the VM over http://192.168.127.1:80. Expose a port: ``` diff --git a/cmd/gvproxy/main.go b/cmd/gvproxy/main.go index 70a4e3e1..3a7ec7aa 100644 --- a/cmd/gvproxy/main.go +++ b/cmd/gvproxy/main.go @@ -30,22 +30,23 @@ import ( ) var ( - debug bool - mtu int - endpoints arrayFlags - vpnkitSocket string - qemuSocket string - bessSocket string - stdioSocket string - vfkitSocket string - forwardSocket arrayFlags - forwardDest arrayFlags - forwardUser arrayFlags - forwardIdentify arrayFlags - sshPort int - pidFile string - exitCode int - logFile string + debug bool + mtu int + endpoints arrayFlags + vpnkitSocket string + qemuSocket string + bessSocket string + stdioSocket string + vfkitSocket string + forwardSocket arrayFlags + forwardDest arrayFlags + forwardUser arrayFlags + forwardIdentify arrayFlags + sshPort int + pidFile string + exitCode int + logFile string + servicesEndpoint string ) const ( @@ -74,6 +75,7 @@ func main() { flag.Var(&forwardIdentify, "forward-identity", "Path to SSH identity key for forwarding") flag.StringVar(&pidFile, "pid-file", "", "Generate a file with the PID in it") flag.StringVar(&logFile, "log-file", "", "Output log messages (logrus) to a given file path") + flag.StringVar(&servicesEndpoint, "services", "", "Exposes the same HTTP API as the --listen flag, without the /connect endpoint") flag.Parse() if version.ShowVersion() { @@ -262,7 +264,7 @@ func main() { } groupErrs.Go(func() error { - return run(ctx, groupErrs, &config, endpoints) + return run(ctx, groupErrs, &config, endpoints, servicesEndpoint) }) // Wait for something to happen @@ -310,7 +312,7 @@ func captureFile() string { return "capture.pcap" } -func run(ctx context.Context, g *errgroup.Group, configuration *types.Configuration, endpoints []string) error { +func run(ctx context.Context, g *errgroup.Group, configuration *types.Configuration, endpoints []string, servicesEndpoint string) error { vn, err := virtualnetwork.New(configuration) if err != nil { return err @@ -326,6 +328,15 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat httpServe(ctx, g, ln, withProfiler(vn)) } + if servicesEndpoint != "" { + log.Infof("enabling services API. Listening %s", servicesEndpoint) + ln, err := transport.Listen(servicesEndpoint) + if err != nil { + return errors.Wrap(err, "cannot listen") + } + httpServe(ctx, g, ln, vn.ServicesMux()) + } + ln, err := vn.Listen("tcp", fmt.Sprintf("%s:80", gatewayIP)) if err != nil { return err diff --git a/pkg/virtualnetwork/mux.go b/pkg/virtualnetwork/mux.go index 6b991d39..59629f34 100644 --- a/pkg/virtualnetwork/mux.go +++ b/pkg/virtualnetwork/mux.go @@ -15,7 +15,7 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" ) -func (n *VirtualNetwork) Mux() *http.ServeMux { +func (n *VirtualNetwork) ServicesMux() *http.ServeMux { mux := http.NewServeMux() mux.Handle("/services/", http.StripPrefix("/services", n.servicesMux)) mux.HandleFunc("/stats", func(w http.ResponseWriter, _ *http.Request) { @@ -27,26 +27,6 @@ func (n *VirtualNetwork) Mux() *http.ServeMux { mux.HandleFunc("/leases", func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(n.ipPool.Leases()) }) - mux.HandleFunc(types.ConnectPath, func(w http.ResponseWriter, _ *http.Request) { - hj, ok := w.(http.Hijacker) - if !ok { - http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) - return - } - conn, bufrw, err := hj.Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer conn.Close() - - if err := bufrw.Flush(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - _ = n.networkSwitch.Accept(context.Background(), conn, n.configuration.Protocol) - }) mux.HandleFunc("/tunnel", func(w http.ResponseWriter, r *http.Request) { ip := r.URL.Query().Get("ip") if ip == "" { @@ -98,3 +78,28 @@ func (n *VirtualNetwork) Mux() *http.ServeMux { }) return mux } + +func (n *VirtualNetwork) Mux() *http.ServeMux { + mux := n.ServicesMux() + mux.HandleFunc(types.ConnectPath, func(w http.ResponseWriter, _ *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + + if err := bufrw.Flush(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _ = n.networkSwitch.Accept(context.Background(), conn, n.configuration.Protocol) + }) + return mux +}