enhanced for wpx response transformer

This commit is contained in:
hyung-hwan 2024-12-27 14:43:44 +09:00
parent 6809cfdeb6
commit f1572f9321
9 changed files with 277 additions and 101 deletions

View File

@ -20,7 +20,8 @@ SRCS=\
server-ctl.go \ server-ctl.go \
server-peer.go \ server-peer.go \
server-proxy.go \ server-proxy.go \
system.go system.go \
transform.go \
DATA = \ DATA = \
xterm.css \ xterm.css \
@ -40,7 +41,10 @@ CMD_SRCS=\
all: $(NAME) all: $(NAME)
$(NAME): $(DATA) $(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) ##go build -buildmode=plugin -o modres.so hook/modres.go
##CGO_ENABLED=0 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)'" -o $@ $(CMD_SRCS)
##CGO_ENABLED=1 go build -x -ldflags "-X 'main.HODU_NAME=$(NAME)' -X 'main.HODU_VERSION=$(VERSION)' -linkmode external -extldflags=-static" -o $@ $(CMD_SRCS)
clean: clean:
go clean -x -i go clean -x -i

6
go.mod
View File

@ -6,12 +6,10 @@ require (
golang.org/x/crypto v0.26.0 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
golang.org/x/text v0.21.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
)

4
go.sum
View File

@ -8,8 +8,8 @@ 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 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=

View File

@ -56,6 +56,7 @@ type server_ctl_server_conns_id_routes_id struct {
type server_ctl_stats struct { type server_ctl_stats struct {
s *Server s *Server
id string
} }
// ------------------------------------ // ------------------------------------
@ -124,7 +125,7 @@ func (ctl *server_ctl_server_conns) ServeHTTP(w http.ResponseWriter, req *http.R
} }
//done: //done:
s.log.Write("", LOG_DEBUG, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken
return return
oops: oops:
@ -197,7 +198,7 @@ func (ctl *server_ctl_server_conns_id) ServeHTTP(w http.ResponseWriter, req *htt
} }
done: done:
s.log.Write("", LOG_DEBUG, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code)
return return
oops: oops:
@ -263,7 +264,7 @@ func (ctl *server_ctl_server_conns_id_routes) ServeHTTP(w http.ResponseWriter, r
} }
done: done:
s.log.Write("", LOG_DEBUG, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken
return return
oops: oops:
@ -320,7 +321,7 @@ func (ctl *server_ctl_server_conns_id_routes_id) ServeHTTP(w http.ResponseWriter
} }
//done: //done:
s.log.Write("", LOG_DEBUG, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken
return return
oops: oops:
@ -330,7 +331,11 @@ oops:
// ------------------------------------ // ------------------------------------
func (ctl *server_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (ctl *server_ctl_stats) GetId() string {
return ctl.id
}
func (ctl *server_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var status_code int var status_code int
var err error var err error
@ -367,10 +372,10 @@ func (ctl *server_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request)
} }
//done: //done:
s.log.Write("", LOG_DEBUG, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken //s.log.Write(ctl.id, LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) // TODO: time taken
return return status_code, nil
oops: oops:
s.log.Write("", LOG_ERROR, "[%s] %s %s - %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error()) //s.log.Write(ctl.id, LOG_ERROR, "[%s] %s %s - %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error())
return return status_code, err
} }

View File

@ -34,21 +34,25 @@ var xterm_html []byte
type server_proxy_http_init struct { type server_proxy_http_init struct {
s *Server s *Server
prefix string prefix string
id string
} }
type server_proxy_http_main struct { type server_proxy_http_main struct {
s *Server s *Server
prefix string prefix string
restore bool // restore URLs in response text id string
} }
type server_proxy_ssh struct { type server_proxy_xterm_file struct {
s *Server s *Server
file string
id string
} }
// ------------------------------------ // ------------------------------------
//Copied from net/http/httputil/reverseproxy.go //Copied from net/http/httputil/reverseproxy.go
var hopHeaders = []string{ var hop_headers = []string{
"Connection", "Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive", "Keep-Alive",
@ -75,12 +79,12 @@ func copy_headers(dst http.Header, src http.Header) {
func delete_hop_by_hop_headers(header http.Header) { func delete_hop_by_hop_headers(header http.Header) {
var h string var h string
for _, h = range hopHeaders { for _, h = range hop_headers {
header.Del(h) header.Del(h)
} }
} }
func mutate_proxy_req_headers(req *http.Request, newreq *http.Request, path_prefix string) bool { func mutate_proxy_req_headers(req *http.Request, newreq *http.Request, path_prefix string, in_wpx_mode bool) bool {
var hdr http.Header var hdr http.Header
var newhdr http.Header var newhdr http.Header
var remote_addr string var remote_addr string
@ -143,7 +147,7 @@ func mutate_proxy_req_headers(req *http.Request, newreq *http.Request, path_pref
newhdr.Set("X-Forwarded-Host", req.Host) newhdr.Set("X-Forwarded-Host", req.Host)
} }
if path_prefix != "" { if !in_wpx_mode && path_prefix != "" {
var v []string var v []string
_, ok = newhdr["X-Forwarded-Path"] _, ok = newhdr["X-Forwarded-Path"]
@ -166,7 +170,11 @@ func mutate_proxy_req_headers(req *http.Request, newreq *http.Request, path_pref
// ------------------------------------ // ------------------------------------
func (pxy *server_proxy_http_init) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (pxy *server_proxy_http_init) GetId() string {
return pxy.id
}
func (pxy *server_proxy_http_init) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var status_code int var status_code int
var conn_id string var conn_id string
@ -198,12 +206,10 @@ func (pxy *server_proxy_http_init) ServeHTTP(w http.ResponseWriter, req *http.Re
status_code = http.StatusFound; w.WriteHeader(status_code) status_code = http.StatusFound; w.WriteHeader(status_code)
//done: //done:
s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) return status_code, nil
return
oops: oops:
s.log.Write("", LOG_ERROR, "[%s] %s %s %d - %s", req.RemoteAddr, req.Method, req.URL.String(), status_code, err.Error()) return status_code, err
return
} }
// ------------------------------------ // ------------------------------------
@ -212,14 +218,14 @@ func prevent_follow_redirect (req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse return http.ErrUseLastResponse
} }
func (pxy *server_proxy_http_main) get_route(req *http.Request) (*ServerRoute, string, string, string, error) { func (pxy *server_proxy_http_main) get_route(req *http.Request, in_wpx_mode bool) (*ServerRoute, string, string, string, error) {
var conn_id string var conn_id string
var route_id string var route_id string
var r *ServerRoute var r *ServerRoute
var path_prefix string var path_prefix string
var err error var err error
if pxy.prefix == PORT_ID_MARKER { // for wpx if in_wpx_mode { // for wpx
conn_id = req.PathValue("port_id") conn_id = req.PathValue("port_id")
route_id = pxy.prefix route_id = pxy.prefix
} else { } else {
@ -238,7 +244,7 @@ func (pxy *server_proxy_http_main) get_route(req *http.Request) (*ServerRoute, s
} }
ids = strings.Split(id.Value, "-") ids = strings.Split(id.Value, "-")
if (len(ids) != 2) { if len(ids) != 2 {
return nil, "", "", "", fmt.Errorf("invalid proxy id cookie value - %s", id.Value) return nil, "", "", "", fmt.Errorf("invalid proxy id cookie value - %s", id.Value)
} }
@ -246,7 +252,7 @@ func (pxy *server_proxy_http_main) get_route(req *http.Request) (*ServerRoute, s
route_id = ids[1] route_id = ids[1]
path_prefix = "" path_prefix = ""
} else { } else {
if pxy.prefix == PORT_ID_MARKER { // for wpx if in_wpx_mode { // for wpx
path_prefix = fmt.Sprintf("/%s", conn_id) path_prefix = fmt.Sprintf("/%s", conn_id)
} else { } else {
path_prefix = fmt.Sprintf("%s/%s/%s", pxy.prefix, conn_id, route_id) path_prefix = fmt.Sprintf("%s/%s/%s", pxy.prefix, conn_id, route_id)
@ -358,7 +364,11 @@ func (pxy *server_proxy_http_main) req_to_proxy_url (req *http.Request, r *Serve
} }
} }
func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (pxy *server_proxy_http_main) GetId() string {
return pxy.id
}
func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var r *ServerRoute var r *ServerRoute
var status_code int var status_code int
@ -366,6 +376,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
var route_id string var route_id string
var path_prefix string var path_prefix string
var resp *http.Response var resp *http.Response
var in_wpx_mode bool
var transport *http.Transport var transport *http.Transport
var client *http.Client var client *http.Client
var addr *net.TCPAddr var addr *net.TCPAddr
@ -380,14 +391,9 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
}() }()
s = pxy.s s = pxy.s
in_wpx_mode = pxy.prefix == PORT_ID_MARKER
/* r, path_prefix, conn_id, route_id, err = pxy.get_route(req, in_wpx_mode)
ctx := req.Context()
if ctx.Done() != nil {
}
*/
r, path_prefix, conn_id, route_id, err = pxy.get_route(req)
if err != nil { if err != nil {
status_code = http.StatusNotFound; w.WriteHeader(status_code) status_code = http.StatusNotFound; w.WriteHeader(status_code)
goto oops goto oops
@ -400,9 +406,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
goto oops goto oops
} }
*/ */
addr = svc_addr_to_dst_addr(r.svc_addr) addr = svc_addr_to_dst_addr(r.svc_addr)
//transport, err = pxy.addr_to_transport(req.Context(), addr)
transport, err = pxy.addr_to_transport(s.ctx, addr) transport, err = pxy.addr_to_transport(s.ctx, addr)
if err != nil { if err != nil {
status_code = http.StatusBadGateway; w.WriteHeader(status_code) status_code = http.StatusBadGateway; w.WriteHeader(status_code)
@ -410,72 +414,86 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re
} }
proxy_url = pxy.req_to_proxy_url(req, r, path_prefix) proxy_url = pxy.req_to_proxy_url(req, r, path_prefix)
s.log.Write("", LOG_INFO, "[%s] %s %s -> %+v", req.RemoteAddr, req.Method, req.URL.String(), proxy_url) s.log.Write(pxy.id, LOG_INFO, "[%s] %s %s -> %+v", req.RemoteAddr, req.Method, req.URL.String(), proxy_url)
//proxy_req, err = http.NewRequestWithContext(req.Context(), req.Method, proxy_url.String(), req.Body)
proxy_req, err = http.NewRequestWithContext(s.ctx, req.Method, proxy_url.String(), req.Body) proxy_req, err = http.NewRequestWithContext(s.ctx, req.Method, proxy_url.String(), req.Body)
if err != nil { if err != nil {
status_code = http.StatusInternalServerError; w.WriteHeader(status_code) status_code = http.StatusInternalServerError; w.WriteHeader(status_code)
goto oops goto oops
} }
upgrade_required = mutate_proxy_req_headers(req, proxy_req, path_prefix) upgrade_required = mutate_proxy_req_headers(req, proxy_req, path_prefix, in_wpx_mode)
if in_wpx_mode {
proxy_req.Header.Set("Accept-Encoding", "")
}
//fmt.Printf ("proxy NEW req [%+v]\n", proxy_req.Header)
client = &http.Client{ client = &http.Client{
Transport: transport, Transport: transport,
CheckRedirect: prevent_follow_redirect, CheckRedirect: prevent_follow_redirect,
Timeout: 5 * time.Second, // TODO: make this configurable.... // don't specify Timeout for this here or make it configurable...
} }
resp, err = client.Do(proxy_req) resp, err = client.Do(proxy_req)
//resp, err = transport.RoundTrip(proxy_req) //resp, err = transport.RoundTrip(proxy_req) // any advantage if using RoundTrip instead?
if err != nil { if err != nil {
status_code = http.StatusInternalServerError; w.WriteHeader(status_code) status_code = http.StatusInternalServerError; w.WriteHeader(status_code)
goto oops goto oops
} else { } else {
status_code = resp.StatusCode status_code = resp.StatusCode
if upgrade_required && resp.StatusCode == http.StatusSwitchingProtocols { if upgrade_required && resp.StatusCode == http.StatusSwitchingProtocols {
s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) s.log.Write(pxy.id, LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code)
err = pxy.serve_upgraded(w, req, resp) err = pxy.serve_upgraded(w, req, resp)
if err != nil { goto oops } if err != nil { goto oops }
return // print the log mesage before calling serve_upgraded() and exit here return 0, nil// print the log mesage before calling serve_upgraded() and exit here
} else { } else {
var hdr http.Header var outhdr http.Header
defer resp.Body.Close() var resp_hdr http.Header
var resp_body io.Reader
hdr = w.Header() defer resp.Body.Close()
copy_headers(hdr, resp.Header) resp_hdr = resp.Header
delete_hop_by_hop_headers(hdr) resp_body = resp.Body
if in_wpx_mode && s.wpx_resp_tf != nil {
resp_body = s.wpx_resp_tf(path_prefix, resp)
}
outhdr = w.Header()
copy_headers(outhdr, resp_hdr)
delete_hop_by_hop_headers(outhdr)
if path_prefix == "" { if path_prefix == "" {
hdr.Add("Set-Cookie", fmt.Sprintf("%s=%s-%s; Path=/; HttpOnly", SERVER_PROXY_ID_COOKIE, conn_id, route_id)) outhdr.Add("Set-Cookie", fmt.Sprintf("%s=%s-%s; Path=/; HttpOnly", SERVER_PROXY_ID_COOKIE, conn_id, route_id))
} }
w.WriteHeader(status_code) w.WriteHeader(status_code)
io.Copy(w, resp.Body)
_, err = io.Copy(w, resp_body)
if err != nil {
s.log.Write(pxy.id, LOG_WARN, "[%s] %s %s %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error())
}
// TODO: handle trailers // TODO: handle trailers
} }
} }
//done: //done:
s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) return status_code, nil
return
oops: oops:
s.log.Write("", LOG_ERROR, "[%s] %s %s %d - %s", req.RemoteAddr, req.Method, req.URL.String(), status_code, err.Error()) return status_code, err
return
} }
// ------------------------------------ // ------------------------------------
type server_proxy_xterm_file struct {
s *Server
file string
}
type server_proxy_xterm_session_info struct { type server_proxy_xterm_session_info struct {
ConnId string ConnId string
RouteId string RouteId string
} }
func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (pxy *server_proxy_xterm_file) GetId() string {
return pxy.id
}
func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var status_code int var status_code int
var err error var err error
@ -535,12 +553,10 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R
} }
//done: //done:
s.log.Write("", LOG_INFO, "[%s] %s %s %d", req.RemoteAddr, req.Method, req.URL.String(), status_code) return status_code, nil
return
oops: oops:
s.log.Write("", LOG_ERROR, "[%s] %s %s %d - %s", req.RemoteAddr, req.Method, req.URL.String(), status_code, err.Error()) return status_code, err
return
} }
// ------------------------------------ // ------------------------------------
@ -548,6 +564,7 @@ oops:
type server_proxy_ssh_ws struct { type server_proxy_ssh_ws struct {
s *Server s *Server
ws *websocket.Conn ws *websocket.Conn
id string
} }
type json_ssh_ws_event struct { type json_ssh_ws_event struct {
@ -683,14 +700,14 @@ func (pxy *server_proxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) {
n, err = out.Read(buf) n, err = out.Read(buf)
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
s.log.Write("", LOG_ERROR, "Read from SSH stdout error - %s", err.Error()) s.log.Write(pxy.id, LOG_ERROR, "Read from SSH stdout error - %s", err.Error())
} }
break break
} }
if n > 0 { if n > 0 {
err = pxy.send_ws_data(ws, "iov", string(buf[:n])) err = pxy.send_ws_data(ws, "iov", string(buf[:n]))
if err != nil { if err != nil {
s.log.Write("", LOG_ERROR, "Failed to send to websocket - %s", err.Error()) s.log.Write(pxy.id, LOG_ERROR, "Failed to send to websocket - %s", err.Error())
break break
} }
} }
@ -724,13 +741,13 @@ ws_recv_loop:
defer wg.Done() defer wg.Done()
c, sess, in, out, err = pxy.connect_ssh(connect_ssh_ctx, username, password, r) c, sess, in, out, err = pxy.connect_ssh(connect_ssh_ctx, username, password, r)
if err != nil { if err != nil {
s.log.Write("", LOG_ERROR, "failed to connect ssh - %s", err.Error()) s.log.Write(pxy.id, LOG_ERROR, "failed to connect ssh - %s", err.Error())
pxy.send_ws_data(ws, "error", err.Error()) pxy.send_ws_data(ws, "error", err.Error())
ws.Close() // dirty way to flag out the error ws.Close() // dirty way to flag out the error
} else { } else {
err = pxy.send_ws_data(ws, "status", "opened") err = pxy.send_ws_data(ws, "status", "opened")
if err != nil { if err != nil {
s.log.Write("", LOG_ERROR, "Failed to write opened event to websocket - %s", err.Error()) s.log.Write(pxy.id, LOG_ERROR, "Failed to write opened event to websocket - %s", err.Error())
ws.Close() // dirty way to flag out the error ws.Close() // dirty way to flag out the error
} else { } else {
conn_ready_chan <- true conn_ready_chan <- true
@ -762,7 +779,7 @@ ws_recv_loop:
rows, _ = strconv.Atoi(ev.Data[0]) rows, _ = strconv.Atoi(ev.Data[0])
cols, _ = strconv.Atoi(ev.Data[1]) cols, _ = strconv.Atoi(ev.Data[1])
sess.WindowChange(rows, cols) sess.WindowChange(rows, cols)
s.log.Write("", LOG_DEBUG, "Resized terminal to %d,%d", rows, cols) s.log.Write(pxy.id, LOG_DEBUG, "Resized terminal to %d,%d", rows, cols)
// ignore error // ignore error
} }
} }
@ -782,8 +799,8 @@ done:
if c != nil { c.Close() } if c != nil { c.Close() }
wg.Wait() wg.Wait()
if err != nil { if err != nil {
s.log.Write("", LOG_ERROR, "[%s] %s %s - %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error()) s.log.Write(pxy.id, LOG_ERROR, "[%s] %s %s - %s", req.RemoteAddr, req.Method, req.URL.String(), err.Error())
} else { } else {
s.log.Write("", LOG_DEBUG, "[%s] %s %s - ended", req.RemoteAddr, req.Method, req.URL.String()) s.log.Write(pxy.id, LOG_DEBUG, "[%s] %s %s - ended", req.RemoteAddr, req.Method, req.URL.String())
} }
} }

110
server.go
View File

@ -9,9 +9,11 @@ import "log"
import "net" import "net"
import "net/http" import "net/http"
import "net/netip" import "net/netip"
import "plugin"
import "strconv" import "strconv"
import "sync" import "sync"
import "sync/atomic" import "sync/atomic"
import "time"
import "unsafe" import "unsafe"
import "golang.org/x/net/websocket" import "golang.org/x/net/websocket"
@ -33,6 +35,8 @@ type ServerRouteMap = map[RouteId]*ServerRoute
type ServerPeerConnMap = map[PeerId]*ServerPeerConn type ServerPeerConnMap = map[PeerId]*ServerPeerConn
type ServerSvcPortMap = map[PortId]ConnRouteId type ServerSvcPortMap = map[PortId]ConnRouteId
type ServerWpxResponseTransformer func(path_prefix string, resp *http.Response) io.Reader
type Server struct { type Server struct {
ctx context.Context ctx context.Context
ctx_cancel context.CancelFunc ctx_cancel context.CancelFunc
@ -56,6 +60,7 @@ type Server struct {
wpx_addr []string wpx_addr []string
wpx_mux *http.ServeMux wpx_mux *http.ServeMux
wpx []*http.Server // proxy server than handles http/https only wpx []*http.Server // proxy server than handles http/https only
wpx_resp_tf ServerWpxResponseTransformer
ctl_addr []string ctl_addr []string
ctl_prefix string ctl_prefix string
@ -129,6 +134,12 @@ type ServerRoute struct {
stop_req atomic.Bool stop_req atomic.Bool
} }
type ServerPluginInterface interface {
ModifyResponse(w http.ResponseWriter, r *http.Request)
Init(server *Server)
Cleanup()
}
type GuardedPacketStreamServer struct { type GuardedPacketStreamServer struct {
mtx sync.Mutex mtx sync.Mutex
//pss Hodu_PacketStreamServer //pss Hodu_PacketStreamServer
@ -491,7 +502,7 @@ func (cts *ServerConn) ReportEvent(route_id RouteId, pts_id PeerId, event_type P
cts.route_mtx.Lock() cts.route_mtx.Lock()
r, ok = cts.route_map[route_id] r, ok = cts.route_map[route_id]
if (!ok) { if !ok {
cts.route_mtx.Unlock() cts.route_mtx.Unlock()
return fmt.Errorf("non-existent route id - %d", route_id) return fmt.Errorf("non-existent route id - %d", route_id)
} }
@ -905,16 +916,44 @@ func (hlw *server_http_log_writer) Write(p []byte) (n int, err error) {
return len(p), nil return len(p), nil
} }
type ServerHttpHandler interface {
GetId() string
ServeHTTP (w http.ResponseWriter, req *http.Request) (int, error)
}
func (s *Server) wrap_http_handler(handler ServerHttpHandler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var status_code int
var err error
var start_time time.Time
var time_taken time.Duration
start_time = time.Now()
status_code, err = handler.ServeHTTP(w, req)
time_taken = time.Now().Sub(start_time)
if status_code > 0 {
if err == nil {
s.log.Write(handler.GetId(), LOG_INFO, "[%s] %s %s %d %v", req.RemoteAddr, req.Method, req.URL.String(), status_code, time_taken)
} else {
s.log.Write(handler.GetId(), LOG_INFO, "[%s] %s %s %d %v - %s", req.RemoteAddr, req.Method, req.URL.String(), status_code, time_taken, err.Error())
}
}
})
}
func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs []string, pxy_addrs []string, wpx_addrs []string, ctl_prefix string, ctltlscfg *tls.Config, rpctlscfg *tls.Config, pxytlscfg *tls.Config, wpxtlscfg *tls.Config, rpc_max int, peer_max int) (*Server, error) { func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs []string, pxy_addrs []string, wpx_addrs []string, ctl_prefix string, ctltlscfg *tls.Config, rpctlscfg *tls.Config, pxytlscfg *tls.Config, wpxtlscfg *tls.Config, rpc_max int, peer_max int) (*Server, error) {
var s Server var s Server
var l *net.TCPListener var l *net.TCPListener
var rpcaddr *net.TCPAddr var rpcaddr *net.TCPAddr
var err error
var addr string var addr string
var gl *net.TCPListener var gl *net.TCPListener
var i int var i int
var hs_log *log.Logger var hs_log *log.Logger
var opts []grpc.ServerOption var opts []grpc.ServerOption
var plgin *plugin.Plugin
var plgsym plugin.Symbol
var err error
if len(rpc_addrs) <= 0 { if len(rpc_addrs) <= 0 {
return nil, fmt.Errorf("no server addresses provided") return nil, fmt.Errorf("no server addresses provided")
@ -980,7 +1019,7 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
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})
s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes/{route_id}", &server_ctl_server_conns_id_routes_id{s: &s}) s.ctl_mux.Handle(s.ctl_prefix + "/server-conns/{conn_id}/routes/{route_id}", &server_ctl_server_conns_id_routes_id{s: &s})
s.ctl_mux.Handle(s.ctl_prefix + "/stats", &server_ctl_stats{s: &s}) s.ctl_mux.Handle(s.ctl_prefix + "/stats", s.wrap_http_handler(&server_ctl_stats{s: &s, id: "ctl-stat"}))
s.ctl_addr = make([]string, len(ctl_addrs)) s.ctl_addr = make([]string, len(ctl_addrs))
s.ctl = make([]*http.Server, len(ctl_addrs)) s.ctl = make([]*http.Server, len(ctl_addrs))
@ -998,20 +1037,20 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
// --------------------------------------------------------- // ---------------------------------------------------------
s.pxy_ws = &server_proxy_ssh_ws{s: &s} s.pxy_ws = &server_proxy_ssh_ws{s: &s, id: "pxy-ssh"}
s.pxy_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh_ws,_http configurable... s.pxy_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh_ws,_http configurable...
s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}",
websocket.Handler(func(ws *websocket.Conn) { s.pxy_ws.ServeWebsocket(ws) })) websocket.Handler(func(ws *websocket.Conn) { s.pxy_ws.ServeWebsocket(ws) }))
s.pxy_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", &server_ctl_server_conns_id_routes_id{s: &s}) s.pxy_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", &server_ctl_server_conns_id_routes_id{s: &s})
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", &server_proxy_xterm_file{s: &s, file: "xterm.html"}) s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", s.wrap_http_handler(&server_proxy_xterm_file{s: &s, file: "xterm.html", id: "pxy-file"}))
s.pxy_mux.Handle("/_ssh/xterm.js", &server_proxy_xterm_file{s: &s, file: "xterm.js"}) s.pxy_mux.Handle("/_ssh/xterm.js", s.wrap_http_handler(&server_proxy_xterm_file{s: &s, file: "xterm.js", id: "pxy-file"}))
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-addon-fit.js", s.wrap_http_handler(&server_proxy_xterm_file{s: &s, file: "xterm-addon-fit.js", id: "pxy-file"}))
s.pxy_mux.Handle("/_ssh/xterm.css", &server_proxy_xterm_file{s: &s, file: "xterm.css"}) s.pxy_mux.Handle("/_ssh/xterm.css", s.wrap_http_handler(&server_proxy_xterm_file{s: &s, file: "xterm.css", id: "pxy-file"}))
s.pxy_mux.Handle("/_ssh/", &server_proxy_xterm_file{s: &s, file: "_forbidden"}) s.pxy_mux.Handle("/_ssh/", s.wrap_http_handler(&server_proxy_xterm_file{s: &s, file: "_forbidden", id: "pxy-file"}))
s.pxy_mux.Handle("/_http/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_main{s: &s, prefix: "/_http"}) s.pxy_mux.Handle("/_http/{conn_id}/{route_id}/{trailer...}", s.wrap_http_handler(&server_proxy_http_main{s: &s, prefix: "/_http", id: "pxy-http"}))
s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", &server_proxy_http_init{s: &s, prefix: "/_init"}) s.pxy_mux.Handle("/_init/{conn_id}/{route_id}/{trailer...}", s.wrap_http_handler(&server_proxy_http_init{s: &s, prefix: "/_init", id: "pxy-http"}))
s.pxy_mux.Handle("/", &server_proxy_http_main{s: &s, prefix: ""}) s.pxy_mux.Handle("/", s.wrap_http_handler(&server_proxy_http_main{s: &s, prefix: "", id: "pxy-http"}))
s.pxy_addr = make([]string, len(pxy_addrs)) s.pxy_addr = make([]string, len(pxy_addrs))
s.pxy = make([]*http.Server, len(pxy_addrs)) s.pxy = make([]*http.Server, len(pxy_addrs))
@ -1030,7 +1069,7 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
// --------------------------------------------------------- // ---------------------------------------------------------
s.wpx_mux = http.NewServeMux() s.wpx_mux = http.NewServeMux()
s.wpx_mux.Handle("/{port_id}/{trailer...}", &server_proxy_http_main{s: &s, prefix: PORT_ID_MARKER}) s.wpx_mux.Handle("/{port_id}/{trailer...}", s.wrap_http_handler(&server_proxy_http_main{s: &s, prefix: PORT_ID_MARKER, id: "wpx"}))
s.wpx_mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { s.wpx_mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
}) })
@ -1055,21 +1094,49 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs
s.stats.peers.Store(0) s.stats.peers.Store(0)
s.stats.ssh_proxy_sessions.Store(0) s.stats.ssh_proxy_sessions.Store(0)
// ---------------------------------------------------------
plgin, err = plugin.Open("modres.so")
if err == nil {
plgsym, err = plgin.Lookup("Plugin")
if err == nil {
var plg ServerPluginInterface
var ok bool
switch plgsym.(type) {
case *ServerPluginInterface:
var tmp *ServerPluginInterface
tmp, ok = plgsym.(*ServerPluginInterface)
if ok { plg = *tmp }
case ServerPluginInterface:
plg, ok = plgsym.(ServerPluginInterface)
}
//plg, ok = plgsym.(*ServerPluginInterface)
if ok {
plg.Init(&s)
plg.Cleanup()
} else {
fmt.Printf ("YYYYYYYYYYYYYYY NOT OK\n")
}
} else {
fmt.Printf ("YYYYYYYYYYYYYYY[%v]\n", err)
}
} else {
fmt.Printf ("XXXXXX[%v]\n", err)
}
return &s, nil return &s, nil
oops: oops:
// TODO: check if rpc_svr needs to be closed. probably not. closing the listen may be good enough if gl != nil { gl.Close() }
if gl != nil { for _, l = range s.rpc { l.Close() }
gl.Close()
}
for _, l = range s.rpc {
l.Close()
}
s.rpc = make([]*net.TCPListener, 0) s.rpc = make([]*net.TCPListener, 0)
return nil, err return nil, err
} }
func (s *Server) SetWpxResponseTransformer(tf ServerWpxResponseTransformer) {
s.wpx_resp_tf = tf
}
func (s *Server) run_grpc_server(idx int, wg *sync.WaitGroup) error { func (s *Server) run_grpc_server(idx int, wg *sync.WaitGroup) error {
var l *net.TCPListener var l *net.TCPListener
var err error var err error
@ -1428,7 +1495,6 @@ func (s *Server) FindServerConnByAddr(addr net.Addr) *ServerConn {
return cts return cts
} }
func (s *Server) FindServerRouteById(id ConnId, route_id RouteId) *ServerRoute { func (s *Server) FindServerRouteById(id ConnId, route_id RouteId) *ServerRoute {
var cts *ServerConn var cts *ServerConn
var ok bool var ok bool

View File

@ -16,7 +16,7 @@ func monotonic_time() uint64 {
var uts unix.Timespec var uts unix.Timespec
n = nanotime() // hopefully it's faster than a system call. say, vdso is utilized. n = nanotime() // hopefully it's faster than a system call. say, vdso is utilized.
if (n >= 0) { return uint64(n) } if n >= 0 { return uint64(n) }
err = unix.ClockGettime(unix.CLOCK_MONOTONIC, &uts) err = unix.ClockGettime(unix.CLOCK_MONOTONIC, &uts)
if err != nil { if err != nil {

82
transform.go Normal file
View File

@ -0,0 +1,82 @@
package hodu
import "bytes"
import "golang.org/x/text/transform"
type Transformer struct {
replacement []byte
needle []byte
needle_len int
}
func NewBytesTransformer(needle []byte, replacement []byte) *Transformer {
return &Transformer{needle: needle, replacement: replacement, needle_len: len(needle)}
}
func NewStringTransformer(needle string, replacement string) *Transformer {
return NewBytesTransformer([]byte(needle), []byte(replacement))
}
func (t *Transformer) Reset() {
// do nothing
}
func (t *Transformer) Transform(dst []byte, src []byte, at_eof bool) (int, int, error) {
var n int
var i int
var ndst int
var nsrc int
var rem int
var err error
if t.needle_len <= 0 {
n, err = t.copy_all(dst, src)
return n, n, err
}
nsrc = 0; ndst = 0
for {
i = bytes.Index(src[nsrc:], t.needle)
if i == -1 { break }
// copy the part before the match
n, err = t.copy_all(dst[ndst:], src[nsrc:nsrc+i])
nsrc += n; ndst += n
if err != nil { goto done }
// copy the new value in place of the match
n, err = t.copy_all(dst[ndst:], t.replacement)
if err != nil { goto done }
ndst += n; nsrc += t.needle_len
}
if at_eof {
n, err = t.copy_all(dst[ndst:], src[nsrc:])
ndst += n; nsrc += n
goto done
}
rem = len(src[nsrc:])
if rem >= t.needle_len {
n, err = t.copy_all(dst[ndst:], src[nsrc: nsrc + (rem - t.needle_len) + 1])
nsrc += n; ndst += n
if err != nil { goto done }
}
// ErrShortSrc means that the source buffer has insufficient data to
// complete the transformation.
err = transform.ErrShortSrc
done:
return ndst, nsrc, err
}
func (t *Transformer) copy_all(dst []byte, src []byte) (int, error) {
var n int
var err error
n = copy(dst, src)
// ErrShortDst means that the destination buffer was too short to
// receive all of the transformed bytes.
if n < len(src) { err = transform.ErrShortDst }
return n, err
}

View File

@ -106,7 +106,11 @@ window.onload = function(event) {
const username_field = document.getElementById('username'); const username_field = document.getElementById('username');
const password_field= document.getElementById('password'); const password_field= document.getElementById('password');
const term = new window.Terminal(); const term = new window.Terminal({
lineHeight: 1.2,
//fontSize: 14,
fontFamily: 'Ubuntu Mono, Consolas, SF Mono, courier-new, courier, monospace'
});
const fit_addon = new window.FitAddon.FitAddon(); const fit_addon = new window.FitAddon.FitAddon();
const text_decoder = new TextDecoder(); const text_decoder = new TextDecoder();