diff --git a/.gitignore b/.gitignore index caaa2bb..0259510 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ certs acme.d xversion.go -assets_vfsdata.go +*_vfsdata.go mgmt-server-linux mgmt-server-macos diff --git a/cmd/mgmt/devices.go b/cmd/mgmt/devices.go index cbbcb97..0151dd8 100644 --- a/cmd/mgmt/devices.go +++ b/cmd/mgmt/devices.go @@ -158,7 +158,12 @@ func handleDeviceRoutes(r chi.Router) { http.Error(w, msg, http.StatusInternalServerError) return } - + if err := store.Delete(auth); nil != err { + msg := `{"error":"not really sure what happened, but it didn't go well (check the logs)"}` + log.Printf("/api/devices/%s\n", slug) + log.Println(err) + http.Error(w, msg, http.StatusInternalServerError) + } w.Write([]byte(`{"success":true}` + "\n")) }) }) diff --git a/cmd/mgmt/route.go b/cmd/mgmt/route.go index ef27653..0929b0b 100644 --- a/cmd/mgmt/route.go +++ b/cmd/mgmt/route.go @@ -93,6 +93,10 @@ func routeAll() chi.Router { return nil, fmt.Errorf("extra jwt payload 'slug' (unknown)") } claims.Slug = auth.Slug + if "" != claims.Subject && auth.Slug != claims.Subject { + return nil, fmt.Errorf("invalid jwt payload 'sub' (mismatch)") + } + claims.Issuer = primaryDomain /* // a little misdirection there @@ -102,7 +106,7 @@ func routeAll() chi.Router { return []byte(auth.SharedKey), nil */ - fmt.Println("ppid:", auth.MachinePPID) + //fmt.Println("ppid:", auth.MachinePPID) return []byte(auth.MachinePPID), nil }, @@ -141,7 +145,12 @@ func routeAll() chi.Router { return } - w.Write([]byte(fmt.Sprintf(`{ "domains": [ "%s.%s" ] }`+"\n", claims.Slug, primaryDomain))) + w.Write([]byte(fmt.Sprintf( + `{ "sub": "%s", "domains": [ "%s.%s" ], "ports": [] }`+"\n", + claims.Subject, + claims.Slug, + primaryDomain, + ))) }) r.Route("/register-device", func(r chi.Router) { diff --git a/cmd/telebit-relay/telebit-relay.go b/cmd/telebit-relay/telebit-relay.go index 88297d2..ae67b40 100644 --- a/cmd/telebit-relay/telebit-relay.go +++ b/cmd/telebit-relay/telebit-relay.go @@ -253,8 +253,12 @@ func main() { tokenString = auth[1] } if "" == tokenString { + // Browsers do not allow Authorization Headers and must use access_token query string tokenString = r.URL.Query().Get("access_token") } + if "" != r.URL.Query().Get("access_token") { + r.URL.Query().Set("access_token", "[redacted]") + } grants, err := telebit.Inspect(authURL, tokenString) /* @@ -266,6 +270,10 @@ func main() { fmt.Println("return an error, do not go on") return nil, err } + if "" != r.URL.Query().Get("access_token") { + r.URL.Query().Set("access_token", "[redacted:"+grants.Subject+"]") + } + /* fmt.Printf("client claims:\n%+v\n", tok.Claims) */ diff --git a/cmd/telebit/admin.go b/cmd/telebit/admin.go new file mode 100644 index 0000000..af66544 --- /dev/null +++ b/cmd/telebit/admin.go @@ -0,0 +1,320 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "strconv" + "strings" + "sync" + + telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" + "git.coolaj86.com/coolaj86/go-telebitd/mplexer/admin" + + "github.com/go-chi/chi" + "github.com/gorilla/websocket" +) + +var httpsrv *http.Server + +// Servers represent actual connections +var Servers *sync.Map + +// Table makes sense to be in-memory, but it could be serialized if needed +var Table *sync.Map + +func init() { + Servers = &sync.Map{} + Table = &sync.Map{} + r := chi.NewRouter() + + r.HandleFunc("/ws", upgradeWebsocket) + + r.Route("/api", func(r chi.Router) { + // TODO token needs a globally unique subject + + r.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + grants, err := authorizer(r) + if nil != err { + log.Println("authorization failed", err) + w.Write(apiNotAuthorizedContent) + return + } + + // TODO define Admins in a better way + if "*" != grants.Subject { + log.Println("only admins allowed", err) + w.Write(apiNotAuthorizedContent) + } + + next.ServeHTTP(w, r) + }) + }) + + r.Get("/subscribers", getSubscribers) + r.Delete("/subscribers/{subject}", delSubscribers) + r.NotFound(apiNotFoundHandler) + }) + + adminUI := http.FileServer(admin.AdminFS) + r.Get("/", adminUI.ServeHTTP) + + httpsrv = &http.Server{ + Handler: r, + } +} + +var apiNotFoundContent = []byte("{ \"error\": \"not found\" }\n") +var apiNotAuthorizedContent = []byte("{ \"error\": \"not authorized\" }\n") + +func apiNotFoundHandler(w http.ResponseWriter, r *http.Request) { + w.Write(apiNotFoundContent) +} + +type SubscriberStatus struct { + Subject string + Tunnels int + Clients int + // TODO bytes read +} + +func getSubscribers(w http.ResponseWriter, r *http.Request) { + statuses := []*SubscriberStatus{} + Servers.Range(func(key, value interface{}) bool { + tunnels := 0 + clients := 0 + //subject := key.(string) + srvMap := value.(*sync.Map) + srvMap.Range(func(k, v interface{}) bool { + tunnels += 1 + srv := v.(*SubscriberConn) + srv.clients.Range(func(k, v interface{}) bool { + clients += 1 + return true + }) + + statuses = append(statuses, &SubscriberStatus{ + Subject: k.(string), + Tunnels: tunnels, + Clients: clients, + }) + return true + }) + return true + }) + _ = json.NewEncoder(w).Encode(&struct { + Success bool `json:"success"` + Subscribers []*SubscriberStatus `json:"subscribers"` + }{ + Success: true, + Subscribers: statuses, + }) +} + +func delSubscribers(w http.ResponseWriter, r *http.Request) { + subject := chi.URLParam(r, "subject") + + srvMapX, ok := Servers.Load(subject) + if !ok { + // TODO should this be an error? + _ = json.NewEncoder(w).Encode(&struct { + Success bool `json:"success"` + }{ + Success: true, + }) + return + } + + srvMap := srvMapX.(*sync.Map) + srvMap.Range(func(k, v interface{}) bool { + srv := v.(*SubscriberConn) + srv.clients.Range(func(k, v interface{}) bool { + conn := v.(net.Conn) + _ = conn.Close() + return true + }) + srv.wsConn.Close() + return true + }) + Servers.Delete(subject) + + _ = json.NewEncoder(w).Encode(&struct { + Success bool `json:"success"` + }{ + Success: true, + }) +} + +// SubscriberConn represents a tunneled server, its grants, and its clients +type SubscriberConn struct { + remoteAddr string + wsConn *websocket.Conn + wsTun net.Conn // *telebit.WebsocketTunnel + grants *telebit.Grants + clients *sync.Map + + // TODO is this the right codec type? + multiEncoder *telebit.Encoder + multiDecoder *telebit.Decoder + + // to fulfill Router interface +} + +func (s *SubscriberConn) RouteBytes(src, dst telebit.Addr, payload []byte) { + id := src.String() + fmt.Println("Routing some more bytes:") + fmt.Println("src", id, src) + fmt.Println("dst", dst) + clientX, ok := s.clients.Load(id) + if !ok { + // TODO send back closed client error + return + } + + client, _ := clientX.(net.Conn) + for { + n, err := client.Write(payload) + if nil != err { + if n > 0 && io.ErrShortWrite == err { + payload = payload[n:] + continue + } + // TODO send back closed client error + break + } + } +} + +func (s *SubscriberConn) Serve(client net.Conn) error { + var wconn *telebit.ConnWrap + switch conn := client.(type) { + case *telebit.ConnWrap: + wconn = conn + default: + // this probably isn't strictly necessary + panic("*SubscriberConn.Serve is special in that it must receive &ConnWrap{ Conn: conn }") + } + + id := client.RemoteAddr().String() + s.clients.Store(id, client) + + fmt.Println("[debug] cancel all the clients") + _ = client.Close() + + // TODO + // - Encode each client to the tunnel + // - Find the right client for decoded messages + + // TODO which order is remote / local? + srcParts := strings.Split(client.RemoteAddr().String(), ":") + srcAddr := srcParts[0] + srcPort, _ := strconv.Atoi(srcParts[1]) + + dstParts := strings.Split(client.LocalAddr().String(), ":") + dstAddr := dstParts[0] + dstPort, _ := strconv.Atoi(dstParts[1]) + + termination := telebit.Unknown + scheme := telebit.None + if 80 == dstPort { + // TODO dstAddr = wconn.Servername() + scheme = telebit.HTTP + } else if 443 == dstPort { + dstAddr = wconn.Servername() + scheme = telebit.HTTPS + } + + src := telebit.NewAddr( + scheme, + termination, + srcAddr, + srcPort, + ) + dst := telebit.NewAddr( + scheme, + termination, + dstAddr, + dstPort, + ) + + err := s.multiEncoder.Encode(wconn, *src, *dst) + s.clients.Delete(id) + return err +} + +func upgradeWebsocket(w http.ResponseWriter, r *http.Request) { + log.Println("websocket opening ", r.RemoteAddr, " ", r.Host) + w.Header().Set("Content-Type", "application/json") + + if "Upgrade" != r.Header.Get("Connection") && "WebSocket" != r.Header.Get("Upgrade") { + w.Write(apiNotFoundContent) + return + } + + grants, err := authorizer(r) + if nil != err { + log.Println("WebSocket authorization failed", err) + w.Write(apiNotAuthorizedContent) + return + } + upgrader := websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("WebSocket upgrade failed", err) + return + } + + wsTun := telebit.NewWebsocketTunnel(conn) + server := SubscriberConn{ + remoteAddr: r.RemoteAddr, + wsConn: conn, + wsTun: wsTun, + grants: grants, + clients: &sync.Map{}, + multiEncoder: telebit.NewEncoder(context.TODO(), wsTun), + multiDecoder: telebit.NewDecoder(wsTun), + } + + go func() { + // (this listener is also a telebit.Router) + err := server.multiDecoder.Decode(&server) + + // The tunnel itself must be closed explicitly because + // there's an encoder with a callback between the websocket + // and the multiplexer, so it doesn't know to stop listening otherwise + _ = wsTun.Close() + fmt.Printf("a subscriber stream is done: %q\n", err) + }() + + var srvMap *sync.Map + srvMapX, ok := Servers.Load(grants.Subject) + if ok { + srvMap = srvMapX.(*sync.Map) + } else { + srvMap = &sync.Map{} + } + srvMap.Store(r.RemoteAddr, server) + Servers.Store(grants.Subject, srvMap) + + // Add this server to the domain name matrix + for _, name := range grants.Domains { + var srvMap *sync.Map + srvMapX, ok := Table.Load(name) + if ok { + srvMap = srvMapX.(*sync.Map) + } else { + srvMap = &sync.Map{} + } + srvMap.Store(r.RemoteAddr, server) + Table.Store(name, srvMap) + } +} diff --git a/cmd/telebit/authorizer.go b/cmd/telebit/authorizer.go new file mode 100644 index 0000000..5f96682 --- /dev/null +++ b/cmd/telebit/authorizer.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + + telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" +) + +func NewAuthorizer(authURL string) telebit.Authorizer { + return func(r *http.Request) (*telebit.Grants, error) { + // do we have a valid wss_client? + + var tokenString string + if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) > 1 { + // TODO handle Basic auth tokens as well + tokenString = auth[1] + } + if "" == tokenString { + // Browsers do not allow Authorization Headers and must use access_token query string + tokenString = r.URL.Query().Get("access_token") + } + if "" != r.URL.Query().Get("access_token") { + r.URL.Query().Set("access_token", "[redacted]") + } + + grants, err := telebit.Inspect(authURL, tokenString) + + if nil != err { + fmt.Println("return an error, do not go on") + return nil, err + } + if "" != r.URL.Query().Get("access_token") { + r.URL.Query().Set("access_token", "[redacted:"+grants.Subject+"]") + } + + return grants, err + } +} diff --git a/cmd/telebit/telebit.go b/cmd/telebit/telebit.go index b6ace7b..17c82d4 100644 --- a/cmd/telebit/telebit.go +++ b/cmd/telebit/telebit.go @@ -8,18 +8,22 @@ import ( "encoding/hex" "flag" "fmt" + "io" "log" "net" "net/url" "os" + "regexp" "strconv" "strings" + "sync" "time" "git.coolaj86.com/coolaj86/go-telebitd/mgmt" "git.coolaj86.com/coolaj86/go-telebitd/mgmt/authstore" telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" "git.coolaj86.com/coolaj86/go-telebitd/mplexer/dns01" + httpshim "git.coolaj86.com/coolaj86/go-telebitd/relay/tunnel" "github.com/caddyserver/certmagic" "github.com/denisbrodbeck/machineid" @@ -45,6 +49,10 @@ type Forward struct { port string } +var authorizer telebit.Authorizer + +var isHostname = regexp.MustCompile(`^[A-Za-z0-9_\.\-]+$`).MatchString + func main() { var domains []string var forwards []Forward @@ -62,6 +70,7 @@ func main() { acmeRelay := flag.String("acme-relay", "", "the base url of the ACME DNS-01 relay, if not the same as the tunnel relay") authURL := flag.String("auth-url", "", "the base url for authentication, if not the same as the tunnel relay") relay := flag.String("relay", "", "the domain (or ip address) at which the relay server is running") + apiHostname := flag.String("admin-hostname", "", "the hostname used to manage clients") secret := flag.String("secret", "", "the same secret used by telebit-relay (used for JWT authentication)") token := flag.String("token", "", "a pre-generated token to give the server (instead of generating one with --secret)") bindAddrsStr := flag.String("listen", "", "list of bind addresses on which to listen, such as localhost:80, or :443") @@ -69,6 +78,8 @@ func main() { portToPorts := flag.String("port-forward", "", "a list of : for raw port-forwarding") flag.Parse() + authorizer = NewAuthorizer(*authURL) + if len(os.Args) >= 2 { if "version" == os.Args[1] { fmt.Printf("telebit %s %s %s", GitVersion, GitRev[:7], GitTimestamp) @@ -206,7 +217,25 @@ func main() { fmt.Println("Fwd:", fwd.pattern, fwd.port) mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second) } + // TODO close connection on invalid hostname + mux.HandleTCP("*", telebit.HandlerFunc(routeSubscribersAndClients)) mux.HandleTLS("*", acme, mux) + + if 0 == len(*apiHostname) { + *apiHostname = os.Getenv("API_HOSTNAME") + } + if "" != *apiHostname { + listener := httpshim.NewListener() + go func() { + httpsrv.Serve(listener) + }() + mux.HandleTCP(*apiHostname, telebit.HandlerFunc(func(client net.Conn) error { + listener.Feed(client) + // TODO use a more correct non-error error? + // or perhaps (ok, error) or (handled, error)? + return io.EOF + })) + } for _, fwd := range forwards { //mux.ForwardTCP("*", "localhost:"+fwd.port, 120*time.Second) mux.ForwardTCP(fwd.pattern, "localhost:"+fwd.port, 120*time.Second) @@ -278,6 +307,96 @@ func main() { } } +func routeSubscribersAndClients(client net.Conn) error { + var wconn *telebit.ConnWrap + switch conn := client.(type) { + case *telebit.ConnWrap: + wconn = conn + default: + panic("HandleTun is special in that it must receive &ConnWrap{ Conn: conn }") + } + + // We know this to be two parts "ip:port" + dstParts := strings.Split(client.LocalAddr().String(), ":") + //dstAddr := dstParts[0] + dstPort, _ := strconv.Atoi(dstParts[1]) + + if 80 != dstPort && 443 != dstPort { + // TODO handle by port without peeking at Servername / Hostname + // if tryToServePort(client.LocalAddr().String(), wconn) { + // return io.EOF + // } + } + + // TODO hostname for plain http? + servername := strings.ToLower(wconn.Servername()) + if "" != servername && !isHostname(servername) { + _ = client.Close() + return fmt.Errorf("invalid servername") + } + + // Match full servername "sub.domain.example.com" + if tryToServeName(servername, wconn) { + // TODO better non-error + return io.EOF + } + + // Match wild names + // - "*.domain.example.com" + // - "*.example.com" + // - (skip) + labels := strings.Split(servername, ".") + n := len(labels) + if n < 3 { + return nil + } + for i := 1; i < n-1; i++ { + wildname := "*." + strings.Join(labels[1:], ".") + if tryToServeName(wildname, wconn) { + return io.EOF + } + } + + // skip + return nil +} + +// tryToServeName picks the server tunnel with the least connections, if any +func tryToServeName(servername string, wconn *telebit.ConnWrap) bool { + var srv *SubscriberConn + load := -1 + srvMapX, ok := Table.Load(servername) + if !ok { + return false + } + + srvMap := srvMapX.(*sync.Map) + srvMap.Range(func(k, v interface{}) bool { + myLoad := 0 + mySrv := v.(*SubscriberConn) + mySrv.clients.Range(func(k, v interface{}) bool { + load += 1 + return true + }) + // pick the least loaded server + if -1 == load || myLoad < load { + load = myLoad + srv = mySrv + } + return true + }) + + // async so that the call stack can complete and be released + //srv.clients.Store(wconn.LocalAddr().String(), wconn) + go func() { + err := srv.Serve(wconn) + fmt.Printf("a browser client stream is done: %q\n", err) + //srv.clients.Delete(wconn.LocalAddr().String()) + }() + + return true +} + func parsePortForwards(portToPorts *string) ([]Forward, error) { var portForwards []Forward diff --git a/mgmt/authstore/authstore.go b/mgmt/authstore/authstore.go index a361fd7..1e925db 100644 --- a/mgmt/authstore/authstore.go +++ b/mgmt/authstore/authstore.go @@ -55,6 +55,8 @@ func HMACToken(secret string) (token string, err error) { _, _ = rand.Read(b) claims := &jwt.StandardClaims{ Id: base64.RawURLEncoding.EncodeToString(b), + Subject: "", // TODO + Issuer: "", // TODO IssuedAt: time.Now().Unix(), ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), } diff --git a/mgmt/authstore/authstore_test.go b/mgmt/authstore/authstore_test.go index d41ac49..feea592 100644 --- a/mgmt/authstore/authstore_test.go +++ b/mgmt/authstore/authstore_test.go @@ -64,6 +64,12 @@ func TestStore(t *testing.T) { return } + auth, err := store.Get(slug) + if nil == err { + t.Fatal("should get nothing back") + return + } + store.Close() fmt.Printf("%#v\n", auth) diff --git a/mplexer/addr.go b/mplexer/addr.go index 1ffac69..679f2b9 100644 --- a/mplexer/addr.go +++ b/mplexer/addr.go @@ -5,6 +5,7 @@ import "fmt" type Scheme string const ( + None = Scheme("") HTTPS = Scheme("https") HTTP = Scheme("http") SSH = Scheme("ssh") @@ -14,20 +15,22 @@ const ( type Termination string const ( - TCP = Termination("none") - TLS = Termination("tls") + Unknown = Termination("") + TCP = Termination("none") + TLS = Termination("tls") ) type Addr struct { + family string // TODO what should be the format? "tcpv6"? scheme Scheme termination Termination - family string // TODO what should be the format? "tcpv6"? addr string port int } func NewAddr(s Scheme, t Termination, a string, p int) *Addr { return &Addr{ + family: "tun", scheme: s, termination: t, addr: a, diff --git a/mplexer/admin/assets.go b/mplexer/admin/assets.go new file mode 100644 index 0000000..cb5dbcd --- /dev/null +++ b/mplexer/admin/assets.go @@ -0,0 +1,4 @@ +// +build !dev +//go:generate go run -mod vendor github.com/shurcooL/vfsgen/cmd/vfsgendev -source="git.coolaj86.com/coolaj86/go-telebitd/mplexer/admin".AdminFS + +package admin diff --git a/mplexer/admin/assets/.well-known/security.txt b/mplexer/admin/assets/.well-known/security.txt new file mode 100644 index 0000000..b6d79c6 --- /dev/null +++ b/mplexer/admin/assets/.well-known/security.txt @@ -0,0 +1,2 @@ +Contact: mailto:security@therootcompany.com +Preferred-Languages: en, lv, sq diff --git a/mplexer/admin/assets/humans.txt b/mplexer/admin/assets/humans.txt new file mode 100644 index 0000000..6f7f021 --- /dev/null +++ b/mplexer/admin/assets/humans.txt @@ -0,0 +1,32 @@ +/* TEAM */ + + Gopher: AJ ONeal + Site: https://coolaj86.com + Twitter: @coolaj86 + Location: Provo, UT, USA. + + Permanent Beta Tester: Ryan Burnette + Site: https://ryanburnette.com + Twitter: @ryanburnette + Location: GA, USA. + + Moral Support: John Shaver + Site: https://jshaver.net + Twitter: @thejshaver + Location: USA. + +/* THANKS */ + + Name: Brian Turley + + Name: Seth Gibelyou + + Name: Henry Camacho + +/* SITE */ + + Last update: 2020/06/10 + Standards: HTML5, WebSockets. + Components: Chi, Cert Magic, vfsgen. + Software: Golang + IDE: vim diff --git a/mplexer/admin/assets/index.html b/mplexer/admin/assets/index.html new file mode 100644 index 0000000..99a4e3b --- /dev/null +++ b/mplexer/admin/assets/index.html @@ -0,0 +1,11 @@ + + + Telebit Local Admin + + + +

Hello, World!

+

with love,

+

Telebit

+ + diff --git a/mplexer/admin/assets/robots.txt b/mplexer/admin/assets/robots.txt new file mode 100644 index 0000000..2cdd961 --- /dev/null +++ b/mplexer/admin/assets/robots.txt @@ -0,0 +1,3 @@ +# Do not crawl the admin interface +User-agent: * +Disallow: / diff --git a/mplexer/admin/assets_dev.go b/mplexer/admin/assets_dev.go new file mode 100644 index 0000000..60aa34a --- /dev/null +++ b/mplexer/admin/assets_dev.go @@ -0,0 +1,7 @@ +// +build dev + +package admin + +import "net/http" + +var AdminFS http.FileSystem = http.Dir("assets") diff --git a/mplexer/connwrap.go b/mplexer/connwrap.go index 6d956fc..7b41485 100644 --- a/mplexer/connwrap.go +++ b/mplexer/connwrap.go @@ -18,6 +18,7 @@ type ConnWrap struct { scheme string Conn net.Conn Plain net.Conn + encrypted *bool } type Peeker interface { @@ -114,6 +115,9 @@ func (c *ConnWrap) Servername() string { //c.servername = string(conn.relayTargetAddr.addr) return string(conn.relayTargetAddr.addr) } + + // this will get the servername + c.isTerminated() return c.servername } @@ -126,6 +130,9 @@ func (c *ConnWrap) isTerminated() bool { return true } */ + if nil != c.encrypted { + return !*c.encrypted + } // how to know how many bytes to read? really needs timeout c.SetDeadline(time.Now().Add(5 * time.Second)) @@ -146,13 +153,16 @@ func (c *ConnWrap) isTerminated() bool { length := (int(b[3]) << 8) + int(b[4]) b, err := c.Peek(n - 1 + length) if nil != err { - return true + *c.encrypted = false + return !*c.encrypted } c.servername, _ = sni.GetHostname(b) - return false + *c.encrypted = true + return !*c.encrypted } } - return true + *c.encrypted = false + return !*c.encrypted /* if nil != err { return true diff --git a/mplexer/listener.go b/mplexer/listener.go index bd9e2f2..486f0a7 100644 --- a/mplexer/listener.go +++ b/mplexer/listener.go @@ -48,12 +48,13 @@ func Listen(tun net.Conn) *Listener { decoder := NewDecoder(tun) go func() { // TODO pass error to Accept() + // (this listener is also a telebit.Router) err := decoder.Decode(listener) // The listener itself must be closed explicitly because // there's an encoder with a callback between the websocket // and the multiplexer, so it doesn't know to stop listening otherwise - listener.Close() + _ = listener.Close() fmt.Printf("the main stream is done: %q\n", err) }() diff --git a/mplexer/routemux.go b/mplexer/routemux.go index 8e5c303..955f99a 100644 --- a/mplexer/routemux.go +++ b/mplexer/routemux.go @@ -66,9 +66,8 @@ func (m *RouteMux) Serve(client net.Conn) error { for _, meta := range m.routes { // TODO '*.example.com' - if meta.terminate && "" == servername { - wconn.isTerminated() - servername = wconn.servername + if meta.terminate { + servername = wconn.Servername() } fmt.Println("Meta:", meta.addr, servername) if servername == meta.addr || "*" == meta.addr || port == meta.addr { diff --git a/mplexer/telebit-run-client.sh b/mplexer/telebit-run-client.sh new file mode 100644 index 0000000..91d18c5 --- /dev/null +++ b/mplexer/telebit-run-client.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +go run cmd/telebit/*.go --acme-agree=true \ + --acme-relay http://devices.rootprojects.org:3010/api/dns \ + --auth-url http://devices.rootprojects.org:3010/api \ + --relay wss://devices.rootprojects.org:8443/api/ws \ + --app-id test-id \ + --secret k7nsLSwNKbOeBhDFpbhwGHv \ + --listen 3443 diff --git a/mplexer/telebit-run-relay.sh b/mplexer/telebit-run-relay.sh new file mode 100644 index 0000000..554b107 --- /dev/null +++ b/mplexer/telebit-run-relay.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +go run cmd/telebit/*.go --acme-agree=true \ + --acme-relay http://devices.rootprojects.org:3010/api/dns \ + --auth-url http://devices.rootprojects.org:3010/api \ + --relay wss://devices.rootprojects.org:8443/ws \ + --app-id test-id \ + --secret xxxxyyyyssss8347 \ + --listen 3443 diff --git a/mplexer/telebit-run.sh b/mplexer/telebit-run.sh deleted file mode 100644 index 8a3d753..0000000 --- a/mplexer/telebit-run.sh +++ /dev/null @@ -1 +0,0 @@ -go run cmd/telebit/*.go --acme-agree=true --acme-relay http://devices.rootprojects.org:3010/api/dns --auth-url http://devices.rootprojects.org:3010/api --app-id test-id --secret k7nsLSwNKbOeBhDFpbhwGHv --listen 3443 diff --git a/mplexer/telebit.go b/mplexer/telebit.go index fb67c2b..d52ce20 100644 --- a/mplexer/telebit.go +++ b/mplexer/telebit.go @@ -35,12 +35,16 @@ var errNetClosing = "use of closed network connection" // A Handler routes, proxies, terminates, or responds to a net.Conn. type Handler interface { + // TODO ServeTCP Serve(net.Conn) error } // HandlerFunc should handle, proxy, or terminate the connection type HandlerFunc func(net.Conn) error +// Authorizer is called when a new client connects and we need to know something about it +type Authorizer func(*http.Request) (*Grants, error) + // Serve calls f(conn). func (f HandlerFunc) Serve(conn net.Conn) error { return f(conn) @@ -313,7 +317,9 @@ func newCertMagic(acme *ACME) (*certmagic.Config, error) { } type Grants struct { + Subject string `json:"sub"` Domains []string `json:"domains"` + Ports []int `json:"ports"` } func Inspect(authURL, token string) (*Grants, error) { diff --git a/relay/mplexy/mplexy.go b/relay/mplexy/mplexy.go index 6d0f175..f31e90d 100644 --- a/relay/mplexy/mplexy.go +++ b/relay/mplexy/mplexy.go @@ -4,7 +4,6 @@ import ( "context" "crypto/tls" "net" - "net/http" "git.coolaj86.com/coolaj86/go-telebitd/log" telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" @@ -34,9 +33,6 @@ type Authz struct { Domains []string } -// Authorizer is called when a new client connects and we need to know something about it -type Authorizer func(*http.Request) (*telebit.Grants, error) - const ( listenerAdded ListenerRegistrationStatus = iota listenerExists @@ -77,8 +73,8 @@ type MPlexy struct { ctx context.Context connnectionTable *api.Table connectionTracking *api.Tracking - AuthorizeTarget Authorizer - AuthorizeAdmin Authorizer + AuthorizeTarget telebit.Authorizer + AuthorizeAdmin telebit.Authorizer tlsConfig *tls.Config register chan *ListenerRegistration wssHostName string @@ -94,8 +90,8 @@ type MPlexy struct { func New( ctx context.Context, tlsConfig *tls.Config, - authAdmin Authorizer, - authz Authorizer, + authAdmin telebit.Authorizer, + authz telebit.Authorizer, serverStatus *api.Status, ) (mx *MPlexy) { mx = &MPlexy{ diff --git a/relay/relay.go b/relay/relay.go index 89a1cec..44a180a 100644 --- a/relay/relay.go +++ b/relay/relay.go @@ -7,6 +7,7 @@ import ( "net" "net/http" + telebit "git.coolaj86.com/coolaj86/go-telebitd/mplexer" "git.coolaj86.com/coolaj86/go-telebitd/relay/admin" "git.coolaj86.com/coolaj86/go-telebitd/relay/api" "git.coolaj86.com/coolaj86/go-telebitd/relay/mplexy" @@ -25,7 +26,7 @@ type Relay struct { } // New initializes and returns a relay service -func New(ctx context.Context, tlsConfig *tls.Config, authz mplexy.Authorizer, status *api.Status, table *api.Table) *Relay { +func New(ctx context.Context, tlsConfig *tls.Config, authz telebit.Authorizer, status *api.Status, table *api.Table) *Relay { // TODO do we need this already setup here? or is it just for logging? status.ConnectionTracking = api.NewTracking() status.ConnectionTable = table