diff --git a/go.mod b/go.mod index 397a2ae..c005f5f 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/inconshreveable/mousetrap v1.0.1 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/spf13/cobra v1.6.1 ) @@ -22,7 +23,6 @@ require ( b612.me/starmap v1.2.3 // indirect b612.me/starnet v0.1.7 // indirect b612.me/win32api v0.0.1 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jlaffaye/ftp v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/pkg/sftp v1.13.4 // indirect diff --git a/httpreverse/cmd.go b/httpreverse/cmd.go index 15c77d0..bac1e3f 100644 --- a/httpreverse/cmd.go +++ b/httpreverse/cmd.go @@ -11,7 +11,7 @@ import ( var remote, config string var addr, key, cert, log string var port int -var enablessl bool +var enablessl, skipsslverify bool var host string func init() { @@ -23,6 +23,7 @@ func init() { Cmd.Flags().StringVarP(&cert, "cert", "c", "", "ssl 证书地址") Cmd.Flags().StringVarP(&log, "log", "l", "", "log日志地址") Cmd.Flags().BoolVarP(&enablessl, "enable-ssl", "s", false, "启用ssl") + Cmd.Flags().BoolVarP(&skipsslverify, "skil-ssl-verify", "S", false, "跳过证书验证") Cmd.Flags().IntVarP(&port, "port", "p", 8080, "监听端口") } @@ -69,11 +70,12 @@ var Cmd = &cobra.Command{ ReverseURL: map[string]*url.URL{ "/": u, }, - Port: port, - UsingSSL: enablessl, - Key: key, - Cert: cert, - XForwardMode: 1, + Port: port, + UsingSSL: enablessl, + SkipSSLVerify: skipsslverify, + Key: key, + Cert: cert, + XForwardMode: 1, } go func() { sig := make(chan os.Signal) diff --git a/httpreverse/reverse.go b/httpreverse/reverse.go index 9db8dbc..075c781 100644 --- a/httpreverse/reverse.go +++ b/httpreverse/reverse.go @@ -12,23 +12,24 @@ import ( ) type ReverseConfig struct { - Name string - Addr string - ReverseURL map[string]*url.URL - Port int - UsingSSL bool - Key string - Cert string - Host string - InHeader [][2]string - OutHeader [][2]string - Cookie [][3]string //[3]string should contains path::key::value - ReplaceList [][2]string - ReplaceOnce bool - proxy map[string]*httputil.ReverseProxy - XForwardMode int //0=off 1=useremote 2=add - httpmux http.ServeMux - httpserver http.Server + Name string + Addr string + ReverseURL map[string]*url.URL + Port int + UsingSSL bool + Key string + Cert string + Host string + SkipSSLVerify bool + InHeader [][2]string + OutHeader [][2]string + Cookie [][3]string //[3]string should contains path::key::value + ReplaceList [][2]string + ReplaceOnce bool + proxy map[string]*httputil.ReverseProxy + XForwardMode int //0=off 1=useremote 2=add + httpmux http.ServeMux + httpserver http.Server basicAuthUser string basicAuthPwd string diff --git a/httpreverse/service.go b/httpreverse/service.go index d81fe25..2d0ae60 100644 --- a/httpreverse/service.go +++ b/httpreverse/service.go @@ -4,9 +4,11 @@ import ( "b612.me/starlog" "bytes" "context" + "crypto/tls" "encoding/base64" "fmt" "io/ioutil" + "net" "net/http" "net/http/httputil" "net/url" @@ -75,10 +77,61 @@ func (h *ReverseConfig) Close() error { return h.httpserver.Shutdown(ctx) } +func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(network, addr, time.Second*20) + if err != nil { + return nil, err + } + + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + if h.Host != "" { + host = h.Host + } + cfg := &tls.Config{ServerName: host} + tlsConn := tls.Client(conn, cfg) + if err := tlsConn.Handshake(); err != nil { + conn.Close() + return nil, err + } + + cs := tlsConn.ConnectionState() + cert := cs.PeerCertificates[0] + + // Verify here + if !h.SkipSSLVerify { + err = cert.VerifyHostname(host) + if err != nil { + return nil, err + } + } + return tlsConn, nil +} + func (h *ReverseConfig) init() error { h.proxy = make(map[string]*httputil.ReverseProxy) for key, val := range h.ReverseURL { - h.proxy[key] = httputil.NewSingleHostReverseProxy(val) + h.proxy[key] = &httputil.ReverseProxy{ + Transport: &http.Transport{DialTLSContext: h.dialTLS}, + Director: func(req *http.Request) { + targetQuery := val.RawQuery + req.URL.Scheme = val.Scheme + if h.Host == "" { + req.Host = val.Host + } else { + req.Host = h.Host + } + req.URL.Host = val.Host + req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL) + if targetQuery == "" || req.URL.RawQuery == "" { + req.URL.RawQuery = targetQuery + req.URL.RawQuery + } else { + req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery + } + }, + } h.proxy[key].ModifyResponse = h.ModifyResponse() originalDirector := h.proxy[key].Director h.proxy[key].Director = func(req *http.Request) { diff --git a/main.go b/main.go index 843f9cd..a710001 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "b612.me/apps/b612/httpserver" "b612.me/apps/b612/image" "b612.me/apps/b612/merge" + "b612.me/apps/b612/net" "b612.me/apps/b612/search" "b612.me/apps/b612/split" "b612.me/apps/b612/tcping" @@ -29,7 +30,7 @@ import ( var cmdRoot = &cobra.Command{ Use: "b612", - Version: "2.0.1", + Version: "2.0.2", } func init() { @@ -37,7 +38,7 @@ func init() { cmdRoot.AddCommand(tcping.Cmd, uac.Cmd, httpserver.Cmd, httpreverse.Cmd, base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd, ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd, - calc.Cmd) + calc.Cmd, net.Cmd) } func main() { diff --git a/net/cmd.go b/net/cmd.go index 9d9f1a1..24499d8 100644 --- a/net/cmd.go +++ b/net/cmd.go @@ -1 +1,15 @@ package net + +import ( + "b612.me/apps/b612/netforward" + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "net", + Short: "net tools", +} + +func init() { + Cmd.AddCommand(netforward.CmdNetforward) +} diff --git a/net/forward_test.go b/net/forward_test.go deleted file mode 100644 index fd00d2d..0000000 --- a/net/forward_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package net - -import "testing" - -func TestForward(t *testing.T) { - var f = NetForward{ - LocalAddr: "127.0.0.1", - LocalPort: 22232, - RemoteURI: "127.0.0.1:1127", - EnableTCP: true, - EnableUDP: true, - DialTimeout: 0, - UDPTimeout: 0, - } - f.Run() -} diff --git a/net/natserver.go b/net/natserver.go index 26fd41f..35d4c6c 100644 --- a/net/natserver.go +++ b/net/natserver.go @@ -1,27 +1,27 @@ package net import ( - "b612.me/starlog" "bytes" - "errors" + "context" + "encoding/hex" "fmt" "io" "net" - "strings" "sync" + "sync/atomic" "time" ) -var MSG_CMD_HELLO = []byte{11, 27, 19, 96, 182, 18, 25, 150, 17, 39} -var MSG_NEW_CONN = []byte{0, 0, 0, 0, 255, 255, 255, 255, 11, 27} -var MSG_NEW_CONN_REQ = []byte{0, 0, 0, 0, 255, 255, 255, 255, 19, 96} -var MSG_CLOSE = []byte{255, 255, 0, 0, 255, 0, 0, 255, 255, 27} -var MSG_HEARTBEAT = []byte{6, 66, 66, 6, 6, 66, 6, 66, 11, 27} +// MSG_CMD_HELLO 控制链路主动链接参头 16byte +var MSG_CMD_HELLO, _ = hex.DecodeString("B6121127AF7ECDA1") +var MSG_CMD_HELLO_REPLY, _ = hex.DecodeString("B6121127AF7ECDA2") -type SimpleNatServer struct { - mu sync.RWMutex +// MSG_NEW_CONN_HELLO 交链路主动连接头 16byte +var MSG_NEW_CONN_HELLO, _ = hex.DecodeString("B6121127AF7ECDFF") + +type NatServer struct { + sync.RWMutex cmdTCPConn net.Conn - cmdUDPConn *net.UDPAddr listenTcp net.Listener listenUDP *net.UDPConn Addr string @@ -32,107 +32,58 @@ type SimpleNatServer struct { DialTimeout int64 UDPTimeout int64 running int32 - - tcpConnPool chan net.Conn - tcpAlived bool + tcpConnPool chan net.Conn + stopCtx context.Context + stopFn context.CancelFunc } -func (s *SimpleNatServer) getConnfromTCPPool() (net.Conn, error) { - select { - case conn := <-s.tcpConnPool: - return conn, nil - case <-time.After(time.Second * 10): - return nil, errors.New("no connection got") +func (n *NatServer) Run() error { + if n.running != 0 { + return fmt.Errorf("Server Already Run") } + n.stopCtx, n.stopFn = context.WithCancel(context.Background()) + return nil } -func (s *SimpleNatServer) tcpCmdConn() net.Conn { - s.mu.RLock() - defer s.mu.RUnlock() - return s.cmdTCPConn -} +func (n *NatServer) cmdTcploop(conn net.Conn) error { + var header = make([]byte, 16) + for { + c, err := conn.Read(header) + if err != nil { + //todo + } + if c != 16 { -func (s *SimpleNatServer) tcpCmdConnAlived() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.tcpAlived + } + } } -func (s *SimpleNatServer) listenTCP() error { - var err error - s.tcpConnPool = make(chan net.Conn, 10) - s.listenTcp, err = net.Listen("tcp", fmt.Sprintf("%s:d", s.Addr, s.Port)) +func (n *NatServer) runTcpListen() error { + atomic.AddInt32(&n.running, 1) + defer atomic.AddInt32(&n.running, -1) + listener, err := net.Listen("tcp", n.Addr) if err != nil { - starlog.Errorln("failed to listen tcp", err) return err } + n.listenTcp = listener for { - conn, err := s.listenTcp.Accept() + conn, err := listener.Accept() if err != nil { continue } - if s.tcpCmdConnAlived() { - go s.tcpClientServe(conn.(*net.TCPConn)) - continue - } - go s.waitingForTCPCmd(conn.(*net.TCPConn)) - } - return nil -} - -func (s *SimpleNatServer) tcpClientServe(conn *net.TCPConn) { - if !s.tcpCmdConnAlived() { - conn.Close() - return - } - - if strings.Split(conn.RemoteAddr().String(), ":")[0] == strings.Split(s.tcpCmdConn().RemoteAddr().String(), ":")[0] { - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - cmdBuf := make([]byte, 10) - if _, err := io.ReadFull(conn, cmdBuf); err == nil { - conn.SetReadDeadline(time.Time{}) - if bytes.Equal(cmdBuf, MSG_NEW_CONN) { - starlog.Noticef("Nat Server Recv New Client Conn From %v\n", conn.RemoteAddr().String()) - s.tcpConnPool <- conn - return + headedr := make([]byte, 16) + conn.SetReadDeadline(time.Now().Add(time.Millisecond * 700)) + c, err := conn.Read(headedr) + if err == nil && c == 16 { + if bytes.Equal(headedr, MSG_CMD_HELLO) { + if n.cmdTCPConn != nil { + n.cmdTCPConn.Close() + } + n.cmdTCPConn = conn + conn.Write(MSG_CMD_HELLO_REPLY) + // } } - conn.SetReadDeadline(time.Time{}) - } - starlog.Noticef("Nat Server Recv New Side Conn From %v\n", conn.RemoteAddr().String()) - _, err := s.tcpCmdConn().Write(MSG_NEW_CONN_REQ) - if err != nil { - s.mu.Lock() - s.cmdTCPConn.Close() - s.tcpAlived = false - s.mu.Unlock() - starlog.Errorf("Failed to Write CMD To Client:%v\n", err) - return - } - reverse, err := s.getConnfromTCPPool() - if err != nil { - starlog.Errorf("Nat Server Conn to %v Closed %v\n", conn.RemoteAddr(), err) - conn.Close() - return - } - starlog.Infof("Nat Server Conn %v<==>%v Connected\n", conn.RemoteAddr(), reverse.RemoteAddr()) - Copy(reverse, conn) - starlog.Warningf("Nat Server Conn %v<==>%v Closed\n", conn.RemoteAddr(), reverse.RemoteAddr()) -} - -func (s *SimpleNatServer) waitingForTCPCmd(conn *net.TCPConn) { - conn.SetReadDeadline(time.Now().Add(time.Duration(s.DialTimeout) * time.Second)) - cmdBuf := make([]byte, 10) - if _, err := io.ReadFull(conn, cmdBuf); err != nil { - conn.Close() - return - } - if bytes.Equal(cmdBuf, MSG_CMD_HELLO) { - s.mu.Lock() - s.cmdTCPConn = conn - s.tcpAlived = true - conn.SetKeepAlive(true) - conn.SetKeepAlivePeriod(time.Second * 20) - s.mu.Unlock() + io.ReadFull(conn, headedr) } } diff --git a/netforward/cmd.go b/netforward/cmd.go new file mode 100644 index 0000000..3deb3a3 --- /dev/null +++ b/netforward/cmd.go @@ -0,0 +1,67 @@ +package netforward + +import ( + "b612.me/stario" + "b612.me/starlog" + "github.com/spf13/cobra" + "os" + "os/signal" + "strings" + "time" +) + +var f = new(NetForward) +var dialTimeout, udpTimeout int64 + +func init() { + CmdNetforward.Flags().StringVarP(&f.LocalAddr, "local", "l", "0.0.0.0", "bind address") + CmdNetforward.Flags().IntVarP(&f.LocalPort, "port", "p", 11270, "local listen port") + CmdNetforward.Flags().BoolVarP(&f.EnableTCP, "enable-tcp-forward", "t", true, "enable tcp forward mode") + CmdNetforward.Flags().BoolVarP(&f.EnableUDP, "enable-udp-forward", "u", true, "enable udp forward mode") + CmdNetforward.Flags().Int64VarP(&dialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") + CmdNetforward.Flags().Int64VarP(&udpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") +} + +var CmdNetforward = &cobra.Command{ + Use: "forward", + Short: "net forward", + Long: "forward tcp and udp packet", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + starlog.Errorln("please enter a target uri") + os.Exit(1) + } + f.RemoteURI = strings.TrimSpace(args[0]) + if dialTimeout == 0 { + dialTimeout = 10000 + } + if udpTimeout == 0 { + udpTimeout = 60000 + } + f.DialTimeout = time.Duration(dialTimeout) * time.Millisecond + f.UDPTimeout = time.Duration(udpTimeout) * time.Millisecond + if err := f.Run(); err != nil { + starlog.Errorln("run net forward failed:", err) + os.Exit(2) + } + sign := make(chan os.Signal) + signal.Notify(sign, os.Interrupt, os.Kill) + for { + select { + case <-sign: + starlog.Noticeln("Recv Stop Signal From User") + f.stopFn() + case <-stario.WaitUntilFinished(func() error { + for { + if f.Status() == 0 { + return nil + } + time.Sleep(time.Second) + } + }): + starlog.Infoln("Service Stoped") + return + } + } + }, +} diff --git a/net/forward.go b/netforward/forward.go similarity index 77% rename from net/forward.go rename to netforward/forward.go index 57ea1bd..e8c3bb5 100644 --- a/net/forward.go +++ b/netforward/forward.go @@ -1,4 +1,4 @@ -package net +package netforward import ( "b612.me/starlog" @@ -28,35 +28,33 @@ type NetForward struct { func (n *NetForward) Close() { n.stopFn() } + +func (n *NetForward) Status() int32 { + return n.running +} + func (n *NetForward) Run() error { - if !atomic.CompareAndSwapInt32(&n.running, 0, 1) { + if n.running > 0 { + starlog.Errorln("already running") return errors.New("already running") } n.stopCtx, n.stopFn = context.WithCancel(context.Background()) if n.DialTimeout == 0 { - n.DialTimeout = time.Second * 10 + n.DialTimeout = time.Second * 5 } - var wg sync.WaitGroup if n.EnableTCP { - wg.Add(1) - go func() { - defer wg.Done() - n.runTCP() - }() + go n.runTCP() } if n.EnableUDP { - wg.Add(1) - go func() { - defer wg.Done() - n.runUDP() - }() + go n.runUDP() } - wg.Wait() return nil } func (n *NetForward) runTCP() error { + atomic.AddInt32(&n.running, 1) + defer atomic.AddInt32(&n.running, -1) listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) if err != nil { starlog.Errorln("Listening On Tcp Failed:", err) @@ -68,6 +66,11 @@ func (n *NetForward) runTCP() error { }() starlog.Infof("Listening TCP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) for { + select { + case <-n.stopCtx.Done(): + return nil + default: + } conn, err := listen.Accept() if err != nil { continue @@ -77,13 +80,13 @@ func (n *NetForward) runTCP() error { go func(conn net.Conn) { rmt, err := net.DialTimeout("tcp", n.RemoteURI, n.DialTimeout) if err != nil { - log.Errorf("Dial Remote %s Failed:%v\n", n.RemoteURI, err) + log.Errorf("TCP:Dial Remote %s Failed:%v\n", n.RemoteURI, err) conn.Close() return } - log.Infof("Connect %s <==> %s\n", conn.RemoteAddr().String(), n.RemoteURI) + log.Infof("TCP Connect %s <==> %s\n", conn.RemoteAddr().String(), rmt.RemoteAddr().String()) Copy(rmt, conn) - log.Noticef("Connection Closed %s <==> %s", conn.RemoteAddr().String(), n.RemoteURI) + log.Noticef("TCP Connection Closed %s <==> %s", conn.RemoteAddr().String(), n.RemoteURI) }(conn) } } @@ -124,6 +127,8 @@ func (u UDPConn) Work() { func (n *NetForward) runUDP() error { var mu sync.RWMutex + atomic.AddInt32(&n.running, 1) + defer atomic.AddInt32(&n.running, -1) udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", n.LocalAddr, n.LocalPort)) if err != nil { return err @@ -148,7 +153,7 @@ func (n *NetForward) runUDP() error { for k, v := range udpMap { if time.Now().Unix() > int64(n.UDPTimeout.Seconds())+v.lastbeat { delete(udpMap, k) - starlog.Noticef("Connection Closed %s <==> %s", v.remoteAddr.String(), n.RemoteURI) + starlog.Noticef("UDP Connection Closed %s <==> %s\n", v.remoteAddr.String(), n.RemoteURI) } } mu.Unlock() @@ -157,6 +162,11 @@ func (n *NetForward) runUDP() error { }() buf := make([]byte, 8192) for { + select { + case <-n.stopCtx.Done(): + return nil + default: + } count, rmt, err := listen.ReadFromUDP(buf) if err != nil || rmt.String() == n.RemoteURI { continue @@ -169,7 +179,7 @@ func (n *NetForward) runUDP() error { log.Infof("Accept New UDP Conn from %v\n", rmt.String()) conn, err := net.Dial("udp", n.RemoteURI) if err != nil { - log.Errorf("Dial Remote %s Failed:%v\n", n.RemoteURI, err) + log.Errorf("UDP:Dial Remote %s Failed:%v\n", n.RemoteURI, err) mu.Unlock() return } @@ -181,7 +191,7 @@ func (n *NetForward) runUDP() error { } udpMap[rmt.String()] = addr go addr.Work() - log.Infof("Connect %s <==> %s\n", rmt.String(), n.RemoteURI) + log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI) } mu.Unlock() _, err := addr.Write(data) @@ -190,7 +200,7 @@ func (n *NetForward) runUDP() error { addr.Close() delete(udpMap, addr.remoteAddr.String()) mu.Unlock() - log.Noticef("Connection Closed %s <==> %s", rmt.String(), n.RemoteURI) + log.Noticef("UDP Connection Closed %s <==> %s\n", rmt.String(), n.RemoteURI) } }(buf[0:count], rmt) } diff --git a/netforward/forward_test.go b/netforward/forward_test.go new file mode 100644 index 0000000..9d4d996 --- /dev/null +++ b/netforward/forward_test.go @@ -0,0 +1,33 @@ +package netforward + +import ( + "fmt" + "testing" + "time" +) + +func TestForward(t *testing.T) { + var f = NetForward{ + LocalAddr: "127.0.0.1", + LocalPort: 22232, + RemoteURI: "192.168.2.1:80", + EnableTCP: true, + EnableUDP: true, + DialTimeout: 6 * time.Second, + UDPTimeout: 7 * time.Second, + } + f.Run() + go func() { + time.Sleep(time.Second * 10) + fmt.Println("closing") + f.Close() + }() + for { + time.Sleep(time.Second * 2) + if f.Status() > 0 { + fmt.Println(f.Status()) + continue + } + return + } +}