diff --git a/.github/goreleaser.yml b/.github/goreleaser.yml index 77ca1d66cd8a8fb73a3fc87f668651e429fc33d2..dcc3333b893758ff4412978fe8c4f53303bf1e64 100644 --- a/.github/goreleaser.yml +++ b/.github/goreleaser.yml @@ -2,7 +2,7 @@ builds: - env: - CGO_ENABLED=0 ldflags: - - -s -w -X github.com/jpillora/chisel/share.BuildVersion={{.Version}} + - -s -w -X gitee.com/g-devops/chisel/share.BuildVersion={{.Version}} goos: - linux - darwin diff --git a/Dockerfile b/Dockerfile index e3e2073e47bcf65fae0ec9496ac74f227b702ac9..4379f6f9839e5702a734d553d3622fb2987e22ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ENV CGO_ENABLED 0 ADD . /src WORKDIR /src RUN go build \ - -ldflags "-X github.com/jpillora/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ + -ldflags "-X gitee.com/g-devops/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ -o chisel # container stage FROM alpine diff --git a/README.md b/README.md index f71f9c516f5385e07c82982fff0558ec9d80bbf4..5772fef5a248976af1e5a01b1393dd1cc0587fcb 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,21 @@ $ chisel client --help R:::: which does reverse port forwarding, sharing : - from the client to the server's :. + from the client to the server's :, or: + + ::unix:// + + ■ local-host defaults to 0.0.0.0 (all interfaces). + ■ local-port is required*. + ■ `path/to/unix/domain/socket` is the path to the domain socket to be connected. + + which shares the unix domain socket from the server to the client as + :, or + + R:::unix:// + + which does reverse forwarding, sharing the unix domain socket + `path/to/unix/domain/socket` from the client to the server's : example remotes @@ -175,6 +189,7 @@ $ chisel client --help 192.168.0.5:3000:google.com:80 socks 5000:socks + 5000:unix:///tmp/mysql.sock R:2222:localhost:22 When the chisel server has --socks5 enabled, remotes can diff --git a/bench/main.go b/bench/main.go index 30a867a969438cf399db09ff84b9fdefa6fc7efc..84ebe51373891ed2193ebe6fc93c3443daa9f239 100644 --- a/bench/main.go +++ b/bench/main.go @@ -25,7 +25,7 @@ import ( "path" "strconv" - "github.com/jpillora/chisel/share" + chshare "gitee.com/g-devops/chisel/share" "time" ) diff --git a/client/client.go b/client/client.go index 3d770136f758f7cf7cbf47de8f9059aa32c63dd2..b324a7066c516c5b9518c55bb388937ffbf51a36 100644 --- a/client/client.go +++ b/client/client.go @@ -8,12 +8,14 @@ import ( "net/http" "net/url" "regexp" + "runtime" "strings" "time" + "encoding/json" + chshare "gitee.com/g-devops/chisel/share" "github.com/gorilla/websocket" "github.com/jpillora/backoff" - chshare "github.com/jpillora/chisel/share" "golang.org/x/crypto/ssh" ) @@ -74,6 +76,10 @@ func NewClient(config *Config) (*Client, error) { return nil, fmt.Errorf("Failed to decode remote '%s': %s", s, err) } shared.Remotes = append(shared.Remotes, r) + + //jsonView + data, _:= json.Marshal(r) + fmt.Println(string(data)) } config.shared = shared client := &Client{ @@ -270,6 +276,7 @@ func (c *Client) Close() error { } func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { + socketPrefix := "unix://" for ch := range chans { remote := string(ch.ExtraData()) stream, reqs, err := ch.Accept() @@ -279,6 +286,15 @@ func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { } go ssh.DiscardRequests(reqs) l := c.Logger.Fork("conn#%d", c.connStats.New()) - go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + if strings.HasPrefix(remote, socketPrefix) { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + c.Debugf("Unix domain socket is only supported on *nix system") + continue + } + remote := strings.TrimPrefix(remote, socketPrefix) + go chshare.HandleUnixDomainSocketStream(l, &c.connStats, stream, remote) + } else { + go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + } } } diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6f69394f3a15b05333a058f089497631a9dcc04b --- /dev/null +++ b/example/.gitignore @@ -0,0 +1 @@ +__*_bin diff --git a/example/clientRun.go b/example/clientRun.go new file mode 100644 index 0000000000000000000000000000000000000000..9581ec6dd9aa120c519a6978c3759019c71b0235 --- /dev/null +++ b/example/clientRun.go @@ -0,0 +1,73 @@ +package example + +import ( + "flag" + // "strings" + // "fmt" + // "io/ioutil" + "log" + "os" + + // "strconv" + + chclient "gitee.com/g-devops/chisel/client" + // chserver "gitee.com/g-devops/chisel/server" + chshare "gitee.com/g-devops/chisel/share" +) + +/* func main2() { + args := flag.Args() + // server(args) + client(args) +} */ + +func client(args []string, ser, auth0 string, remotes []string) { + flags := flag.NewFlagSet("client", flag.ContinueOnError) + + fingerprint := flags.String("fingerprint", "", "") + auth := flags.String("auth", "", "") + keepalive := flags.Duration("keepalive", 0, "") + maxRetryCount := flags.Int("max-retry-count", -1, "") + maxRetryInterval := flags.Duration("max-retry-interval", 0, "") + proxy := flags.String("proxy", "", "") + // pid := flags.Bool("pid", false, "") + hostname := flags.String("hostname", "", "") + verbose := flags.Bool("v", false, "") + /* flags.Usage = func() { + fmt.Print(clientHelp) + os.Exit(1) + } */ + flags.Parse(args) + //pull out options, put back remaining args + args = flags.Args() + if *auth == "" { + *auth = os.Getenv("AUTH") + } + + // ser:= args[0] + // remotes:= args[1:] + *auth= auth0 //"user:pass" + + c, err := chclient.NewClient(&chclient.Config{ + Fingerprint: *fingerprint, + Auth: *auth, + KeepAlive: *keepalive, + MaxRetryCount: *maxRetryCount, + MaxRetryInterval: *maxRetryInterval, + HTTPProxy: *proxy, + Server: ser, + Remotes: remotes, + HostHeader: *hostname, + }) + if err != nil { + log.Fatal(err) + } + c.Debug = *verbose + /* if *pid { + generatePidFile() + } */ + go chshare.GoStats() + if err = c.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/example/serverRun.go b/example/serverRun.go new file mode 100644 index 0000000000000000000000000000000000000000..792ca5fcdbd0a7d470e8303e90cd945d49fa3650 --- /dev/null +++ b/example/serverRun.go @@ -0,0 +1,168 @@ +package example + +import ( + "flag" + // "fmt" + // "io/ioutil" + "log" + "os" + // "strconv" + + // chclient "gitee.com/g-devops/chisel/client" + chserver "gitee.com/g-devops/chisel/server" + chshare "gitee.com/g-devops/chisel/share" +) + +func main1() { + + /* version := flag.Bool("version", false, "") + v := flag.Bool("v", false, "") + flag.Bool("help", false, "") + flag.Bool("h", false, "") + flag.Usage = func() {} + flag.Parse() + + if *version || *v { + fmt.Println(chshare.BuildVersion) + os.Exit(1) + } */ + + /* args := flag.Args() + + subcmd := "" + if len(args) > 0 { + subcmd = args[0] + args = args[1:] + } + + switch subcmd { + case "server": + server(args) + case "client": + client(args) + default: + fmt.Fprintf(os.Stderr, help) + os.Exit(1) + } */ + + args := flag.Args() + server(args) + // client(args) +} + +func server(args []string) (*chserver.Server, error) { + + flags := flag.NewFlagSet("server", flag.ContinueOnError) + + // host := flags.String("host", "", "") + // // p := flags.String("p", "", "") + // port := flags.String("port", "", "") + key := flags.String("key", "", "") + authfile := flags.String("authfile", "", "") + auth := flags.String("auth", "", "") + proxy := flags.String("proxy", "", "") + socks5 := flags.Bool("socks5", false, "") + reverse := flags.Bool("reverse", false, "") + // pid := flags.Bool("pid", false, "") + verbose := flags.Bool("v", false, "") + uds := flags.Bool("uds", false, "") + + /* flags.Usage = func() { + fmt.Print(serverHelp) + os.Exit(1) + } */ + flags.Parse(args) + + /* if *host == "" { + *host = os.Getenv("HOST") + } + if *host == "" { + *host = "0.0.0.0" + } + if *port == "" { + *port = *p + } + if *port == "" { + *port = os.Getenv("PORT") + } + if *port == "" { + *port = "8080" + } + if *key == "" { + *key = os.Getenv("CHISEL_KEY") + } */ + + // *port = "8080" + *key = os.Getenv("CHISEL_KEY") + *reverse = true // + *uds = true + s, err := chserver.NewServer(&chserver.Config{ + KeySeed: *key, + AuthFile: *authfile, + Auth: *auth, + Proxy: *proxy, + Socks5: *socks5, + Reverse: *reverse, + UdsMode: *uds, + }) + if err != nil { + log.Fatal(err) + } + s.Debug = *verbose + /* if *pid { + generatePidFile() + } */ + + go chshare.GoStats() + return s, nil + +} + +// func client(args []string) { + +// flags := flag.NewFlagSet("client", flag.ContinueOnError) + +// fingerprint := flags.String("fingerprint", "", "") +// auth := flags.String("auth", "", "") +// keepalive := flags.Duration("keepalive", 0, "") +// maxRetryCount := flags.Int("max-retry-count", -1, "") +// maxRetryInterval := flags.Duration("max-retry-interval", 0, "") +// proxy := flags.String("proxy", "", "") +// // pid := flags.Bool("pid", false, "") +// hostname := flags.String("hostname", "", "") +// verbose := flags.Bool("v", false, "") +// /* flags.Usage = func() { +// fmt.Print(clientHelp) +// os.Exit(1) +// } */ +// flags.Parse(args) +// //pull out options, put back remaining args +// args = flags.Args() +// if *auth == "" { +// *auth = os.Getenv("AUTH") +// } + + +// c, err := chclient.NewClient(&chclient.Config{ +// Fingerprint: *fingerprint, +// Auth: *auth, +// KeepAlive: *keepalive, +// MaxRetryCount: *maxRetryCount, +// MaxRetryInterval: *maxRetryInterval, +// HTTPProxy: *proxy, +// Server: args[0], +// Remotes: args[1:], +// HostHeader: *hostname, +// }) +// if err != nil { +// log.Fatal(err) +// } +// c.Debug = *verbose +// /* if *pid { +// generatePidFile() +// } */ +// go chshare.GoStats() +// if err = c.Run(); err != nil { +// log.Fatal(err) +// } +// } diff --git a/example/server_client_test.go b/example/server_client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cdad2586ed25b62d8f1e2a9e196ad64bd3b37185 --- /dev/null +++ b/example/server_client_test.go @@ -0,0 +1,56 @@ +package example + +import ( + "fmt" + "testing" + "flag" + "strings" + + "log" + // chserver "gitee.com/g-devops/chisel/server" + // chshare "gitee.com/g-devops/chisel/share" +) + +// .config/Code/User/settings.json << "go.testFlags": ["-v"], "go.testTimeout": "1h", +func Test1(t *testing.T){ + fmt.Println("hello sam") +} + +// debugMode: 可停止退出进程 +func Test_server(t *testing.T){ + args := flag.Args() + s, err:= server(args) + host := "" + port := "8080" + + // s.AddUser("user", "pass", "aa.*") + // R:unix:///tmp/chisel-18998.socket:localhost:8998|127.0.0.1:28998:unix:///tmp/chisel-18998.socket + // server: access to 'R:/tmp/chisel-18998.socket' denied + // server: access to '/tmp/chisel-18998.socket' denied + // "R:bindURI|remoteURI" + s.AddUser("user", "pass", "R:/tmp/chisel-18998.socket|/tmp/chisel-18998.socket") + + if err = s.Run(host, port); err != nil { + log.Fatal(err) + } +} +func Test_client(t *testing.T){ + args := flag.Args() + // server(args) + + // tcpMode + // connstr:= "R:127.0.0.1:18998:localhost:8998" //"aa|bb" + // connstr:= "R:127.0.0.1:18998:localhost:8998|127.0.0.1:28998:localhost:18998" //ReverseBindServer> bindClient + + // unixMode: curl --unix-socket /tmp/chisel-18998.socket localhost/ + // connstr:= "unix:///tmp/chisel-18998.socket:localhost:8998" + // connstr:= "R:unix:///tmp/chisel-18998.socket:localhost:8998" + + // proxy#1:R:/tmp/chisel-18998.socket=>localhost:8998: Listening + // proxy#2:0.0.0.0:28998=>unix:///tmp/chisel-18998.socket: Listening + connstr:= "R:unix:///tmp/chisel-18998.socket:localhost:8998|127.0.0.1:28998:unix:///tmp/chisel-18998.socket" //串联 sock< 8998 | 28998 < sock + ser:= "localhost:8080" + auth:= "user:pass" + remotes:= strings.Split(connstr, "|") + client(args, ser, auth, remotes) +} diff --git a/example/t1/main.go b/example/t1/main.go new file mode 100644 index 0000000000000000000000000000000000000000..aa6374dffc1c77c1cc54d99a79dc93ab73b92a3a --- /dev/null +++ b/example/t1/main.go @@ -0,0 +1,104 @@ +package main + +// https://www.cnblogs.com/walkinginthesun/p/10397539.html +import ( + "context" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "time" + "os" +) + +var handlersMap = make(map[string]http.HandlerFunc) + +func f1(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "software is healthy\n") +} + +type bluetool struct { +} + +func (bt *bluetool) ServeHTTP(w http.ResponseWriter, r *http.Request) { + msg := fmt.Sprintf("url is %s\n",r.URL.String()) + io.WriteString(w, msg) + if h, ok := handlersMap[r.URL.String()]; ok { + h(w, r) + } +} + +//cp from fk-agent +//headless@mac23-199:~$ curl --unix-socket /tmp/t1_unix.socket http://localhost #8998> 18998 > 28998/uds ##查看文件 +func main() { + sockpath := "/tmp/t1_unix.socket" + // hand: touch /data/unix.socket + os.Remove(sockpath) + + // server + bt := new(bluetool) + handlersMap["/health"] = f1 + + // unix socket + addr, err := net.ResolveUnixAddr("unix", sockpath) + if err != nil { + panic("Cannot resolve unix addr: " + err.Error()) + } + fmt.Println(addr.String()) + + listener, err := net.ListenUnix("unix", addr) + defer listener.Close() + if err != nil { + panic("Cannot listen to unix domain socket: " + err.Error()) + } + fmt.Println("Listening on", listener.Addr()) + + donec := make(chan struct{}) + go func() { + defer close(donec) + http.Serve(listener, bt) + }() + defer func() { + listener.Close() + <-donec + }() + + + // client + defaultTimeout := 10 * time.Second + tr := new(http.Transport) + tr.DisableCompression = true + tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { + return net.DialTimeout("unix", sockpath, defaultTimeout) + } + client := &http.Client{Transport: tr} + + if _, ok := client.Transport.(http.RoundTripper); !ok { + fmt.Printf("unable to verify TLS configuration, invalid transport %v", client.Transport) + } + + + //url := "http://" + listener.Addr().String() + "/health" + //pingUrl := url.URL{Scheme:"unix",Host:sockpath, Path:path.Join("/","/health")} + //fmt.Println(pingUrl) + + var body io.Reader + body=nil + request, err := http.NewRequest("GET", "/health", body) + request.URL.Scheme="http" + request.URL.Host = sockpath + // fmt.Println(request) + + resp, err := client.Do(request) + if err != nil { + log.Fatalf("fetch error: %v", err) + } + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("fetch error: reading %v", err) + } + fmt.Println(string(b)) +} \ No newline at end of file diff --git a/go.mod b/go.mod index 8832e6904e3ccc40295eeca33932ced930196acc..8123f1587f79618ca83d32f1a14aa58f412224c8 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jpillora/chisel +module gitee.com/g-devops/chisel go 1.13 diff --git a/main.go b/main.go index 570b508ed7797738587123df83626fe8a6246c91..73f25b23648625ad2b74679ae4485152d0215648 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,9 @@ import ( "os" "strconv" - "github.com/jpillora/chisel/client" - "github.com/jpillora/chisel/server" - chshare "github.com/jpillora/chisel/share" + chclient "gitee.com/g-devops/chisel/client" + chserver "gitee.com/g-devops/chisel/server" + chshare "gitee.com/g-devops/chisel/share" ) var help = ` @@ -129,6 +129,10 @@ var serverHelp = ` --reverse, Allow clients to specify reverse port forwarding remotes in addition to normal remotes. + + --uds, Allow clients to connect to unix domain sockets on remote targets. + This mode is only supported by *nix family (linux, osx) + ` + commonHelp func server(args []string) { @@ -146,6 +150,7 @@ func server(args []string) { reverse := flags.Bool("reverse", false, "") pid := flags.Bool("pid", false, "") verbose := flags.Bool("v", false, "") + uds := flags.Bool("uds", false, "") flags.Usage = func() { fmt.Print(serverHelp) @@ -178,6 +183,7 @@ func server(args []string) { Proxy: *proxy, Socks5: *socks5, Reverse: *reverse, + UdsMode: *uds, }) if err != nil { log.Fatal(err) diff --git a/server/handler.go b/server/handler.go index d48eeb6c4472535af7e84d1916235d9d305e9aed..febce6b0ea587ede243888a917148505503446e9 100644 --- a/server/handler.go +++ b/server/handler.go @@ -4,11 +4,12 @@ import ( "context" "io" "net/http" + "runtime" "strings" "sync/atomic" "time" - chshare "github.com/jpillora/chisel/share" + chshare "gitee.com/g-devops/chisel/share" "golang.org/x/crypto/ssh" ) @@ -66,7 +67,7 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) { var user *chshare.User if s.users.Len() > 0 { sid := string(sshConn.SessionID()) - user, _ = s.sessions.Get(sid) + user, _ = s.sessions.Get(sid) //s.sessions: set by server.go: authUser s.sessions.Del(sid) } //verify configuration @@ -108,16 +109,32 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) { failed(s.Errorf("Reverse port forwaring not enabled on server")) return } + if r.Uds && !s.UdsMode { + clog.Debugf("Denied unix domain socket forwarding request, please enable --uds") + failed(s.Errorf("Unix domain socket not enabled on server")) + return + } } + + // judge user's access //if user is provided, ensure they have //access to the desired remotes if user != nil { for _, r := range c.Remotes { var addr string - if r.Reverse { - addr = "R:" + r.LocalHost + ":" + r.LocalPort - } else { - addr = r.RemoteHost + ":" + r.RemotePort + if r.Reverse { //validate bind addr + //tcpMode || udsMode + if "tcpMode" == r.LocalUds { + addr = "R:" + r.LocalHost + ":" + r.LocalPort + } else { + addr = "R:" + r.LocalUds + } + } else { //validate remoteURI + if "tcpMode" == r.RemoteUds { + addr = r.RemoteHost + ":" + r.RemotePort + } else { + addr = r.RemoteUds + } } if !user.HasAccess(addr) { failed(s.Errorf("access to '%s' denied", addr)) @@ -159,6 +176,7 @@ func (s *Server) handleSSHRequests(clientLog *chshare.Logger, reqs <-chan *ssh.R } func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.NewChannel) { + socketPrefix := "unix://" for ch := range chans { remote := string(ch.ExtraData()) socks := remote == "socks" @@ -176,10 +194,22 @@ func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.N } go ssh.DiscardRequests(reqs) //handle stream type - connID := s.connStats.New() if socks { + connID := s.connStats.New() go s.handleSocksStream(clientLog.Fork("socksconn#%d", connID), stream) + } else if strings.HasPrefix(remote, socketPrefix) { + if !s.UdsMode { + clientLog.Debugf("Unix domain socket is not allowed by the server") + } + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + clientLog.Debugf("Unix domain socket is only supported on *nix system") + continue + } + remote := strings.TrimPrefix(remote, socketPrefix) + connID := s.connStats.New() + go chshare.HandleUnixDomainSocketStream(clientLog.Fork("unixconn#%d", connID), &s.connStats, stream, remote) } else { + connID := s.connStats.New() go chshare.HandleTCPStream(clientLog.Fork("conn#%d", connID), &s.connStats, stream, remote) } } diff --git a/server/server.go b/server/server.go index 8a60dd5b1124e18ea080c1c623de798bb905a9cc..a9e5539de46f6d9c21d61bc7ef8a6d24ea9d7c94 100644 --- a/server/server.go +++ b/server/server.go @@ -10,9 +10,9 @@ import ( "os" "regexp" + chshare "gitee.com/g-devops/chisel/share" socks5 "github.com/armon/go-socks5" "github.com/gorilla/websocket" - chshare "github.com/jpillora/chisel/share" "github.com/jpillora/requestlog" "golang.org/x/crypto/ssh" ) @@ -25,6 +25,7 @@ type Config struct { Proxy string Socks5 bool Reverse bool + UdsMode bool } // Server respresent a chisel service @@ -40,6 +41,7 @@ type Server struct { sshConfig *ssh.ServerConfig users *chshare.UserIndex reverseOk bool + UdsMode bool } var upgrader = websocket.Upgrader{ @@ -55,6 +57,7 @@ func NewServer(config *Config) (*Server, error) { Logger: chshare.NewLogger("server"), sessions: chshare.NewUsers(), reverseOk: config.Reverse, + UdsMode: config.UdsMode, } s.Info = true s.users = chshare.NewUserIndex(s.Logger) @@ -82,7 +85,7 @@ func NewServer(config *Config) (*Server, error) { //create ssh config s.sshConfig = &ssh.ServerConfig{ ServerVersion: "SSH-" + chshare.ProtocolVersion + "-server", - PasswordCallback: s.authUser, + PasswordCallback: s.authUser, //sshAuth? } s.sshConfig.AddHostKey(private) //setup reverse proxy @@ -167,19 +170,19 @@ func (s *Server) GetFingerprint() string { // authUser is responsible for validating the ssh user / password combination func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { // check if user authenication is enable and it not allow all - if s.users.Len() == 0 { + if s.users.Len() == 0 { //如未设定帐号,passed return nil, nil } // check the user exists and has matching password n := c.User() user, found := s.users.Get(n) - if !found || user.Pass != string(password) { + if !found || user.Pass != string(password) { //auth s.Debugf("Login failed for user: %s", n) return nil, errors.New("Invalid authentication for username: %s") } // insert the user session map // @note: this should probably have a lock on it given the map isn't thread-safe?? - s.sessions.Set(string(c.SessionID()), user) + s.sessions.Set(string(c.SessionID()), user) //set user to s.sessions return nil, nil } diff --git a/share/proxy.go b/share/proxy.go index 862a5d76a14dd730a576f2033303c37bcb9b25f8..46f500b7b3be43635b947e54332e52c07cddc395 100644 --- a/share/proxy.go +++ b/share/proxy.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "os" "github.com/jpillora/sizestr" "golang.org/x/crypto/ssh" @@ -30,8 +31,32 @@ func NewTCPProxy(logger *Logger, ssh GetSSHConn, index int, remote *Remote) *TCP } } +func listen_uds(sockpath string) (net.Listener, error) { + // hand: touch /data/unix.socket + // sockpath := "/tmp/t1_unix.socket" + os.Remove(sockpath) + + // unix socket + addr, err := net.ResolveUnixAddr("unix", sockpath) + if err != nil { + panic("Cannot resolve unix addr: " + err.Error()) + } + fmt.Println(addr.String()) + + listener, err := net.ListenUnix("unix", addr) + + return listener, nil +} +//被ser.handler; client两端共用 func (p *TCPProxy) Start(ctx context.Context) error { - l, err := net.Listen("tcp4", p.remote.LocalHost+":"+p.remote.LocalPort) + //if LocalUds非空 + var l net.Listener; var err error + if "tcpMode" == p.remote.LocalUds { + l, err = net.Listen("tcp4", p.remote.LocalHost+":"+p.remote.LocalPort) //localHost > bindHost, bindPort + } else { + l, err = listen_uds(p.remote.LocalUds) //只用LocalPort即可; > 改存于LocalUds内 + } + if err != nil { return fmt.Errorf("%s: %s", p.Logger.Prefix(), err) } diff --git a/share/remote.go b/share/remote.go index 3aad30e1f716fdab28f904367daabafaaf2779b9..bd2363a0926672add02b0489e78533135a206d15 100644 --- a/share/remote.go +++ b/share/remote.go @@ -3,7 +3,9 @@ package chshare import ( "errors" "net/url" + "os" "regexp" + "runtime" "strings" ) @@ -21,13 +23,24 @@ import ( // local 192.168.0.1:3000 // remote google.com:80 +// +// 3000:unix:///tmp/mysql.sock -> ##OK +// local 127.0.0.1:3000 +// remote (local sock) /tmp/mysql.sock +// R:unix:///tmp/mysql.sock:3000 -> ##ERR? +// local (local sock) /tmp/mysql.sock +// remote 127.0.0.1:3000 + type Remote struct { - LocalHost, LocalPort, RemoteHost, RemotePort string - Socks, Reverse bool + LocalHost, LocalPort, RemoteHost, RemotePort, RemoteUds, LocalUds string + Socks, Uds, Reverse bool } const revPrefix = "R:" +const udsScheme = "unix" +const udsPrefix = udsScheme + "://" +// 解析组装remote结构体: 只在client端调用 func DecodeRemote(s string) (*Remote, error) { reverse := false if strings.HasPrefix(s, revPrefix) { @@ -39,6 +52,9 @@ func DecodeRemote(s string) (*Remote, error) { return nil, errors.New("Invalid remote") } r := &Remote{Reverse: reverse} + r.RemoteUds="tcpMode" + r.LocalUds="tcpMode" + for i := len(parts) - 1; i >= 0; i-- { p := parts[i] //last part "socks"? @@ -60,9 +76,48 @@ func DecodeRemote(s string) (*Remote, error) { } continue } + //last part unix://path/to/unix/domain/socket + // R:127.0.0.1:XXX:UNIX:///tmp/xx1.socket + // isUds(udsScheme+":"+p) ##由于unix://本身被分割了 + if i == len(parts)-1 && isUds(udsScheme+":"+p) { //i == len(parts)-1; + udsPath := strings.TrimPrefix(p, "//") //直接记录的 /tmp/xx1.socket到rPort内 + if reverse { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + return nil, errors.New("Unix domain socket is only supported on *nix system") + } + if _, err := os.Stat(udsPath); os.IsNotExist(err) { + return nil, errors.New("Unix domain socket " + udsPath + " does not exist!") + } + } + r.RemotePort = udsPath //写到rPort; + r.RemoteUds = udsPath //+ + r.Uds = true + continue + } + //local: unixSocket模式 + if isUds(udsScheme+":"+p) { + udsPath := strings.TrimPrefix(p, "//") + if reverse { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + return nil, errors.New("Unix domain socket is only supported on *nix system") + } + /* if _, err := os.Stat(udsPath); os.IsNotExist(err) { + return nil, errors.New("Unix domain socket " + udsPath + " does not exist!") + } */ + } + // r.LocalPort = udsPath //写到lPort; + r.LocalUds = udsPath //+ + r.Uds = true + continue + } + //Socks if !r.Socks && (r.RemotePort == "" && r.LocalPort == "") { return nil, errors.New("Missing ports") } + //不干扰原模式: 第一个unix及rHost都不写入Remote主机字段 + if p == "unix" { //skip host set, 之前模式是用到的: .Remote() 拼接: unix+":"+"//"+rPort + continue + } if !isHost(p) { return nil, errors.New("Invalid host") } @@ -72,6 +127,7 @@ func DecodeRemote(s string) (*Remote, error) { r.LocalHost = p } } + //Host emp: fill data if r.LocalHost == "" { if r.Socks { r.LocalHost = "127.0.0.1" @@ -88,8 +144,18 @@ func DecodeRemote(s string) (*Remote, error) { return r, nil } -var isPortRegExp = regexp.MustCompile(`^\d+$`) +func isUds(s string) bool { + if !strings.HasPrefix(s, udsPrefix) { + return false + } + url, err := url.Parse(s) + if err != nil || s != udsPrefix+url.Hostname()+url.Path { + return false + } + return true +} +var isPortRegExp = regexp.MustCompile(`^\d+$`) func isPort(s string) bool { if !isPortRegExp.MatchString(s) { return false @@ -106,17 +172,50 @@ func isHost(s string) bool { } //implement Stringer -func (r *Remote) String() string { +func (r *Remote) String() string { // tag := "" if r.Reverse { tag = revPrefix } - return tag + r.LocalHost + ":" + r.LocalPort + "=>" + r.Remote() + bind:= "" + if "tcpMode" == r.LocalUds { + bind= r.LocalHost + ":" + r.LocalPort + } else { + bind= r.LocalUds + } + return tag + bind + "=>" + r.Remote() } -func (r *Remote) Remote() string { +// 只proxy.go> accept一处用到: bind的端口做accept时, sshConn建立远端的channel; +// TODO: 改从RemoteUds内取值: (当r.Uds时 当前回的串格式?) +func (r *Remote) Remote00() string { //拼装获取remote地址: rHost+rPort if r.Socks { return "socks" } - return r.RemoteHost + ":" + r.RemotePort + joiner := ":" + if r.Uds { + joiner += "//" + } + + //uds时socket路径存放于rPort, rHost直接为空? just: :///var/run/xxx.socket?? ##host 应该为unix; + // log: r.RemoteHost + joiner + r.RemotePort + return r.RemoteHost + joiner + r.RemotePort } +func (r *Remote) Remote() string { //拼装获取remote地址: rHost+rPort + if r.Socks { + return "socks" + } + joiner := ":" + /* if r.Uds { + joiner += "//" + } */ + + //uds时socket路径存放于rPort, rHost直接为空? just: :///var/run/xxx.socket?? ##host 应该为unix; + // log: r.RemoteHost + joiner + r.RemotePort + if "tcpMode" == r.RemoteUds { + return r.RemoteHost + joiner + r.RemotePort + } else { + return "unix://"+r.RemoteUds + } + +} \ No newline at end of file diff --git a/share/ssh.go b/share/ssh.go index 610c59bfd978b7c9613d49271f838e754111738d..3f1b4a380b7f41e9ff45ea5bf050e368f67ec446 100644 --- a/share/ssh.go +++ b/share/ssh.go @@ -44,7 +44,15 @@ func FingerprintKey(k ssh.PublicKey) string { } func HandleTCPStream(l *Logger, connStats *ConnStats, src io.ReadWriteCloser, remote string) { - dst, err := net.Dial("tcp", remote) + HandleDuplexStream(l, connStats, src, "tcp", remote) +} + +func HandleUnixDomainSocketStream(l *Logger, connStats *ConnStats, src io.ReadWriteCloser, remote string) { + HandleDuplexStream(l, connStats, src, "unix", remote) +} + +func HandleDuplexStream(l *Logger, connStats *ConnStats, src io.ReadWriteCloser, protocol string, remote string) { + dst, err := net.Dial(protocol, remote) if err != nil { l.Debugf("Remote failed (%s)", err) src.Close()