implementing a ssh termianl
This commit is contained in:
		
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Makefile
									
									
									
									
									
								
							| @ -14,9 +14,14 @@ SRCS=\ | |||||||
| 	server-ctl.go \ | 	server-ctl.go \ | ||||||
| 	server-peer.go \ | 	server-peer.go \ | ||||||
| 	server-proxy.go \ | 	server-proxy.go \ | ||||||
| 	server-ws.go \ |  | ||||||
| 	system.go | 	system.go | ||||||
|  |  | ||||||
|  | DATA = \ | ||||||
|  | 	xterm.css \ | ||||||
|  | 	xterm.js \ | ||||||
|  | 	xterm-addon-fit.js \ | ||||||
|  | 	xterm.html | ||||||
|  |  | ||||||
| CMD_DATA=\ | CMD_DATA=\ | ||||||
| 	cmd/tls.crt \ | 	cmd/tls.crt \ | ||||||
| 	cmd/tls.key | 	cmd/tls.key | ||||||
| @ -27,7 +32,7 @@ CMD_SRCS=\ | |||||||
|  |  | ||||||
| all: $(NAME) | all: $(NAME) | ||||||
|  |  | ||||||
| $(NAME): $(SRCS) $(CMD_DATA) $(CMD_SRCS) | $(NAME): $(DATA) $(SRCS) $(CMD_DATA) $(CMD_SRCS) | ||||||
| 	CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS) | 	CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS) | ||||||
|  |  | ||||||
| clean: | clean: | ||||||
| @ -44,6 +49,15 @@ hodu_grpc.pb.go: hodu.proto | |||||||
| 		--go-grpc_out=. --go-grpc_opt=paths=source_relative \ | 		--go-grpc_out=. --go-grpc_opt=paths=source_relative \ | ||||||
| 		hodu.proto | 		hodu.proto | ||||||
|  |  | ||||||
|  | xterm.js: | ||||||
|  | 	curl -L -o "$@" https://cdn.jsdelivr.net/npm/@xterm/xterm/lib/xterm.min.js | ||||||
|  |  | ||||||
|  | xterm-addon-fit.js: | ||||||
|  | 	curl -L -o "$@" https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.min.js | ||||||
|  |  | ||||||
|  | xterm.css: | ||||||
|  | 	curl -L -o "$@" https://cdn.jsdelivr.net/npm/@xterm/xterm/css/xterm.min.css | ||||||
|  |  | ||||||
| cmd/tls.crt: | cmd/tls.crt: | ||||||
| 	openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1" | 	openssl req -x509 -newkey rsa:4096 -keyout cmd/tls.key -out cmd/tls.crt -sha256 -days 36500 -nodes -subj "/CN=$(NAME)" --addext "subjectAltName=DNS:$(NAME),IP:10.0.0.1,IP:::1" | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package hodu | package hodu | ||||||
|  |  | ||||||
| import "encoding/json" | import "encoding/json" | ||||||
|  | import "fmt" | ||||||
| import "net/http" | import "net/http" | ||||||
| import "net/url" | import "net/url" | ||||||
| import "runtime" | import "runtime" | ||||||
| @ -154,7 +155,7 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R | |||||||
| 						ClientPeerAddr: r.peer_addr, | 						ClientPeerAddr: r.peer_addr, | ||||||
| 						ServerPeerListenAddr: r.server_peer_listen_addr.String(), | 						ServerPeerListenAddr: r.server_peer_listen_addr.String(), | ||||||
| 						ServerPeerNet: r.server_peer_net, | 						ServerPeerNet: r.server_peer_net, | ||||||
| 						ServerPeerOption: r.server_peer_proto.string(), | 						ServerPeerOption: r.server_peer_option.string(), | ||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 				js = append(js, json_out_client_conn{ | 				js = append(js, json_out_client_conn{ | ||||||
| @ -272,7 +273,7 @@ func (ctl *client_ctl_client_conns_id) ServeHTTP(w http.ResponseWriter, req *htt | |||||||
| 					ClientPeerAddr: r.peer_addr, | 					ClientPeerAddr: r.peer_addr, | ||||||
| 					ServerPeerListenAddr: r.server_peer_listen_addr.String(), | 					ServerPeerListenAddr: r.server_peer_listen_addr.String(), | ||||||
| 					ServerPeerNet: r.server_peer_net, | 					ServerPeerNet: r.server_peer_net, | ||||||
| 					ServerPeerOption: r.server_peer_proto.string(), | 					ServerPeerOption: r.server_peer_option.string(), | ||||||
| 				}) | 				}) | ||||||
| 			} | 			} | ||||||
| 			js = &json_out_client_conn{ | 			js = &json_out_client_conn{ | ||||||
| @ -355,7 +356,7 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r | |||||||
| 					ClientPeerAddr: r.peer_addr, | 					ClientPeerAddr: r.peer_addr, | ||||||
| 					ServerPeerListenAddr: r.server_peer_listen_addr.String(), | 					ServerPeerListenAddr: r.server_peer_listen_addr.String(), | ||||||
| 					ServerPeerNet: r.server_peer_net, | 					ServerPeerNet: r.server_peer_net, | ||||||
| 					ServerPeerOption: r.server_peer_proto.string(), | 					ServerPeerOption: r.server_peer_option.string(), | ||||||
| 				}) | 				}) | ||||||
| 			} | 			} | ||||||
| 			cts.route_mtx.Unlock() | 			cts.route_mtx.Unlock() | ||||||
| @ -366,21 +367,28 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r | |||||||
| 		case http.MethodPost: | 		case http.MethodPost: | ||||||
| 			var jcr json_in_client_route | 			var jcr json_in_client_route | ||||||
| 			var r *ClientRoute | 			var r *ClientRoute | ||||||
| 			var server_peer_proto RouteOption | 			var server_peer_option RouteOption | ||||||
|  |  | ||||||
| 			err = json.NewDecoder(req.Body).Decode(&jcr) | 			err = json.NewDecoder(req.Body).Decode(&jcr) | ||||||
| 			if err != nil || jcr.ClientPeerAddr == "" { | 			if err != nil { | ||||||
| 				status_code = http.StatusBadRequest; w.WriteHeader(status_code) | 				status_code = http.StatusBadRequest; w.WriteHeader(status_code) | ||||||
| 				goto done | 				goto oops | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			server_peer_proto = string_to_route_proto(jcr.ServerPeerOption) | 			if  jcr.ClientPeerAddr == "" { | ||||||
| 			if server_peer_proto == RouteOption(ROUTE_OPTION_UNSPEC) { |  | ||||||
| 				status_code = http.StatusBadRequest; w.WriteHeader(status_code) | 				status_code = http.StatusBadRequest; w.WriteHeader(status_code) | ||||||
| 				goto done | 				err = fmt.Errorf("blank client-peer-addr") | ||||||
|  | 				goto oops; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r, err = cts.AddNewClientRoute(jcr.ClientPeerAddr, jcr.ServerPeerServiceAddr, jcr.ServerPeerServiceNet, server_peer_proto) | 			server_peer_option = string_to_route_option(jcr.ServerPeerOption) | ||||||
|  | 			if server_peer_option == RouteOption(ROUTE_OPTION_UNSPEC) { | ||||||
|  | 				status_code = http.StatusBadRequest; w.WriteHeader(status_code) | ||||||
|  | 				err = fmt.Errorf("wrong server-peer-option value - %d", server_peer_option) | ||||||
|  | 				goto oops | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			r, err = cts.AddNewClientRoute(jcr.ClientPeerAddr, jcr.ServerPeerServiceAddr, jcr.ServerPeerServiceNet, server_peer_option) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				status_code = http.StatusInternalServerError; w.WriteHeader(status_code) | 				status_code = http.StatusInternalServerError; w.WriteHeader(status_code) | ||||||
| 				if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } | 				if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } | ||||||
| @ -468,7 +476,7 @@ func (ctl *client_ctl_client_conns_id_routes_id) ServeHTTP(w http.ResponseWriter | |||||||
| 				ClientPeerAddr: r.peer_addr, | 				ClientPeerAddr: r.peer_addr, | ||||||
| 				ServerPeerListenAddr: r.server_peer_listen_addr.String(), | 				ServerPeerListenAddr: r.server_peer_listen_addr.String(), | ||||||
| 				ServerPeerNet: r.server_peer_net, | 				ServerPeerNet: r.server_peer_net, | ||||||
| 				ServerPeerOption: r.server_peer_proto.string(), | 				ServerPeerOption: r.server_peer_option.string(), | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { goto oops } | 			if err != nil { goto oops } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								client.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								client.go
									
									
									
									
									
								
							| @ -102,12 +102,12 @@ type ClientRoute struct { | |||||||
| 	cts *ClientConn | 	cts *ClientConn | ||||||
| 	id RouteId | 	id RouteId | ||||||
| 	peer_addr string | 	peer_addr string | ||||||
| 	peer_proto RouteOption | 	peer_option RouteOption | ||||||
|  |  | ||||||
| 	server_peer_listen_addr *net.TCPAddr // actual service-side service address | 	server_peer_listen_addr *net.TCPAddr // actual service-side service address | ||||||
| 	server_peer_addr string // desired server-side service address | 	server_peer_addr string // desired server-side service address | ||||||
| 	server_peer_net string | 	server_peer_net string | ||||||
| 	server_peer_proto RouteOption | 	server_peer_option RouteOption | ||||||
|  |  | ||||||
| 	ptc_mtx        sync.Mutex | 	ptc_mtx        sync.Mutex | ||||||
| 	ptc_map        ClientPeerConnMap | 	ptc_map        ClientPeerConnMap | ||||||
| @ -155,7 +155,7 @@ func (g *GuardedPacketStreamClient) Context() context.Context { | |||||||
| }*/ | }*/ | ||||||
|  |  | ||||||
| // -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||||||
| func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server_peer_svc_addr string, server_peer_svc_net string, server_peer_proto RouteOption) *ClientRoute { | func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server_peer_svc_addr string, server_peer_svc_net string, server_peer_option RouteOption) *ClientRoute { | ||||||
| 	var r ClientRoute | 	var r ClientRoute | ||||||
|  |  | ||||||
| 	r.cts = cts | 	r.cts = cts | ||||||
| @ -164,11 +164,11 @@ func NewClientRoute(cts *ClientConn, id RouteId, client_peer_addr string, server | |||||||
| 	r.ptc_cancel_map = make(ClientPeerCancelFuncMap) | 	r.ptc_cancel_map = make(ClientPeerCancelFuncMap) | ||||||
| 	r.peer_addr = client_peer_addr // client-side peer | 	r.peer_addr = client_peer_addr // client-side peer | ||||||
| 	// if the client_peer_addr is a domain name, it can't tell between tcp4 and tcp6 | 	// if the client_peer_addr is a domain name, it can't tell between tcp4 and tcp6 | ||||||
| 	r.peer_proto = string_to_route_proto(tcp_addr_str_class(client_peer_addr)) | 	r.peer_option = string_to_route_option(tcp_addr_str_class(client_peer_addr)) | ||||||
|  |  | ||||||
| 	r.server_peer_addr = server_peer_svc_addr | 	r.server_peer_addr = server_peer_svc_addr | ||||||
| 	r.server_peer_net = server_peer_svc_net // permitted network for server-side peer | 	r.server_peer_net = server_peer_svc_net // permitted network for server-side peer | ||||||
| 	r.server_peer_proto = server_peer_proto | 	r.server_peer_option = server_peer_option | ||||||
| 	r.stop_req.Store(false) | 	r.stop_req.Store(false) | ||||||
| 	r.stop_chan = make(chan bool, 8) | 	r.stop_chan = make(chan bool, 8) | ||||||
|  |  | ||||||
| @ -257,16 +257,16 @@ func (r *ClientRoute) RunTask(wg *sync.WaitGroup) { | |||||||
| 	// most useful works are triggered by ReportEvent() and done by ConnectToPeer() | 	// most useful works are triggered by ReportEvent() and done by ConnectToPeer() | ||||||
| 	defer wg.Done() | 	defer wg.Done() | ||||||
|  |  | ||||||
| 	err = r.cts.psc.Send(MakeRouteStartPacket(r.id, r.server_peer_proto, r.peer_addr, r.server_peer_addr, r.server_peer_net)) | 	err = r.cts.psc.Send(MakeRouteStartPacket(r.id, r.server_peer_option, r.peer_addr, r.server_peer_addr, r.server_peer_net)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | ||||||
| 			"Failed to send route_start for route(%d,%s,%v,%v) to %s", | 			"Failed to send route_start for route(%d,%s,%v,%v) to %s", | ||||||
| 			r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr) | 			r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr) | ||||||
| 		goto done | 		goto done | ||||||
| 	} else { | 	} else { | ||||||
| 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | ||||||
| 			"Sent route_start for route(%d,%s,%v,%v) to %s", | 			"Sent route_start for route(%d,%s,%v,%v) to %s", | ||||||
| 			r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr) | 			r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| main_loop: | main_loop: | ||||||
| @ -281,15 +281,15 @@ done: | |||||||
| 	r.ReqStop() | 	r.ReqStop() | ||||||
| 	r.ptc_wg.Wait() // wait for all peer tasks are finished | 	r.ptc_wg.Wait() // wait for all peer tasks are finished | ||||||
|  |  | ||||||
| 	err = r.cts.psc.Send(MakeRouteStopPacket(r.id, r.server_peer_proto, r.peer_addr, r.server_peer_addr, r.server_peer_net)) | 	err = r.cts.psc.Send(MakeRouteStopPacket(r.id, r.server_peer_option, r.peer_addr, r.server_peer_addr, r.server_peer_net)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | ||||||
| 			"Failed to route_stop for route(%d,%s,%v,%v) to %s - %s", | 			"Failed to route_stop for route(%d,%s,%v,%v) to %s - %s", | ||||||
| 			r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr, err.Error()) | 			r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr, err.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | 		r.cts.cli.log.Write(r.cts.sid, LOG_DEBUG, | ||||||
| 			"Sent route_stop for route(%d,%s,%v,%v) to %s", | 			"Sent route_stop for route(%d,%s,%v,%v) to %s", | ||||||
| 			r.id, r.peer_addr, r.server_peer_proto, r.server_peer_net, r.cts.remote_addr) | 			r.id, r.peer_addr, r.server_peer_option, r.server_peer_net, r.cts.remote_addr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	r.cts.RemoveClientRoute(r) | 	r.cts.RemoveClientRoute(r) | ||||||
| @ -305,7 +305,7 @@ func (r *ClientRoute) ReqStop() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_proto RouteOption, pts_raddr string, pts_laddr string, wg *sync.WaitGroup) { | func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_option RouteOption, pts_raddr string, pts_laddr string, wg *sync.WaitGroup) { | ||||||
| 	var err error | 	var err error | ||||||
| 	var conn net.Conn | 	var conn net.Conn | ||||||
| 	var real_conn *net.TCPConn | 	var real_conn *net.TCPConn | ||||||
| @ -319,7 +319,7 @@ func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_proto RouteOption, pts_ | |||||||
| 	var ok bool | 	var ok bool | ||||||
|  |  | ||||||
| // TODO: handle TTY | // TODO: handle TTY | ||||||
| //	if route_proto & RouteOption(ROUTE_OPTION_TTY) it must create a pseudo-tty insteaad of connecting to tcp address | //	if route_option & RouteOption(ROUTE_OPTION_TTY) it must create a pseudo-tty insteaad of connecting to tcp address | ||||||
| // | // | ||||||
|  |  | ||||||
| 	defer wg.Done() | 	defer wg.Done() | ||||||
| @ -470,7 +470,7 @@ func (r *ClientRoute) ReportEvent(pts_id PeerId, event_type PACKET_KIND, event_d | |||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					r.ptc_wg.Add(1) | 					r.ptc_wg.Add(1) | ||||||
| 					go r.ConnectToPeer(pts_id, r.peer_proto, pd.RemoteAddrStr, pd.LocalAddrStr, &r.ptc_wg) | 					go r.ConnectToPeer(pts_id, r.peer_option, pd.RemoteAddrStr, pd.LocalAddrStr, &r.ptc_wg) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @ -735,8 +735,10 @@ func (cts *ClientConn) AddClientRoutes(peer_addrs []string) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		option = RouteOption(ROUTE_OPTION_TCP) | 		option = RouteOption(ROUTE_OPTION_TCP) | ||||||
| 		// automatic determination of optioncol for common ports | 		// automatic determination of protocol for common ports | ||||||
| 		switch port { | 		switch port { | ||||||
|  | 			case "22": | ||||||
|  | 				option |= RouteOption(ROUTE_OPTION_SSH) | ||||||
| 			case "80": | 			case "80": | ||||||
| 				option |= RouteOption(ROUTE_OPTION_HTTP) | 				option |= RouteOption(ROUTE_OPTION_HTTP) | ||||||
| 			case "443": | 			case "443": | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,6 +3,7 @@ module hodu | |||||||
| go 1.22.0 | go 1.22.0 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
|  | 	golang.org/x/crypto v0.26.0 | ||||||
| 	golang.org/x/net v0.28.0 | 	golang.org/x/net v0.28.0 | ||||||
| 	golang.org/x/sys v0.24.0 | 	golang.org/x/sys v0.24.0 | ||||||
| 	google.golang.org/grpc v1.67.1 | 	google.golang.org/grpc v1.67.1 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,9 +1,13 @@ | |||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= | ||||||
|  | golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= | ||||||
| golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= | ||||||
| golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= | ||||||
| golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= | ||||||
| golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= | ||||||
|  | golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= | ||||||
| golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | ||||||
| golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								hodu.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								hodu.go
									
									
									
									
									
								
							| @ -54,7 +54,7 @@ func tcp_addr_str_class(addr string) string { | |||||||
| 	return "tcp" | 	return "tcp" | ||||||
| } | } | ||||||
|  |  | ||||||
| func word_to_route_proto(word string) RouteOption { | func word_to_route_option(word string) RouteOption { | ||||||
| 	switch word { | 	switch word { | ||||||
| 		case "tcp4": | 		case "tcp4": | ||||||
| 			return RouteOption(ROUTE_OPTION_TCP4) | 			return RouteOption(ROUTE_OPTION_TCP4) | ||||||
| @ -68,35 +68,37 @@ func word_to_route_proto(word string) RouteOption { | |||||||
| 			return RouteOption(ROUTE_OPTION_HTTP) | 			return RouteOption(ROUTE_OPTION_HTTP) | ||||||
| 		case "https": | 		case "https": | ||||||
| 			return RouteOption(ROUTE_OPTION_HTTPS) | 			return RouteOption(ROUTE_OPTION_HTTPS) | ||||||
|  | 		case "ssh": | ||||||
|  | 			return RouteOption(ROUTE_OPTION_SSH) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return RouteOption(ROUTE_OPTION_UNSPEC) | 	return RouteOption(ROUTE_OPTION_UNSPEC) | ||||||
| } | } | ||||||
|  |  | ||||||
| func string_to_route_proto(desc string) RouteOption { | func string_to_route_option(desc string) RouteOption { | ||||||
| 	var fld string | 	var fld string | ||||||
| 	var proto RouteOption | 	var option RouteOption | ||||||
| 	var p RouteOption | 	var p RouteOption | ||||||
|  |  | ||||||
| 	proto = RouteOption(0) | 	option = RouteOption(0) | ||||||
| 	for _, fld = range strings.Fields(desc) { | 	for _, fld = range strings.Fields(desc) { | ||||||
| 		p = word_to_route_proto(fld) | 		p = word_to_route_option(fld) | ||||||
| 		if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p } | 		if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p } | ||||||
| 		proto |= p | 		option |= p | ||||||
| 	} | 	} | ||||||
| 	return proto | 	return option | ||||||
| } | } | ||||||
|  |  | ||||||
| func (proto RouteOption) string() string { | func (option RouteOption) string() string { | ||||||
| 	var str string | 	var str string | ||||||
|  |  | ||||||
| 	str = "" | 	str = "" | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" } | 	if option & RouteOption(ROUTE_OPTION_TCP6)  != 0 { str += " tcp6" } | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" } | 	if option & RouteOption(ROUTE_OPTION_TCP4)  != 0 { str += " tcp4" } | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_TCP)  != 0 { str += " tcp" } | 	if option & RouteOption(ROUTE_OPTION_TCP)   != 0 { str += " tcp" } | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_TTY)  != 0 { str += " tty" } | 	if option & RouteOption(ROUTE_OPTION_TTY)   != 0 { str += " tty" } | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" } | 	if option & RouteOption(ROUTE_OPTION_HTTP)  != 0 { str += " http" } | ||||||
| 	if proto & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" } | 	if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" } | ||||||
|  | 	if option & RouteOption(ROUTE_OPTION_SSH)   != 0 { str += " ssh" } | ||||||
| 	if str == "" { return str } | 	if str == "" { return str } | ||||||
| 	return str[1:] // remove the leading space | 	return str[1:] // remove the leading space | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								hodu.pb.go
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								hodu.pb.go
									
									
									
									
									
								
							| @ -30,6 +30,7 @@ const ( | |||||||
| 	ROUTE_OPTION_TTY    ROUTE_OPTION = 8 | 	ROUTE_OPTION_TTY    ROUTE_OPTION = 8 | ||||||
| 	ROUTE_OPTION_HTTP   ROUTE_OPTION = 16 | 	ROUTE_OPTION_HTTP   ROUTE_OPTION = 16 | ||||||
| 	ROUTE_OPTION_HTTPS  ROUTE_OPTION = 32 | 	ROUTE_OPTION_HTTPS  ROUTE_OPTION = 32 | ||||||
|  | 	ROUTE_OPTION_SSH    ROUTE_OPTION = 64 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Enum value maps for ROUTE_OPTION. | // Enum value maps for ROUTE_OPTION. | ||||||
| @ -42,6 +43,7 @@ var ( | |||||||
| 		8:  "TTY", | 		8:  "TTY", | ||||||
| 		16: "HTTP", | 		16: "HTTP", | ||||||
| 		32: "HTTPS", | 		32: "HTTPS", | ||||||
|  | 		64: "SSH", | ||||||
| 	} | 	} | ||||||
| 	ROUTE_OPTION_value = map[string]int32{ | 	ROUTE_OPTION_value = map[string]int32{ | ||||||
| 		"UNSPEC": 0, | 		"UNSPEC": 0, | ||||||
| @ -51,6 +53,7 @@ var ( | |||||||
| 		"TTY":    8, | 		"TTY":    8, | ||||||
| 		"HTTP":   16, | 		"HTTP":   16, | ||||||
| 		"HTTPS":  32, | 		"HTTPS":  32, | ||||||
|  | 		"SSH":    64, | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -566,30 +569,30 @@ var file_hodu_proto_rawDesc = []byte{ | |||||||
| 	0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x04, 0x50, | 	0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x48, 0x00, 0x52, 0x04, 0x50, | ||||||
| 	0x65, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, | 	0x65, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, | ||||||
| 	0x0b, 0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04, | 	0x0b, 0x32, 0x09, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04, | ||||||
| 	0x44, 0x61, 0x74, 0x61, 0x42, 0x03, 0x0a, 0x01, 0x55, 0x2a, 0x55, 0x0a, 0x0c, 0x52, 0x4f, 0x55, | 	0x44, 0x61, 0x74, 0x61, 0x42, 0x03, 0x0a, 0x01, 0x55, 0x2a, 0x5e, 0x0a, 0x0c, 0x52, 0x4f, 0x55, | ||||||
| 	0x54, 0x45, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x53, | 	0x54, 0x45, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x53, | ||||||
| 	0x50, 0x45, 0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, | 	0x50, 0x45, 0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, | ||||||
| 	0x0a, 0x04, 0x54, 0x43, 0x50, 0x34, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x43, 0x50, 0x36, | 	0x0a, 0x04, 0x54, 0x43, 0x50, 0x34, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x43, 0x50, 0x36, | ||||||
| 	0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x54, 0x59, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x48, | 	0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x54, 0x59, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x48, | ||||||
| 	0x54, 0x54, 0x50, 0x10, 0x10, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x20, | 	0x54, 0x54, 0x50, 0x10, 0x10, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x20, | ||||||
| 	0x2a, 0xb5, 0x01, 0x0a, 0x0b, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, | 	0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x40, 0x2a, 0xb5, 0x01, 0x0a, 0x0b, 0x50, 0x41, | ||||||
| 	0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, | 	0x43, 0x4b, 0x45, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, | ||||||
| 	0x0a, 0x0b, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, | 	0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x4f, 0x55, 0x54, 0x45, | ||||||
| 	0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x12, | 	0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x4f, 0x55, 0x54, | ||||||
| 	0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, | 	0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, | ||||||
| 	0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, | 	0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x52, | ||||||
| 	0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, | 	0x4f, 0x55, 0x54, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x10, | ||||||
| 	0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, | 	0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05, | ||||||
| 	0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, | 	0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, | ||||||
| 	0x52, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x50, | 	0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, | ||||||
| 	0x45, 0x45, 0x52, 0x5f, 0x45, 0x4f, 0x46, 0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x45, 0x45, | 	0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x45, 0x4f, 0x46, | ||||||
| 	0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x09, 0x32, 0x49, 0x0a, 0x04, 0x48, 0x6f, 0x64, 0x75, | 	0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, | ||||||
| 	0x12, 0x19, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x65, 0x65, 0x64, 0x12, 0x05, 0x2e, 0x53, 0x65, | 	0x09, 0x32, 0x49, 0x0a, 0x04, 0x48, 0x6f, 0x64, 0x75, 0x12, 0x19, 0x0a, 0x07, 0x47, 0x65, 0x74, | ||||||
| 	0x65, 0x64, 0x1a, 0x05, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x0c, 0x50, | 	0x53, 0x65, 0x65, 0x64, 0x12, 0x05, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x1a, 0x05, 0x2e, 0x53, 0x65, | ||||||
| 	0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x07, 0x2e, 0x50, 0x61, | 	0x65, 0x64, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, | ||||||
| 	0x63, 0x6b, 0x65, 0x74, 0x1a, 0x07, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x28, | 	0x72, 0x65, 0x61, 0x6d, 0x12, 0x07, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x07, 0x2e, | ||||||
| 	0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x68, 0x6f, 0x64, 0x75, 0x62, 0x06, 0x70, | 	0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, | ||||||
| 	0x72, 0x6f, 0x74, 0x6f, 0x33, | 	0x2e, 0x2f, 0x68, 0x6f, 0x64, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ enum ROUTE_OPTION { | |||||||
| 	TTY    = 8; | 	TTY    = 8; | ||||||
| 	HTTP   = 16; | 	HTTP   = 16; | ||||||
| 	HTTPS  = 32; | 	HTTPS  = 32; | ||||||
|  | 	SSH    = 64; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| message RouteDesc { | message RouteDesc { | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								server-ws.go
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								server-ws.go
									
									
									
									
									
								
							| @ -1,43 +0,0 @@ | |||||||
| package hodu |  | ||||||
|  |  | ||||||
| import "fmt" |  | ||||||
| import "net/http" |  | ||||||
| import "golang.org/x/net/websocket" |  | ||||||
|  |  | ||||||
| type server_ctl_ws_tty struct { |  | ||||||
| 	s *Server |  | ||||||
| 	h websocket.Handler |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func server_ws_tty (ws* websocket.Conn) { |  | ||||||
| 	var msg []byte |  | ||||||
| 	var err error |  | ||||||
|  |  | ||||||
| 	ws.Write([]byte("hello world\r\n")) |  | ||||||
| 	ws.Write([]byte("it's so wrong. it's awesome\r\n")) |  | ||||||
| 	ws.Write([]byte("it's so wrong. 동키가 지나간다.it's awesome\r\n")) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		err = websocket.Message.Receive(ws, &msg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			break |  | ||||||
| 		} else if len(msg) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| fmt.Printf ("RECEIVED MESSAGE [%v]\n", msg) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func new_server_ctl_ws_tty(s *Server) *server_ctl_ws_tty { |  | ||||||
| 	return &server_ctl_ws_tty{s: s, h: websocket.Handler(server_ws_tty)} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (ctl *server_ctl_ws_tty) ServeHTTP(w http.ResponseWriter, req *http.Request) { |  | ||||||
| 	ctl.h.ServeHTTP(w, req) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								server.go
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								server.go
									
									
									
									
									
								
							| @ -6,14 +6,13 @@ import "errors" | |||||||
| import "fmt" | import "fmt" | ||||||
| import "io" | import "io" | ||||||
| import "log" | import "log" | ||||||
| //import "math/rand" |  | ||||||
| import "net" | import "net" | ||||||
| import "net/http" | import "net/http" | ||||||
| import "net/netip" | import "net/netip" | ||||||
| import "os" |  | ||||||
| import "sync" | import "sync" | ||||||
| import "sync/atomic" | import "sync/atomic" | ||||||
|  |  | ||||||
|  | import "golang.org/x/net/websocket" | ||||||
| import "google.golang.org/grpc" | import "google.golang.org/grpc" | ||||||
| import "google.golang.org/grpc/credentials" | import "google.golang.org/grpc/credentials" | ||||||
| //import "google.golang.org/grpc/metadata" | //import "google.golang.org/grpc/metadata" | ||||||
| @ -874,7 +873,6 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs | |||||||
| 	var addr string | 	var addr string | ||||||
| 	var gl *net.TCPListener | 	var gl *net.TCPListener | ||||||
| 	var i int | 	var i int | ||||||
| 	var cwd string |  | ||||||
| 	var hs_log *log.Logger | 	var hs_log *log.Logger | ||||||
| 	var opts []grpc.ServerOption | 	var opts []grpc.ServerOption | ||||||
|  |  | ||||||
| @ -934,10 +932,7 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs | |||||||
|  |  | ||||||
| 	s.ctl_prefix = ctl_prefix | 	s.ctl_prefix = ctl_prefix | ||||||
| 	s.ctl_mux = http.NewServeMux() | 	s.ctl_mux = http.NewServeMux() | ||||||
| 	cwd, _ = os.Getwd() // TODO: |  | ||||||
|  |  | ||||||
| 	s.ctl_mux.Handle(s.ctl_prefix + "/ui/", http.StripPrefix(s.ctl_prefix, http.FileServer(http.Dir(cwd)))) // TODO: proper directory. it must not use the current working directory... |  | ||||||
| 	s.ctl_mux.Handle(s.ctl_prefix + "/ws/tty", new_server_ctl_ws_tty(&s)) |  | ||||||
| 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns", &server_ctl_server_conns{s: &s}) | 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns", &server_ctl_server_conns{s: &s}) | ||||||
| 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}", &server_ctl_server_conns_id{s: &s}) | 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}", &server_ctl_server_conns_id{s: &s}) | ||||||
| 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes", &server_ctl_server_conns_id_routes{s: &s}) | 	s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes", &server_ctl_server_conns_id_routes{s: &s}) | ||||||
| @ -959,7 +954,21 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// --------------------------------------------------------- | 	// --------------------------------------------------------- | ||||||
|  |  | ||||||
| 	s.pxy_mux = http.NewServeMux() // TODO: make /_init configurable... | 	s.pxy_mux = http.NewServeMux() // TODO: make /_init configurable... | ||||||
|  | 	s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", | ||||||
|  | 		websocket.Handler(func(ws *websocket.Conn) { | ||||||
|  | 			server_proxy_serve_ssh_ws(ws, &s) | ||||||
|  | 		})) | ||||||
|  | 	s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", &server_proxy_xterm_file{s: &s, file: "xterm.html"}) | ||||||
|  | 	s.pxy_mux.Handle("/_ssh/xterm.js", &server_proxy_xterm_file{s: &s, file: "xterm.js"}) | ||||||
|  | 	s.pxy_mux.Handle("/_ssh/xterm-addon-fit.js", &server_proxy_xterm_file{s: &s, file: "xterm-addon-fit.js"}) | ||||||
|  | 	s.pxy_mux.Handle("/_ssh/xterm.css", &server_proxy_xterm_file{s: &s, file: "xterm.css"}) | ||||||
|  | 	s.pxy_mux.Handle("/_ssh/", &server_proxy_xterm_file{s: &s, file: "_forbidden"}) | ||||||
|  |  | ||||||
|  | 	//cwd, _ = os.Getwd() // TODO: | ||||||
|  | 	//s.pxy_mux.Handle(s.ctl_prefix + "/ui/", http.StripPrefix(s.ctl_prefix, http.FileServer(http.Dir(cwd)))) // TODO: proper directory. it must not use the current working directory... | ||||||
|  |  | ||||||
| 	s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_init{s: &s}) | 	s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_init{s: &s}) | ||||||
| 	s.pxy_mux.Handle("/", &server_proxy_http_main{s: &s}) | 	s.pxy_mux.Handle("/", &server_proxy_http_main{s: &s}) | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								xterm-addon-fit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								xterm-addon-fit.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * Skipped minification because the original files appears to be already minified. | ||||||
|  |  * Original file: /npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js | ||||||
|  |  * | ||||||
|  |  * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | ||||||
|  |  */ | ||||||
|  | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})())); | ||||||
|  | //# sourceMappingURL=xterm-addon-fit.js.map | ||||||
							
								
								
									
										8
									
								
								xterm.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								xterm.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * Minified by jsDelivr using clean-css v5.3.2. | ||||||
|  |  * Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css | ||||||
|  |  * | ||||||
|  |  * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | ||||||
|  |  */ | ||||||
|  | .xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative} | ||||||
|  | /*# sourceMappingURL=/sm/97377c0c258e109358121823f5790146c714989366481f90e554c42277efb500.map */ | ||||||
							
								
								
									
										76
									
								
								xterm.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								xterm.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  | <meta charset="UTF-8"> | ||||||
|  | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |  | ||||||
|  | <title>Terminal</title> | ||||||
|  | <link rel="stylesheet" href="/_ssh/xterm.css" /> | ||||||
|  | <style> | ||||||
|  | 	body { | ||||||
|  | 		margin: 0; | ||||||
|  | 	} | ||||||
|  | 	#terminal-container { | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100vh; | ||||||
|  | 	} | ||||||
|  | </style> | ||||||
|  | <script src="/_ssh/xterm.js"></script> | ||||||
|  | <script src="/_ssh/xterm-addon-fit.js"></script> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | window.addEventListener("load", function(event) { | ||||||
|  | 	const term = new window.Terminal(); | ||||||
|  | 	const fit = new window.FitAddon.FitAddon(); | ||||||
|  | 	const textDecoder = new TextDecoder(); | ||||||
|  |  | ||||||
|  | 	let currentLocation = window.location.host; | ||||||
|  | 	let prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; | ||||||
|  | 	const socket = new WebSocket(prefix + currentLocation + '/_ssh-ws/{{ .ConnId }}/{{ .RouteId }}'); | ||||||
|  | 	socket.binaryType = 'arraybuffer'; | ||||||
|  |  | ||||||
|  | 	term.loadAddon(fit) | ||||||
|  | 	term.open(document.getElementById('terminal-container')); | ||||||
|  | 	term.writeln('Connecting...'); | ||||||
|  |  | ||||||
|  | 	const fit_term = function() { | ||||||
|  | 		fit.fit(); | ||||||
|  | 		socket.send(JSON.stringify({ type: 'resize', data: term.rows.toString() + " " + term.cols.toString() })); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	socket.onopen = function () { | ||||||
|  | 		term.writeln('Connected'); | ||||||
|  | 		fit_term(); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	socket.onmessage = function(event) { | ||||||
|  | 		if (typeof event.data === 'string') { | ||||||
|  | 			term.write(event.data); | ||||||
|  | 		} else { | ||||||
|  | 			const text = textDecoder.decode(new Uint8Array(event.data)); | ||||||
|  | 			term.write(text); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	socket.onerror = function (event) { | ||||||
|  | 		console.error('WebSocket error:', event); | ||||||
|  | 		term.writeln('WebSocket error. See console for details.'); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	socket.onclose = function () { | ||||||
|  | 		term.writeln('Disconnected'); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	term.onData(function(data) { | ||||||
|  | 		socket.send(JSON.stringify({ type: 'key', data: data })); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	window.addEventListener('resize', fit_term); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | </head> | ||||||
|  |  | ||||||
|  | <body> | ||||||
|  | <div id="terminal-container"></div> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
		Reference in New Issue
	
	Block a user