diff --git a/go-rvpn-client/main.go b/go-rvpn-client/main.go index 58a2a65..63aba8a 100644 --- a/go-rvpn-client/main.go +++ b/go-rvpn-client/main.go @@ -24,8 +24,17 @@ func main() { defer quit() config := client.Config{ - Server: "wss://localhost.daplie.me:9999", - Services: map[string]int{"https": 8443}, + Server: "wss://localhost.daplie.me:9999", + Services: map[string]map[string]int{ + "https": map[string]int{ + "*": 8443, + "localhost.foo.daplie.me": 4443, + }, + "http": map[string]int{ + "*": 8443, + "localhost.foo.daplie.me": 4443, + }, + }, Token: tokenStr, Insecure: true, } diff --git a/rvpn/client/client.go b/rvpn/client/client.go index e98660e..a903305 100644 --- a/rvpn/client/client.go +++ b/rvpn/client/client.go @@ -10,13 +10,19 @@ import ( "github.com/gorilla/websocket" ) +// The Config struct holds all of the information needed to establish and handle a connection +// with the RVPN server. type Config struct { Server string Token string - Services map[string]int Insecure bool + Services map[string]map[string]int } +// Run establishes a connection with the RVPN server specified in the config. If the first attempt +// to connect fails it is assumed that something is wrong with the authentication and it will +// return an error. Otherwise it will continuously attempt to reconnect whenever the connection +// is broken. func Run(ctx context.Context, config *Config) error { serverURL, err := url.Parse(config.Server) if err != nil { @@ -36,11 +42,17 @@ func Run(ctx context.Context, config *Config) error { dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } + for name, portList := range config.Services { + if _, ok := portList["*"]; !ok { + return fmt.Errorf(`service %s missing port for "*"`, name) + } + } handler := NewWsHandler(config.Services) authenticated := false for { if conn, _, err := dialer.Dial(serverURL.String(), nil); err == nil { + loginfo.Println("connected to remote server") authenticated = true handler.HandleConn(ctx, conn) } else if !authenticated { diff --git a/rvpn/client/ws_handler.go b/rvpn/client/ws_handler.go index 212970d..7501d88 100644 --- a/rvpn/client/ws_handler.go +++ b/rvpn/client/ws_handler.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "regexp" "strings" "sync" "time" @@ -12,15 +13,18 @@ import ( "github.com/gorilla/websocket" "git.daplie.com/Daplie/go-rvpn-server/rvpn/packer" + "git.daplie.com/Daplie/go-rvpn-server/rvpn/sni" ) +var hostRegexp = regexp.MustCompile(`(?im)(?:^|[\r\n])Host: *([^\r\n]+)[\r\n]`) + // WsHandler handles all of reading and writing for the websocket connection to the RVPN server // and the TCP connections to the local servers. type WsHandler struct { lock sync.Mutex localConns map[string]net.Conn - servicePorts map[string]int + servicePorts map[string]map[string]int ctx context.Context dataChan chan *packer.Packer @@ -28,7 +32,7 @@ type WsHandler struct { // NewWsHandler creates a new handler ready to be given a websocket connection. The services // argument specifies what port each service type should be directed to on the local interface. -func NewWsHandler(services map[string]int) *WsHandler { +func NewWsHandler(services map[string]map[string]int) *WsHandler { h := new(WsHandler) h.servicePorts = services h.localConns = make(map[string]net.Conn) @@ -127,9 +131,35 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { return conn } - port := h.servicePorts[p.Service()] + service := strings.ToLower(p.Service()) + portList := h.servicePorts[service] + if portList == nil { + loginfo.Println("cannot open connection for invalid service", service) + return nil + } + + var hostname string + if service == "http" { + if match := hostRegexp.FindSubmatch(p.Data.Data()); match != nil { + hostname = strings.Split(string(match[1]), ":")[0] + } + } else if service == "https" { + hostname, _ = sni.GetHostname(p.Data.Data()) + } else { + hostname = "*" + } + if hostname == "" { + loginfo.Println("missing servername for", service, key) + return nil + } + hostname = strings.ToLower(hostname) + + port := portList[hostname] if port == 0 { - loginfo.Println("cannot open connection for invalid service", p.Service()) + port = portList["*"] + } + if port == 0 { + loginfo.Println("unable to determine local port for", service, hostname) return nil } @@ -139,8 +169,8 @@ func (h *WsHandler) getLocalConn(p *packer.Packer) net.Conn { return nil } - loginfo.Println("opened new connection to port", port, "for", key) h.localConns[key] = conn + loginfo.Printf("new client %q for %s:%d (%d clients)\n", key, hostname, port, len(h.localConns)) go h.readLocal(key, &p.Header) return conn } @@ -172,9 +202,9 @@ func (h *WsHandler) readLocal(key string, header *packer.Header) { defer func() { h.lock.Lock() delete(h.localConns, key) + loginfo.Printf("closing client %q: (%d clients)\n", key, len(h.localConns)) h.lock.Unlock() }() - defer loginfo.Println("finished with client", key) buf := make([]byte, 4096) for {