feat(tunnel): refactor VNC and proxy handling with structured routing
- Updated VNC and proxy handling in the tunnel to utilize a structured routing approach, enhancing clarity and maintainability. - Replaced direct port checks with a new `forwardRoute` struct to encapsulate routing information, including channel type, container ID, and port. - Modified request handling to streamline the forwarding process and improve error handling for unknown routes. - Enhanced tests to validate the new routing logic and ensure consistent behavior across VNC and proxy requests. Made-with: Cursor
This commit is contained in:
parent
d26bbd1e1f
commit
9c9701ed7e
10 changed files with 379 additions and 245 deletions
|
|
@ -203,7 +203,7 @@ func nodeToHostOption(s taitypes.NodeMeta) computerOption {
|
||||||
Status: status,
|
Status: status,
|
||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
VNC: s.Ports.VNC > 0,
|
VNC: s.Capabilities.VNC,
|
||||||
System: computerSystemInfo{
|
System: computerSystemInfo{
|
||||||
OS: s.System.OS,
|
OS: s.System.OS,
|
||||||
Arch: s.System.Arch,
|
Arch: s.System.Arch,
|
||||||
|
|
@ -239,7 +239,7 @@ func nodeToNodeOption(s taitypes.NodeMeta) computerOption {
|
||||||
Status: status,
|
Status: status,
|
||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
VNC: s.Ports.VNC > 0,
|
VNC: s.Capabilities.VNC,
|
||||||
System: computerSystemInfo{
|
System: computerSystemInfo{
|
||||||
OS: s.System.OS,
|
OS: s.System.OS,
|
||||||
Arch: s.System.Arch,
|
Arch: s.System.Arch,
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ func hostToResponse(s taitypes.NodeMeta) sandboxResponse {
|
||||||
Policy: "persistent",
|
Policy: "persistent",
|
||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
VNC: s.Ports.VNC > 0,
|
VNC: s.Capabilities.VNC,
|
||||||
CreatedAt: s.ConnectedAt,
|
CreatedAt: s.ConnectedAt,
|
||||||
LastActive: s.LastPing,
|
LastActive: s.LastPing,
|
||||||
System: sandboxSystemInfo{
|
System: sandboxSystemInfo{
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,7 @@ func capsFromMap(m map[string]bool) types.Capabilities {
|
||||||
Docker: m["docker"],
|
Docker: m["docker"],
|
||||||
K8s: m["k8s"],
|
K8s: m["k8s"],
|
||||||
HostExec: m["host_exec"],
|
HostExec: m["host_exec"],
|
||||||
|
VNC: m["vnc"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -12,11 +13,24 @@ import (
|
||||||
"github.com/yaoapp/yao/tai/types"
|
"github.com/yaoapp/yao/tai/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultVNCPort = 5900
|
||||||
|
|
||||||
|
// forwardRoute holds the structured routing information extracted from the
|
||||||
|
// incoming request URL. It is passed to RequestForward so that Yao can
|
||||||
|
// populate the TunnelControl proto fields and Tai can route directly without
|
||||||
|
// parsing the first packet.
|
||||||
|
type forwardRoute struct {
|
||||||
|
channelType string // "proxy" | "vnc"
|
||||||
|
containerID string // target container or "__host__"
|
||||||
|
containerPort int // container-internal port (vnc default 5900)
|
||||||
|
subpath string // rewritten request path for the container
|
||||||
|
}
|
||||||
|
|
||||||
// HandleForward handles HTTP/VNC/any TCP-level forwarding through the gRPC tunnel.
|
// HandleForward handles HTTP/VNC/any TCP-level forwarding through the gRPC tunnel.
|
||||||
// Route: ANY /tai/:taiID/proxy/*path and GET /tai/:taiID/vnc/*path
|
// Route: ANY /tai/:taiID/proxy/*path and GET /tai/:taiID/vnc/*path
|
||||||
//
|
//
|
||||||
// It hijacks the browser's raw TCP connection, asks Tai to open a Forward stream
|
// It hijacks the browser's raw TCP connection, asks Tai to open a Forward stream
|
||||||
// to the resolved target port, rewrites the request path, and then performs
|
// with explicit routing information, rewrites the request path, and then performs
|
||||||
// bidirectional byte-level bridging. No protocol parsing beyond HTTP hijack.
|
// bidirectional byte-level bridging. No protocol parsing beyond HTTP hijack.
|
||||||
func (h *TunnelHandler) HandleForward(c *gin.Context) {
|
func (h *TunnelHandler) HandleForward(c *gin.Context) {
|
||||||
logger := h.logger
|
logger := h.logger
|
||||||
|
|
@ -30,16 +44,18 @@ func (h *TunnelHandler) HandleForward(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetPort := resolveTargetPort(c, node)
|
route, err := resolveRoute(c, node)
|
||||||
if targetPort == 0 {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot resolve target port"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rewrittenReq := rewriteRequest(c.Request, taiID)
|
rewrittenReq := rewriteRequest(c.Request, taiID, route)
|
||||||
logger.Debug("[forward] "+node.Mode+" → tai:"+fmt.Sprintf("%d", targetPort),
|
logger.Debug("[forward] "+node.Mode+" → tai",
|
||||||
"tai_id", taiID,
|
"tai_id", taiID,
|
||||||
"addr", node.Addr,
|
"type", route.channelType,
|
||||||
|
"container", route.containerID,
|
||||||
|
"container_port", route.containerPort,
|
||||||
"path", rewrittenReq.URL.Path,
|
"path", rewrittenReq.URL.Path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,10 +71,10 @@ func (h *TunnelHandler) HandleForward(c *gin.Context) {
|
||||||
}
|
}
|
||||||
defer browserConn.Close()
|
defer browserConn.Close()
|
||||||
|
|
||||||
fwd, err := h.RequestForward(taiID, targetPort)
|
fwd, err := h.RequestForward(taiID, route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[forward] stream failed",
|
logger.Error("[forward] stream failed",
|
||||||
"tai_id", taiID, "port", targetPort, "err", err)
|
"tai_id", taiID, "type", route.channelType, "err", err)
|
||||||
browserConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
|
browserConn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -94,47 +110,91 @@ func HandleForwardLazy(c *gin.Context) {
|
||||||
h.HandleForward(c)
|
h.HandleForward(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveTargetPort determines the Tai-side port from the route pattern.
|
// resolveRoute extracts structured routing info from the request URL path.
|
||||||
func resolveTargetPort(c *gin.Context, node *types.NodeMeta) int {
|
//
|
||||||
|
// For proxy requests (/tai/:taiID/proxy/{containerID}:{port}/{subpath}):
|
||||||
|
//
|
||||||
|
// channelType = "proxy", containerPort from URL, subpath = remaining path.
|
||||||
|
//
|
||||||
|
// For VNC requests (/tai/:taiID/vnc/{containerID}/ws):
|
||||||
|
//
|
||||||
|
// channelType = "vnc", containerPort = 5900, subpath = /vnc/{containerID}/ws.
|
||||||
|
func resolveRoute(c *gin.Context, node *types.NodeMeta) (*forwardRoute, error) {
|
||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
|
taiID := c.Param("taiID")
|
||||||
if strings.Contains(path, "/vnc/") {
|
|
||||||
if node.Ports.VNC != 0 {
|
|
||||||
return node.Ports.VNC
|
|
||||||
}
|
|
||||||
return 16080
|
|
||||||
}
|
|
||||||
if strings.Contains(path, "/proxy/") {
|
|
||||||
if node.Ports.HTTP != 0 {
|
|
||||||
return node.Ports.HTTP
|
|
||||||
}
|
|
||||||
return 8099
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// rewriteRequest clones the request and strips the Yao-side route prefix,
|
|
||||||
// leaving only what the Tai-side handler expects.
|
|
||||||
//
|
|
||||||
// The Tai httpproxy expects /{containerID}:{port}/..., so the /proxy prefix
|
|
||||||
// is stripped. The Tai VNC router expects /vnc/{containerID}/ws, so the /vnc
|
|
||||||
// prefix is kept.
|
|
||||||
//
|
|
||||||
// /v1/tai/abc/proxy/cid:8080/foo → /cid:8080/foo
|
|
||||||
// /v1/tai/abc/vnc/cid/ws → /vnc/cid/ws
|
|
||||||
func rewriteRequest(orig *http.Request, taiID string) *http.Request {
|
|
||||||
r := orig.Clone(orig.Context())
|
|
||||||
|
|
||||||
marker := "/tai/" + taiID
|
marker := "/tai/" + taiID
|
||||||
if idx := strings.Index(r.URL.Path, marker); idx >= 0 {
|
idx := strings.Index(path, marker)
|
||||||
rest := r.URL.Path[idx+len(marker):]
|
if idx < 0 {
|
||||||
rest = strings.TrimPrefix(rest, "/proxy")
|
return nil, fmt.Errorf("cannot locate /tai/%s in path", taiID)
|
||||||
if rest == "" {
|
}
|
||||||
rest = "/"
|
rest := path[idx+len(marker):]
|
||||||
|
|
||||||
|
if strings.HasPrefix(rest, "/vnc/") {
|
||||||
|
// /vnc/{containerID}/ws → containerID, port=5900
|
||||||
|
tail := strings.TrimPrefix(rest, "/vnc/")
|
||||||
|
containerID := tail
|
||||||
|
if slashIdx := strings.IndexByte(tail, '/'); slashIdx >= 0 {
|
||||||
|
containerID = tail[:slashIdx]
|
||||||
}
|
}
|
||||||
r.URL.Path = rest
|
if containerID == "" {
|
||||||
|
return nil, fmt.Errorf("missing container ID in VNC path: %s", path)
|
||||||
|
}
|
||||||
|
return &forwardRoute{
|
||||||
|
channelType: "vnc",
|
||||||
|
containerID: containerID,
|
||||||
|
containerPort: defaultVNCPort,
|
||||||
|
subpath: rest, // keep /vnc/{containerID}/ws
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(rest, "/proxy/") {
|
||||||
|
// /proxy/{containerID}:{port}/{subpath}
|
||||||
|
proxyPath := strings.TrimPrefix(rest, "/proxy")
|
||||||
|
// proxyPath = /{containerID}:{port}/{subpath}
|
||||||
|
proxyPath = strings.TrimPrefix(proxyPath, "/")
|
||||||
|
if proxyPath == "" {
|
||||||
|
return nil, fmt.Errorf("empty proxy path")
|
||||||
|
}
|
||||||
|
|
||||||
|
slash := strings.IndexByte(proxyPath, '/')
|
||||||
|
var head, subpath string
|
||||||
|
if slash == -1 {
|
||||||
|
head = proxyPath
|
||||||
|
subpath = "/"
|
||||||
|
} else {
|
||||||
|
head = proxyPath[:slash]
|
||||||
|
subpath = proxyPath[slash:]
|
||||||
|
}
|
||||||
|
|
||||||
|
colon := strings.LastIndexByte(head, ':')
|
||||||
|
if colon < 0 {
|
||||||
|
return nil, fmt.Errorf("missing port in proxy path: %s", path)
|
||||||
|
}
|
||||||
|
containerID := head[:colon]
|
||||||
|
portStr := head[colon+1:]
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid port %q in proxy path: %w", portStr, err)
|
||||||
|
}
|
||||||
|
return &forwardRoute{
|
||||||
|
channelType: "proxy",
|
||||||
|
containerID: containerID,
|
||||||
|
containerPort: port,
|
||||||
|
subpath: subpath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown route pattern: %s", rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteRequest clones the request and sets the path to the route's subpath.
|
||||||
|
//
|
||||||
|
// For proxy: the path becomes the subpath (e.g. /foo/bar).
|
||||||
|
// For VNC: the path keeps /vnc/{containerID}/ws as-is.
|
||||||
|
func rewriteRequest(orig *http.Request, taiID string, route *forwardRoute) *http.Request {
|
||||||
|
r := orig.Clone(orig.Context())
|
||||||
|
r.URL.Path = route.subpath
|
||||||
r.RequestURI = r.URL.RequestURI()
|
r.RequestURI = r.URL.RequestURI()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,155 +16,131 @@ func init() {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveTargetPort_VNC(t *testing.T) {
|
func TestResolveRoute_Proxy(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
vncPort int
|
wantType string
|
||||||
wantPort int
|
wantContainer string
|
||||||
|
wantPort int
|
||||||
|
wantSubpath string
|
||||||
}{
|
}{
|
||||||
{"default_vnc", "/tai/abc/vnc/websockify", 0, 16080},
|
{
|
||||||
{"custom_vnc", "/tai/abc/vnc/websockify", 5900, 5900},
|
"basic_proxy",
|
||||||
|
"/tai/abc/proxy/cid123:8080/foo/bar",
|
||||||
|
"proxy", "cid123", 8080, "/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"proxy_root",
|
||||||
|
"/tai/abc/proxy/cid:3000",
|
||||||
|
"proxy", "cid", 3000, "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"proxy_host",
|
||||||
|
"/v1/tai/abc/proxy/__host__:9090/api",
|
||||||
|
"proxy", "__host__", 9090, "/api",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request = &http.Request{URL: &url.URL{Path: tt.path}}
|
c.Request = &http.Request{URL: &url.URL{Path: tt.path}}
|
||||||
node := &types.NodeMeta{Ports: types.Ports{VNC: tt.vncPort}}
|
c.Params = gin.Params{{Key: "taiID", Value: "abc"}}
|
||||||
got := resolveTargetPort(c, node)
|
node := &types.NodeMeta{}
|
||||||
if got != tt.wantPort {
|
|
||||||
t.Errorf("resolveTargetPort = %d, want %d", got, tt.wantPort)
|
r, err := resolveRoute(c, node)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolveRoute error: %v", err)
|
||||||
|
}
|
||||||
|
if r.channelType != tt.wantType {
|
||||||
|
t.Errorf("channelType = %q, want %q", r.channelType, tt.wantType)
|
||||||
|
}
|
||||||
|
if r.containerID != tt.wantContainer {
|
||||||
|
t.Errorf("containerID = %q, want %q", r.containerID, tt.wantContainer)
|
||||||
|
}
|
||||||
|
if r.containerPort != tt.wantPort {
|
||||||
|
t.Errorf("containerPort = %d, want %d", r.containerPort, tt.wantPort)
|
||||||
|
}
|
||||||
|
if r.subpath != tt.wantSubpath {
|
||||||
|
t.Errorf("subpath = %q, want %q", r.subpath, tt.wantSubpath)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveTargetPort_Proxy(t *testing.T) {
|
func TestResolveRoute_VNC(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
httpPort int
|
wantContainer string
|
||||||
wantPort int
|
wantPort int
|
||||||
}{
|
}{
|
||||||
{"default_proxy", "/tai/abc/proxy/api/v1/foo", 0, 8099},
|
{"vnc_basic", "/tai/abc/vnc/container1/ws", "container1", defaultVNCPort},
|
||||||
{"custom_proxy", "/tai/abc/proxy/api/v1/foo", 9090, 9090},
|
{"vnc_host", "/v1/tai/abc/vnc/__host__/ws", "__host__", defaultVNCPort},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request = &http.Request{URL: &url.URL{Path: tt.path}}
|
c.Request = &http.Request{URL: &url.URL{Path: tt.path}}
|
||||||
node := &types.NodeMeta{Ports: types.Ports{HTTP: tt.httpPort}}
|
c.Params = gin.Params{{Key: "taiID", Value: "abc"}}
|
||||||
got := resolveTargetPort(c, node)
|
node := &types.NodeMeta{}
|
||||||
if got != tt.wantPort {
|
|
||||||
t.Errorf("resolveTargetPort = %d, want %d", got, tt.wantPort)
|
r, err := resolveRoute(c, node)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolveRoute error: %v", err)
|
||||||
|
}
|
||||||
|
if r.channelType != "vnc" {
|
||||||
|
t.Errorf("channelType = %q, want vnc", r.channelType)
|
||||||
|
}
|
||||||
|
if r.containerID != tt.wantContainer {
|
||||||
|
t.Errorf("containerID = %q, want %q", r.containerID, tt.wantContainer)
|
||||||
|
}
|
||||||
|
if r.containerPort != tt.wantPort {
|
||||||
|
t.Errorf("containerPort = %d, want %d", r.containerPort, tt.wantPort)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveTargetPort_Unknown(t *testing.T) {
|
func TestResolveRoute_Unknown(t *testing.T) {
|
||||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request = &http.Request{URL: &url.URL{Path: "/tai/abc/unknown/something"}}
|
c.Request = &http.Request{URL: &url.URL{Path: "/tai/abc/unknown/something"}}
|
||||||
|
c.Params = gin.Params{{Key: "taiID", Value: "abc"}}
|
||||||
node := &types.NodeMeta{}
|
node := &types.NodeMeta{}
|
||||||
got := resolveTargetPort(c, node)
|
|
||||||
if got != 0 {
|
_, err := resolveRoute(c, node)
|
||||||
t.Errorf("resolveTargetPort = %d, want 0", got)
|
if err == nil {
|
||||||
|
t.Error("expected error for unknown route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteRequest(t *testing.T) {
|
func TestRewriteRequest_Proxy(t *testing.T) {
|
||||||
tests := []struct {
|
u, _ := url.Parse("http://localhost/v1/tai/abc/proxy/cid:8080/foo")
|
||||||
name string
|
orig := &http.Request{
|
||||||
origPath string
|
Method: "GET",
|
||||||
taiID string
|
URL: u,
|
||||||
wantPath string
|
RequestURI: u.RequestURI(),
|
||||||
wantURI string
|
Host: "localhost",
|
||||||
}{
|
Header: http.Header{},
|
||||||
{
|
}
|
||||||
"proxy_path",
|
route := &forwardRoute{
|
||||||
"/tai/abc123/proxy/api/v1/data",
|
channelType: "proxy",
|
||||||
"abc123",
|
containerID: "cid",
|
||||||
"/api/v1/data",
|
containerPort: 8080,
|
||||||
"/api/v1/data",
|
subpath: "/foo",
|
||||||
},
|
|
||||||
{
|
|
||||||
"vnc_path",
|
|
||||||
"/tai/node-1/vnc/websockify",
|
|
||||||
"node-1",
|
|
||||||
"/vnc/websockify",
|
|
||||||
"/vnc/websockify",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with_query",
|
|
||||||
"/tai/node-1/proxy/api?foo=bar",
|
|
||||||
"node-1",
|
|
||||||
"/api",
|
|
||||||
"/api?foo=bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"exact_prefix",
|
|
||||||
"/tai/node-1",
|
|
||||||
"node-1",
|
|
||||||
"/",
|
|
||||||
"/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with_base_url",
|
|
||||||
"/v1/tai/node-1/proxy/api/v1/data",
|
|
||||||
"node-1",
|
|
||||||
"/api/v1/data",
|
|
||||||
"/api/v1/data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with_base_url_vnc",
|
|
||||||
"/v1/tai/abc123/vnc/__host__/ws",
|
|
||||||
"abc123",
|
|
||||||
"/vnc/__host__/ws",
|
|
||||||
"/vnc/__host__/ws",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"proxy_container_port",
|
|
||||||
"/v1/tai/abc/proxy/cid123:8080/foo",
|
|
||||||
"abc",
|
|
||||||
"/cid123:8080/foo",
|
|
||||||
"/cid123:8080/foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"no_match",
|
|
||||||
"/other/path",
|
|
||||||
"node-1",
|
|
||||||
"/other/path",
|
|
||||||
"/other/path",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
u, _ := url.Parse("http://localhost" + tt.origPath)
|
|
||||||
orig := &http.Request{
|
|
||||||
Method: "GET",
|
|
||||||
URL: u,
|
|
||||||
RequestURI: u.RequestURI(),
|
|
||||||
Host: "localhost",
|
|
||||||
Header: http.Header{},
|
|
||||||
}
|
|
||||||
|
|
||||||
got := rewriteRequest(orig, tt.taiID)
|
got := rewriteRequest(orig, "abc", route)
|
||||||
|
if got.URL.Path != "/foo" {
|
||||||
if got.URL.Path != tt.wantPath {
|
t.Errorf("path = %q, want /foo", got.URL.Path)
|
||||||
t.Errorf("path = %q, want %q", got.URL.Path, tt.wantPath)
|
}
|
||||||
}
|
if got == orig {
|
||||||
if got.RequestURI != tt.wantURI {
|
t.Error("rewriteRequest should return a clone")
|
||||||
t.Errorf("requestURI = %q, want %q", got.RequestURI, tt.wantURI)
|
|
||||||
}
|
|
||||||
if got == orig {
|
|
||||||
t.Error("rewriteRequest should return a clone, not the original")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteRequest_PreservesHeaders(t *testing.T) {
|
func TestRewriteRequest_VNC(t *testing.T) {
|
||||||
u, _ := url.Parse("http://localhost/tai/node-1/vnc/websockify")
|
u, _ := url.Parse("http://localhost/tai/node-1/vnc/cid/ws")
|
||||||
orig := &http.Request{
|
orig := &http.Request{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
URL: u,
|
URL: u,
|
||||||
|
|
@ -175,14 +151,20 @@ func TestRewriteRequest_PreservesHeaders(t *testing.T) {
|
||||||
"Upgrade": {"websocket"},
|
"Upgrade": {"websocket"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
route := &forwardRoute{
|
||||||
|
channelType: "vnc",
|
||||||
|
containerID: "cid",
|
||||||
|
containerPort: 5900,
|
||||||
|
subpath: "/vnc/cid/ws",
|
||||||
|
}
|
||||||
|
|
||||||
got := rewriteRequest(orig, "node-1")
|
got := rewriteRequest(orig, "node-1", route)
|
||||||
|
if got.URL.Path != "/vnc/cid/ws" {
|
||||||
|
t.Errorf("path = %q, want /vnc/cid/ws", got.URL.Path)
|
||||||
|
}
|
||||||
if got.Header.Get("Connection") != "Upgrade" {
|
if got.Header.Get("Connection") != "Upgrade" {
|
||||||
t.Error("expected Connection header preserved")
|
t.Error("expected Connection header preserved")
|
||||||
}
|
}
|
||||||
if got.Header.Get("Upgrade") != "websocket" {
|
|
||||||
t.Error("expected Upgrade header preserved")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleForwardLazy_NilHandler(t *testing.T) {
|
func TestHandleForwardLazy_NilHandler(t *testing.T) {
|
||||||
|
|
@ -217,29 +199,24 @@ func TestHandleForward_NodeNotFound(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleForward_NodeOffline(t *testing.T) {
|
func TestHandleForward_UnknownRoute(t *testing.T) {
|
||||||
reg := registry.NewForTest()
|
reg := registry.NewForTest()
|
||||||
h := NewTunnelHandler(reg)
|
h := NewTunnelHandler(reg)
|
||||||
|
|
||||||
reg.Register(®istry.TaiNode{
|
reg.Register(®istry.TaiNode{
|
||||||
TaiID: "offline-node",
|
TaiID: "online-node",
|
||||||
Mode: "tunnel",
|
Mode: "tunnel",
|
||||||
Ports: types.Ports{HTTP: 8099},
|
|
||||||
})
|
})
|
||||||
// Manually set status to offline via a Get() — the node is online by default
|
|
||||||
// after Register, but we need an offline one. We'll use Unregister + re-register
|
|
||||||
// pattern. Actually, let's just test with a node that doesn't exist:
|
|
||||||
// the NodeNotFound test above covers that case. Instead, test zero port.
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := gin.CreateTestContext(w)
|
c, _ := gin.CreateTestContext(w)
|
||||||
c.Request = httptest.NewRequest("GET", "/tai/offline-node/unknown/foo", nil)
|
c.Request = httptest.NewRequest("GET", "/tai/online-node/unknown/foo", nil)
|
||||||
c.Params = gin.Params{{Key: "taiID", Value: "offline-node"}}
|
c.Params = gin.Params{{Key: "taiID", Value: "online-node"}}
|
||||||
|
|
||||||
h.HandleForward(c)
|
h.HandleForward(c)
|
||||||
|
|
||||||
if w.Code != http.StatusBadRequest {
|
if w.Code != http.StatusBadRequest {
|
||||||
t.Errorf("expected 400 for unresolvable port, got %d", w.Code)
|
t.Errorf("expected 400 for unresolvable route, got %d", w.Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,7 +245,6 @@ func TestHandleForward_ViaRealHTTP(t *testing.T) {
|
||||||
reg.Register(®istry.TaiNode{
|
reg.Register(®istry.TaiNode{
|
||||||
TaiID: "http-node",
|
TaiID: "http-node",
|
||||||
Mode: "tunnel",
|
Mode: "tunnel",
|
||||||
Ports: types.Ports{HTTP: 8099},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
|
|
@ -277,16 +253,12 @@ func TestHandleForward_ViaRealHTTP(t *testing.T) {
|
||||||
srv := httptest.NewServer(router)
|
srv := httptest.NewServer(router)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
resp, err := http.Get(srv.URL + "/tai/http-node/proxy/api")
|
resp, err := http.Get(srv.URL + "/tai/http-node/proxy/cid:8080/api")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// RequestForward will fail (no register stream) → hijacked conn gets "502"
|
|
||||||
// or the response will be a 502 written before hijack.
|
|
||||||
// Since hijack happens, the actual HTTP status may not be set normally.
|
|
||||||
// We just verify no panic and the request completes.
|
|
||||||
if resp.StatusCode == 200 {
|
if resp.StatusCode == 200 {
|
||||||
t.Error("expected non-200 response for failed forward")
|
t.Error("expected non-200 response for failed forward")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,9 @@ func (h *TunnelHandler) Forward(stream taipb.TaiTunnel_ForwardServer) error {
|
||||||
|
|
||||||
// RequestForward sends an "open" command to Tai via the Register stream and
|
// RequestForward sends an "open" command to Tai via the Register stream and
|
||||||
// waits for Tai to call back with a Forward stream. Returns the Forward stream.
|
// waits for Tai to call back with a Forward stream. Returns the Forward stream.
|
||||||
func (h *TunnelHandler) RequestForward(taiID string, targetPort int) (taipb.TaiTunnel_ForwardServer, error) {
|
//
|
||||||
|
// route may be nil for raw TCP tunnels (gRPC, Docker API, K8s API).
|
||||||
|
func (h *TunnelHandler) RequestForward(taiID string, route *forwardRoute) (taipb.TaiTunnel_ForwardServer, error) {
|
||||||
stream := h.reg.GetRegisterStream(taiID)
|
stream := h.reg.GetRegisterStream(taiID)
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
return nil, fmt.Errorf("tai %s: no active register stream", taiID)
|
return nil, fmt.Errorf("tai %s: no active register stream", taiID)
|
||||||
|
|
@ -217,23 +219,30 @@ func (h *TunnelHandler) RequestForward(taiID string, targetPort int) (taipb.TaiT
|
||||||
return nil, fmt.Errorf("tai %s: register stream type mismatch", taiID)
|
return nil, fmt.Errorf("tai %s: register stream type mismatch", taiID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctrl := &taipb.TunnelControl{
|
||||||
|
Type: "open",
|
||||||
|
ChannelId: channelID,
|
||||||
|
}
|
||||||
|
if route != nil {
|
||||||
|
ctrl.ChannelType = route.channelType
|
||||||
|
ctrl.ContainerId = route.containerID
|
||||||
|
ctrl.ContainerPort = int32(route.containerPort)
|
||||||
|
}
|
||||||
|
|
||||||
short := registry.ShortChannelID(channelID)
|
short := registry.ShortChannelID(channelID)
|
||||||
h.logger.Debug("[forward] sending open command",
|
h.logger.Debug("[forward] sending open command",
|
||||||
"tai_id", taiID, "port", targetPort, "channel_id", short)
|
"tai_id", taiID, "channel_type", ctrl.ChannelType,
|
||||||
|
"container", ctrl.ContainerId, "channel_id", short)
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
sendErr := regStream.Send(&taipb.TunnelControl{
|
sendErr := regStream.Send(ctrl)
|
||||||
Type: "open",
|
|
||||||
ChannelId: channelID,
|
|
||||||
TargetPort: int32(targetPort),
|
|
||||||
})
|
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
if sendErr != nil {
|
if sendErr != nil {
|
||||||
return nil, fmt.Errorf("send open: %w", sendErr)
|
return nil, fmt.Errorf("send open: %w", sendErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Debug("[forward] open sent, waiting for callback",
|
h.logger.Debug("[forward] open sent, waiting for callback",
|
||||||
"tai_id", taiID, "port", targetPort, "channel_id", short)
|
"tai_id", taiID, "channel_id", short)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case fwd := <-waitCh:
|
case fwd := <-waitCh:
|
||||||
|
|
@ -261,8 +270,9 @@ func (h *TunnelHandler) connectTunnelNode(taiID string) {
|
||||||
|
|
||||||
// bridgeConn bridges a local TCP connection to a Tai port via gRPC Forward stream.
|
// bridgeConn bridges a local TCP connection to a Tai port via gRPC Forward stream.
|
||||||
// Called by registry.OpenLocalListener for each accepted TCP connection.
|
// Called by registry.OpenLocalListener for each accepted TCP connection.
|
||||||
|
// Uses raw TCP forwarding (TargetPort only, no container routing).
|
||||||
func (h *TunnelHandler) bridgeConn(taiID string, targetPort int, localConn net.Conn) {
|
func (h *TunnelHandler) bridgeConn(taiID string, targetPort int, localConn net.Conn) {
|
||||||
fwd, err := h.RequestForward(taiID, targetPort)
|
fwd, err := h.requestForwardRaw(taiID, targetPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
localConn.Close()
|
localConn.Close()
|
||||||
h.logger.Error("request forward failed",
|
h.logger.Error("request forward failed",
|
||||||
|
|
@ -274,6 +284,59 @@ func (h *TunnelHandler) bridgeConn(taiID string, targetPort int, localConn net.C
|
||||||
bridgeTCP(localConn, streamConn)
|
bridgeTCP(localConn, streamConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requestForwardRaw sends an "open" command with only TargetPort (no container
|
||||||
|
// routing). Used by bridgeConn for raw TCP tunnels (gRPC, Docker API, K8s API).
|
||||||
|
func (h *TunnelHandler) requestForwardRaw(taiID string, targetPort int) (taipb.TaiTunnel_ForwardServer, error) {
|
||||||
|
stream := h.reg.GetRegisterStream(taiID)
|
||||||
|
if stream == nil {
|
||||||
|
return nil, fmt.Errorf("tai %s: no active register stream", taiID)
|
||||||
|
}
|
||||||
|
|
||||||
|
muVal, ok := h.sendMu.Load(taiID)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("tai %s: no send mutex (stream closing?)", taiID)
|
||||||
|
}
|
||||||
|
mu := muVal.(*sync.Mutex)
|
||||||
|
|
||||||
|
channelID, err := registry.GenerateChannelID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("generate channel_id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
waitCh := make(chan taipb.TaiTunnel_ForwardServer, 1)
|
||||||
|
h.pending.Store(channelID, waitCh)
|
||||||
|
defer h.pending.Delete(channelID)
|
||||||
|
|
||||||
|
regStream, ok := stream.(taipb.TaiTunnel_RegisterServer)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("tai %s: register stream type mismatch", taiID)
|
||||||
|
}
|
||||||
|
|
||||||
|
short := registry.ShortChannelID(channelID)
|
||||||
|
h.logger.Debug("[forward] sending open command (raw)",
|
||||||
|
"tai_id", taiID, "port", targetPort, "channel_id", short)
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
sendErr := regStream.Send(&taipb.TunnelControl{
|
||||||
|
Type: "open",
|
||||||
|
ChannelId: channelID,
|
||||||
|
TargetPort: int32(targetPort),
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
|
if sendErr != nil {
|
||||||
|
return nil, fmt.Errorf("send open: %w", sendErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case fwd := <-waitCh:
|
||||||
|
return fwd, nil
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
return nil, fmt.Errorf("tai %s: forward timeout (10s) channel=%s", taiID, short)
|
||||||
|
case <-regStream.Context().Done():
|
||||||
|
return nil, fmt.Errorf("tai %s: register stream closed while waiting for forward", taiID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// forwardConn wraps a Forward stream as a net.Conn-like reader/writer.
|
// forwardConn wraps a Forward stream as a net.Conn-like reader/writer.
|
||||||
type forwardConn struct {
|
type forwardConn struct {
|
||||||
stream taipb.TaiTunnel_ForwardServer
|
stream taipb.TaiTunnel_ForwardServer
|
||||||
|
|
@ -364,6 +427,7 @@ func capsFromProto(c *taipb.Capabilities) types.Capabilities {
|
||||||
Docker: c.Docker,
|
Docker: c.Docker,
|
||||||
K8s: c.K8S,
|
K8s: c.K8S,
|
||||||
HostExec: c.HostExec,
|
HostExec: c.HostExec,
|
||||||
|
VNC: c.Vnc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ func TestRequestForward_NoRegisterStream(t *testing.T) {
|
||||||
|
|
||||||
reg.Register(®istry.TaiNode{TaiID: "no-stream", Mode: "tunnel"})
|
reg.Register(®istry.TaiNode{TaiID: "no-stream", Mode: "tunnel"})
|
||||||
|
|
||||||
_, err := h.RequestForward("no-stream", 8099)
|
_, err := h.requestForwardRaw("no-stream", 8099)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error when no register stream")
|
t.Fatal("expected error when no register stream")
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +258,7 @@ func TestRequestForward_TypeMismatch(t *testing.T) {
|
||||||
reg.Register(®istry.TaiNode{TaiID: "bad-type", Mode: "tunnel"})
|
reg.Register(®istry.TaiNode{TaiID: "bad-type", Mode: "tunnel"})
|
||||||
reg.SetRegisterStream("bad-type", "not-a-stream")
|
reg.SetRegisterStream("bad-type", "not-a-stream")
|
||||||
|
|
||||||
_, err := h.RequestForward("bad-type", 8099)
|
_, err := h.requestForwardRaw("bad-type", 8099)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for type mismatch")
|
t.Fatal("expected error for type mismatch")
|
||||||
}
|
}
|
||||||
|
|
@ -348,7 +348,7 @@ drainLoop:
|
||||||
requestDone.Add(1)
|
requestDone.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer requestDone.Done()
|
defer requestDone.Done()
|
||||||
requestResult, requestErr = h.RequestForward(taiID, 8099)
|
requestResult, requestErr = h.requestForwardRaw(taiID, 8099)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Receive the "open" command
|
// Receive the "open" command
|
||||||
|
|
@ -748,7 +748,7 @@ func TestRequestForward_Timeout(t *testing.T) {
|
||||||
// by never sending Forward). We'll use a short context cancel to avoid waiting.
|
// by never sending Forward). We'll use a short context cancel to avoid waiting.
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := h.RequestForward(taiID, 8099)
|
_, err := h.requestForwardRaw(taiID, 8099)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -828,7 +828,7 @@ drained:
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
port := 8099 + i
|
port := 8099 + i
|
||||||
go func(port int) {
|
go func(port int) {
|
||||||
_, err := h.RequestForward(taiID, port)
|
_, err := h.requestForwardRaw(taiID, port)
|
||||||
results <- err
|
results <- err
|
||||||
}(port)
|
}(port)
|
||||||
}
|
}
|
||||||
|
|
@ -926,7 +926,7 @@ drained2:
|
||||||
// Start RequestForward
|
// Start RequestForward
|
||||||
fwdResult := make(chan error, 1)
|
fwdResult := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := h.RequestForward(taiID, 8099)
|
_, err := h.requestForwardRaw(taiID, 8099)
|
||||||
fwdResult <- err
|
fwdResult <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -1101,7 +1101,7 @@ proxyDrained:
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Now do an actual RequestForward + simulate browser side
|
// Now do an actual RequestForward + simulate browser side
|
||||||
fwd, err := h.RequestForward(taiID, 8099)
|
fwd, err := h.requestForwardRaw(taiID, 8099)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("RequestForward:", err)
|
t.Fatal("RequestForward:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1267,7 +1267,7 @@ vncDrained:
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Send WS upgrade request through tunnel
|
// Send WS upgrade request through tunnel
|
||||||
fwd, err := h.RequestForward(taiID, 16080)
|
fwd, err := h.requestForwardRaw(taiID, 16080)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("RequestForward:", err)
|
t.Fatal("RequestForward:", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v4.25.0
|
// protoc v4.25.0
|
||||||
// source: tunnel.proto
|
// source: tunnel/proto/tunnel.proto
|
||||||
|
|
||||||
package taipb
|
package taipb
|
||||||
|
|
||||||
|
|
@ -33,8 +33,11 @@ type TunnelControl struct {
|
||||||
Caps *Capabilities `protobuf:"bytes,7,opt,name=caps,proto3" json:"caps,omitempty"`
|
Caps *Capabilities `protobuf:"bytes,7,opt,name=caps,proto3" json:"caps,omitempty"`
|
||||||
System *SystemInfo `protobuf:"bytes,8,opt,name=system,proto3" json:"system,omitempty"`
|
System *SystemInfo `protobuf:"bytes,8,opt,name=system,proto3" json:"system,omitempty"`
|
||||||
// Carried on "open" (Yao → Tai)
|
// Carried on "open" (Yao → Tai)
|
||||||
ChannelId string `protobuf:"bytes,10,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
|
ChannelId string `protobuf:"bytes,10,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
|
||||||
TargetPort int32 `protobuf:"varint,11,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"`
|
TargetPort int32 `protobuf:"varint,11,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"`
|
||||||
|
ChannelType string `protobuf:"bytes,12,opt,name=channel_type,json=channelType,proto3" json:"channel_type,omitempty"` // "proxy" | "vnc" | "" (legacy/raw TCP)
|
||||||
|
ContainerId string `protobuf:"bytes,13,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` // target container or "__host__"
|
||||||
|
ContainerPort int32 `protobuf:"varint,14,opt,name=container_port,json=containerPort,proto3" json:"container_port,omitempty"` // container-internal port (vnc default 5900)
|
||||||
// Carried on "registered" (Yao → Tai)
|
// Carried on "registered" (Yao → Tai)
|
||||||
TaiId string `protobuf:"bytes,20,opt,name=tai_id,json=taiId,proto3" json:"tai_id,omitempty"`
|
TaiId string `protobuf:"bytes,20,opt,name=tai_id,json=taiId,proto3" json:"tai_id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
@ -43,7 +46,7 @@ type TunnelControl struct {
|
||||||
|
|
||||||
func (x *TunnelControl) Reset() {
|
func (x *TunnelControl) Reset() {
|
||||||
*x = TunnelControl{}
|
*x = TunnelControl{}
|
||||||
mi := &file_tunnel_proto_msgTypes[0]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +58,7 @@ func (x *TunnelControl) String() string {
|
||||||
func (*TunnelControl) ProtoMessage() {}
|
func (*TunnelControl) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *TunnelControl) ProtoReflect() protoreflect.Message {
|
func (x *TunnelControl) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_tunnel_proto_msgTypes[0]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[0]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -68,7 +71,7 @@ func (x *TunnelControl) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use TunnelControl.ProtoReflect.Descriptor instead.
|
// Deprecated: Use TunnelControl.ProtoReflect.Descriptor instead.
|
||||||
func (*TunnelControl) Descriptor() ([]byte, []int) {
|
func (*TunnelControl) Descriptor() ([]byte, []int) {
|
||||||
return file_tunnel_proto_rawDescGZIP(), []int{0}
|
return file_tunnel_proto_tunnel_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TunnelControl) GetType() string {
|
func (x *TunnelControl) GetType() string {
|
||||||
|
|
@ -141,6 +144,27 @@ func (x *TunnelControl) GetTargetPort() int32 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *TunnelControl) GetChannelType() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ChannelType
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TunnelControl) GetContainerId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ContainerId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TunnelControl) GetContainerPort() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ContainerPort
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (x *TunnelControl) GetTaiId() string {
|
func (x *TunnelControl) GetTaiId() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.TaiId
|
return x.TaiId
|
||||||
|
|
@ -157,7 +181,7 @@ type ForwardData struct {
|
||||||
|
|
||||||
func (x *ForwardData) Reset() {
|
func (x *ForwardData) Reset() {
|
||||||
*x = ForwardData{}
|
*x = ForwardData{}
|
||||||
mi := &file_tunnel_proto_msgTypes[1]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +193,7 @@ func (x *ForwardData) String() string {
|
||||||
func (*ForwardData) ProtoMessage() {}
|
func (*ForwardData) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ForwardData) ProtoReflect() protoreflect.Message {
|
func (x *ForwardData) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_tunnel_proto_msgTypes[1]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -182,7 +206,7 @@ func (x *ForwardData) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use ForwardData.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ForwardData.ProtoReflect.Descriptor instead.
|
||||||
func (*ForwardData) Descriptor() ([]byte, []int) {
|
func (*ForwardData) Descriptor() ([]byte, []int) {
|
||||||
return file_tunnel_proto_rawDescGZIP(), []int{1}
|
return file_tunnel_proto_tunnel_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ForwardData) GetData() []byte {
|
func (x *ForwardData) GetData() []byte {
|
||||||
|
|
@ -205,7 +229,7 @@ type Ports struct {
|
||||||
|
|
||||||
func (x *Ports) Reset() {
|
func (x *Ports) Reset() {
|
||||||
*x = Ports{}
|
*x = Ports{}
|
||||||
mi := &file_tunnel_proto_msgTypes[2]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +241,7 @@ func (x *Ports) String() string {
|
||||||
func (*Ports) ProtoMessage() {}
|
func (*Ports) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Ports) ProtoReflect() protoreflect.Message {
|
func (x *Ports) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_tunnel_proto_msgTypes[2]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -230,7 +254,7 @@ func (x *Ports) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use Ports.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Ports.ProtoReflect.Descriptor instead.
|
||||||
func (*Ports) Descriptor() ([]byte, []int) {
|
func (*Ports) Descriptor() ([]byte, []int) {
|
||||||
return file_tunnel_proto_rawDescGZIP(), []int{2}
|
return file_tunnel_proto_tunnel_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Ports) GetGrpc() int32 {
|
func (x *Ports) GetGrpc() int32 {
|
||||||
|
|
@ -273,13 +297,14 @@ type Capabilities struct {
|
||||||
Docker bool `protobuf:"varint,1,opt,name=docker,proto3" json:"docker,omitempty"`
|
Docker bool `protobuf:"varint,1,opt,name=docker,proto3" json:"docker,omitempty"`
|
||||||
K8S bool `protobuf:"varint,2,opt,name=k8s,proto3" json:"k8s,omitempty"`
|
K8S bool `protobuf:"varint,2,opt,name=k8s,proto3" json:"k8s,omitempty"`
|
||||||
HostExec bool `protobuf:"varint,3,opt,name=host_exec,json=hostExec,proto3" json:"host_exec,omitempty"`
|
HostExec bool `protobuf:"varint,3,opt,name=host_exec,json=hostExec,proto3" json:"host_exec,omitempty"`
|
||||||
|
Vnc bool `protobuf:"varint,4,opt,name=vnc,proto3" json:"vnc,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Capabilities) Reset() {
|
func (x *Capabilities) Reset() {
|
||||||
*x = Capabilities{}
|
*x = Capabilities{}
|
||||||
mi := &file_tunnel_proto_msgTypes[3]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +316,7 @@ func (x *Capabilities) String() string {
|
||||||
func (*Capabilities) ProtoMessage() {}
|
func (*Capabilities) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Capabilities) ProtoReflect() protoreflect.Message {
|
func (x *Capabilities) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_tunnel_proto_msgTypes[3]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -304,7 +329,7 @@ func (x *Capabilities) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use Capabilities.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Capabilities.ProtoReflect.Descriptor instead.
|
||||||
func (*Capabilities) Descriptor() ([]byte, []int) {
|
func (*Capabilities) Descriptor() ([]byte, []int) {
|
||||||
return file_tunnel_proto_rawDescGZIP(), []int{3}
|
return file_tunnel_proto_tunnel_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Capabilities) GetDocker() bool {
|
func (x *Capabilities) GetDocker() bool {
|
||||||
|
|
@ -328,6 +353,13 @@ func (x *Capabilities) GetHostExec() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Capabilities) GetVnc() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Vnc
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type SystemInfo struct {
|
type SystemInfo struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Os string `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"`
|
Os string `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"`
|
||||||
|
|
@ -340,7 +372,7 @@ type SystemInfo struct {
|
||||||
|
|
||||||
func (x *SystemInfo) Reset() {
|
func (x *SystemInfo) Reset() {
|
||||||
*x = SystemInfo{}
|
*x = SystemInfo{}
|
||||||
mi := &file_tunnel_proto_msgTypes[4]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +384,7 @@ func (x *SystemInfo) String() string {
|
||||||
func (*SystemInfo) ProtoMessage() {}
|
func (*SystemInfo) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SystemInfo) ProtoReflect() protoreflect.Message {
|
func (x *SystemInfo) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_tunnel_proto_msgTypes[4]
|
mi := &file_tunnel_proto_tunnel_proto_msgTypes[4]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -365,7 +397,7 @@ func (x *SystemInfo) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use SystemInfo.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SystemInfo.ProtoReflect.Descriptor instead.
|
||||||
func (*SystemInfo) Descriptor() ([]byte, []int) {
|
func (*SystemInfo) Descriptor() ([]byte, []int) {
|
||||||
return file_tunnel_proto_rawDescGZIP(), []int{4}
|
return file_tunnel_proto_tunnel_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SystemInfo) GetOs() string {
|
func (x *SystemInfo) GetOs() string {
|
||||||
|
|
@ -396,12 +428,12 @@ func (x *SystemInfo) GetShell() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_tunnel_proto protoreflect.FileDescriptor
|
var File_tunnel_proto_tunnel_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_tunnel_proto_rawDesc = "" +
|
const file_tunnel_proto_tunnel_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\ftunnel.proto\x12\n" +
|
"\x19tunnel/proto/tunnel.proto\x12\n" +
|
||||||
"tai.tunnel\"\xf6\x02\n" +
|
"tai.tunnel\"\xe3\x03\n" +
|
||||||
"\rTunnelControl\x12\x12\n" +
|
"\rTunnelControl\x12\x12\n" +
|
||||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x17\n" +
|
"\x04type\x18\x01 \x01(\tR\x04type\x12\x17\n" +
|
||||||
"\anode_id\x18\x02 \x01(\tR\x06nodeId\x12\x1d\n" +
|
"\anode_id\x18\x02 \x01(\tR\x06nodeId\x12\x1d\n" +
|
||||||
|
|
@ -416,7 +448,10 @@ const file_tunnel_proto_rawDesc = "" +
|
||||||
"channel_id\x18\n" +
|
"channel_id\x18\n" +
|
||||||
" \x01(\tR\tchannelId\x12\x1f\n" +
|
" \x01(\tR\tchannelId\x12\x1f\n" +
|
||||||
"\vtarget_port\x18\v \x01(\x05R\n" +
|
"\vtarget_port\x18\v \x01(\x05R\n" +
|
||||||
"targetPort\x12\x15\n" +
|
"targetPort\x12!\n" +
|
||||||
|
"\fchannel_type\x18\f \x01(\tR\vchannelType\x12!\n" +
|
||||||
|
"\fcontainer_id\x18\r \x01(\tR\vcontainerId\x12%\n" +
|
||||||
|
"\x0econtainer_port\x18\x0e \x01(\x05R\rcontainerPort\x12\x15\n" +
|
||||||
"\x06tai_id\x18\x14 \x01(\tR\x05taiId\"!\n" +
|
"\x06tai_id\x18\x14 \x01(\tR\x05taiId\"!\n" +
|
||||||
"\vForwardData\x12\x12\n" +
|
"\vForwardData\x12\x12\n" +
|
||||||
"\x04data\x18\x01 \x01(\fR\x04data\"k\n" +
|
"\x04data\x18\x01 \x01(\fR\x04data\"k\n" +
|
||||||
|
|
@ -425,11 +460,12 @@ const file_tunnel_proto_rawDesc = "" +
|
||||||
"\x04http\x18\x02 \x01(\x05R\x04http\x12\x10\n" +
|
"\x04http\x18\x02 \x01(\x05R\x04http\x12\x10\n" +
|
||||||
"\x03vnc\x18\x03 \x01(\x05R\x03vnc\x12\x16\n" +
|
"\x03vnc\x18\x03 \x01(\x05R\x03vnc\x12\x16\n" +
|
||||||
"\x06docker\x18\x04 \x01(\x05R\x06docker\x12\x10\n" +
|
"\x06docker\x18\x04 \x01(\x05R\x06docker\x12\x10\n" +
|
||||||
"\x03k8s\x18\x05 \x01(\x05R\x03k8s\"U\n" +
|
"\x03k8s\x18\x05 \x01(\x05R\x03k8s\"g\n" +
|
||||||
"\fCapabilities\x12\x16\n" +
|
"\fCapabilities\x12\x16\n" +
|
||||||
"\x06docker\x18\x01 \x01(\bR\x06docker\x12\x10\n" +
|
"\x06docker\x18\x01 \x01(\bR\x06docker\x12\x10\n" +
|
||||||
"\x03k8s\x18\x02 \x01(\bR\x03k8s\x12\x1b\n" +
|
"\x03k8s\x18\x02 \x01(\bR\x03k8s\x12\x1b\n" +
|
||||||
"\thost_exec\x18\x03 \x01(\bR\bhostExec\"b\n" +
|
"\thost_exec\x18\x03 \x01(\bR\bhostExec\x12\x10\n" +
|
||||||
|
"\x03vnc\x18\x04 \x01(\bR\x03vnc\"b\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"SystemInfo\x12\x0e\n" +
|
"SystemInfo\x12\x0e\n" +
|
||||||
"\x02os\x18\x01 \x01(\tR\x02os\x12\x12\n" +
|
"\x02os\x18\x01 \x01(\tR\x02os\x12\x12\n" +
|
||||||
|
|
@ -438,29 +474,29 @@ const file_tunnel_proto_rawDesc = "" +
|
||||||
"\x05shell\x18\x04 \x01(\tR\x05shell2\x92\x01\n" +
|
"\x05shell\x18\x04 \x01(\tR\x05shell2\x92\x01\n" +
|
||||||
"\tTaiTunnel\x12D\n" +
|
"\tTaiTunnel\x12D\n" +
|
||||||
"\bRegister\x12\x19.tai.tunnel.TunnelControl\x1a\x19.tai.tunnel.TunnelControl(\x010\x01\x12?\n" +
|
"\bRegister\x12\x19.tai.tunnel.TunnelControl\x1a\x19.tai.tunnel.TunnelControl(\x010\x01\x12?\n" +
|
||||||
"\aForward\x12\x17.tai.tunnel.ForwardData\x1a\x17.tai.tunnel.ForwardData(\x010\x01B(Z&github.com/yaoapp/yao/tai/tunnel/taipbb\x06proto3"
|
"\aForward\x12\x17.tai.tunnel.ForwardData\x1a\x17.tai.tunnel.ForwardData(\x010\x01B$Z\"github.com/yaoapp/tai/tunnel/taipbb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_tunnel_proto_rawDescOnce sync.Once
|
file_tunnel_proto_tunnel_proto_rawDescOnce sync.Once
|
||||||
file_tunnel_proto_rawDescData []byte
|
file_tunnel_proto_tunnel_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_tunnel_proto_rawDescGZIP() []byte {
|
func file_tunnel_proto_tunnel_proto_rawDescGZIP() []byte {
|
||||||
file_tunnel_proto_rawDescOnce.Do(func() {
|
file_tunnel_proto_tunnel_proto_rawDescOnce.Do(func() {
|
||||||
file_tunnel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_tunnel_proto_rawDesc), len(file_tunnel_proto_rawDesc)))
|
file_tunnel_proto_tunnel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_tunnel_proto_tunnel_proto_rawDesc), len(file_tunnel_proto_tunnel_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_tunnel_proto_rawDescData
|
return file_tunnel_proto_tunnel_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_tunnel_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_tunnel_proto_tunnel_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_tunnel_proto_goTypes = []any{
|
var file_tunnel_proto_tunnel_proto_goTypes = []any{
|
||||||
(*TunnelControl)(nil), // 0: tai.tunnel.TunnelControl
|
(*TunnelControl)(nil), // 0: tai.tunnel.TunnelControl
|
||||||
(*ForwardData)(nil), // 1: tai.tunnel.ForwardData
|
(*ForwardData)(nil), // 1: tai.tunnel.ForwardData
|
||||||
(*Ports)(nil), // 2: tai.tunnel.Ports
|
(*Ports)(nil), // 2: tai.tunnel.Ports
|
||||||
(*Capabilities)(nil), // 3: tai.tunnel.Capabilities
|
(*Capabilities)(nil), // 3: tai.tunnel.Capabilities
|
||||||
(*SystemInfo)(nil), // 4: tai.tunnel.SystemInfo
|
(*SystemInfo)(nil), // 4: tai.tunnel.SystemInfo
|
||||||
}
|
}
|
||||||
var file_tunnel_proto_depIdxs = []int32{
|
var file_tunnel_proto_tunnel_proto_depIdxs = []int32{
|
||||||
2, // 0: tai.tunnel.TunnelControl.ports:type_name -> tai.tunnel.Ports
|
2, // 0: tai.tunnel.TunnelControl.ports:type_name -> tai.tunnel.Ports
|
||||||
3, // 1: tai.tunnel.TunnelControl.caps:type_name -> tai.tunnel.Capabilities
|
3, // 1: tai.tunnel.TunnelControl.caps:type_name -> tai.tunnel.Capabilities
|
||||||
4, // 2: tai.tunnel.TunnelControl.system:type_name -> tai.tunnel.SystemInfo
|
4, // 2: tai.tunnel.TunnelControl.system:type_name -> tai.tunnel.SystemInfo
|
||||||
|
|
@ -475,26 +511,26 @@ var file_tunnel_proto_depIdxs = []int32{
|
||||||
0, // [0:3] is the sub-list for field type_name
|
0, // [0:3] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_tunnel_proto_init() }
|
func init() { file_tunnel_proto_tunnel_proto_init() }
|
||||||
func file_tunnel_proto_init() {
|
func file_tunnel_proto_tunnel_proto_init() {
|
||||||
if File_tunnel_proto != nil {
|
if File_tunnel_proto_tunnel_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_tunnel_proto_rawDesc), len(file_tunnel_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_tunnel_proto_tunnel_proto_rawDesc), len(file_tunnel_proto_tunnel_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 5,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
GoTypes: file_tunnel_proto_goTypes,
|
GoTypes: file_tunnel_proto_tunnel_proto_goTypes,
|
||||||
DependencyIndexes: file_tunnel_proto_depIdxs,
|
DependencyIndexes: file_tunnel_proto_tunnel_proto_depIdxs,
|
||||||
MessageInfos: file_tunnel_proto_msgTypes,
|
MessageInfos: file_tunnel_proto_tunnel_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_tunnel_proto = out.File
|
File_tunnel_proto_tunnel_proto = out.File
|
||||||
file_tunnel_proto_goTypes = nil
|
file_tunnel_proto_tunnel_proto_goTypes = nil
|
||||||
file_tunnel_proto_depIdxs = nil
|
file_tunnel_proto_tunnel_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v4.25.0
|
// - protoc v4.25.0
|
||||||
// source: tunnel.proto
|
// source: tunnel/proto/tunnel.proto
|
||||||
|
|
||||||
package taipb
|
package taipb
|
||||||
|
|
||||||
|
|
@ -147,5 +147,5 @@ var TaiTunnel_ServiceDesc = grpc.ServiceDesc{
|
||||||
ClientStreams: true,
|
ClientStreams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Metadata: "tunnel.proto",
|
Metadata: "tunnel/proto/tunnel.proto",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type Capabilities struct {
|
||||||
Docker bool `json:"docker"`
|
Docker bool `json:"docker"`
|
||||||
K8s bool `json:"k8s"`
|
K8s bool `json:"k8s"`
|
||||||
HostExec bool `json:"host_exec"`
|
HostExec bool `json:"host_exec"`
|
||||||
|
VNC bool `json:"vnc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemInfo describes the host machine running Tai.
|
// SystemInfo describes the host machine running Tai.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue