From 7d85b14d6097ca4d349b7660b4acf227d407d55d Mon Sep 17 00:00:00 2001 From: starainrt Date: Sat, 29 Jun 2024 20:06:14 +0800 Subject: [PATCH] add more nat func --- main.go | 2 +- net/cmd.go | 87 +++++ net/natt.go | 642 ++++++++++++++++++++++++++++++++ net/natt_test.go | 50 +++ net/scan_test.go | 33 ++ net/scanip.go | 279 ++++++++++++++ net/scanport.go | 131 +++++++ net/tcpserver.go | 2 +- netforward/forward.go | 8 +- netforward/forward_test.go | 3 +- netforward/setcpinfo_darwin.go | 23 ++ netforward/setcpinfo_linux.go | 23 ++ netforward/setcpinfo_windows.go | 13 + 13 files changed, 1292 insertions(+), 4 deletions(-) create mode 100644 net/natt.go create mode 100644 net/natt_test.go create mode 100644 net/scan_test.go create mode 100644 net/scanip.go create mode 100644 net/scanport.go diff --git a/main.go b/main.go index 06c1618..79b9ca2 100644 --- a/main.go +++ b/main.go @@ -41,7 +41,7 @@ import ( var cmdRoot = &cobra.Command{ Use: "b612", - Version: "2.1.0.beta.8", + Version: "2.1.0.beta.10", } func init() { diff --git a/net/cmd.go b/net/cmd.go index d866159..2bd5b30 100644 --- a/net/cmd.go +++ b/net/cmd.go @@ -29,6 +29,10 @@ var maxHop int var disableIpInfo bool var bindAddr string var hideIncorrect bool +var natt NatThroughs + +var scanip ScanIP +var scanport ScanPort func init() { CmdNatPClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address") @@ -68,6 +72,36 @@ func init() { CmdNatServer.Flags().StringVarP(&nattests.AltPort, "alt-port", "A", "46610", "备用端口") CmdNatServer.Flags().StringVarP(&nattests.LogPath, "log", "l", "", "日志文件") Cmd.AddCommand(CmdNatServer) + + CmdNatThrough.Flags().StringVarP(&natt.STUN, "stun", "s", "turn.b612.me:3478", "stun服务器") + CmdNatThrough.Flags().StringVarP(&natt.Remote, "remote", "r", "baidu.com:80", "keepalive地址") + CmdNatThrough.Flags().IntVarP(&natt.KeepAlivePeriod, "keepalive-period", "p", 30, "KeepAlive周期") + CmdNatThrough.Flags().IntVarP(&natt.KeepAliveIdel, "keepalive-idel", "i", 30, "KeepAlive空闲时间") + CmdNatThrough.Flags().IntVarP(&natt.KeepAliveCount, "keepalive-count", "c", 5, "KeepAlive次数") + CmdNatThrough.Flags().BoolVarP(&natt.AutoUPnP, "auto-upnp", "u", true, "自动UPnP") + CmdNatThrough.Flags().IntVarP(&natt.WebPort, "web-port", "w", 8080, "web端口") + CmdNatThrough.Flags().IntVarP(&natt.HealthCheckInterval, "health-check-interval", "H", 30, "健康检查间隔") + Cmd.AddCommand(CmdNatThrough) + + CmdScanIP.Flags().StringVarP(&scanip.Host, "ip", "i", "", "扫描IP地址") + CmdScanIP.Flags().IntVarP(&scanip.Port, "port", "p", 80, "TCP模式扫描端口") + CmdScanIP.Flags().IntVarP(&scanip.Timeout, "timeout", "t", 2000, "超时时间") + CmdScanIP.Flags().IntVarP(&scanip.Threads, "threads", "m", 100, "最大线程数") + CmdScanIP.Flags().StringVarP(&scanip.Log, "log", "l", "", "日志文件地址") + CmdScanIP.Flags().StringVarP(&scanip.Mask, "mask", "M", "", "掩码") + CmdScanIP.Flags().IntVarP(&scanip.CIDR, "cidr", "c", 24, "CIDR") + CmdScanIP.Flags().StringVarP(&scanip.ScanType, "type", "T", "icmp", "扫描类型") + CmdScanIP.Flags().IntVarP(&scanip.Retry, "retry", "r", 2, "重试次数") + CmdScanIP.Flags().BoolVarP(&scanip.WithHostname, "with-hostname", "H", false, "显示主机名") + Cmd.AddCommand(CmdScanIP) + + CmdScanPort.Flags().StringVarP(&scanport.Host, "ip", "i", "", "扫描IP地址") + CmdScanPort.Flags().IntVarP(&scanport.Timeout, "timeout", "t", 2000, "超时时间") + CmdScanPort.Flags().IntVarP(&scanport.Threads, "threads", "m", 100, "最大线程数") + CmdScanPort.Flags().StringVarP(&scanport.Log, "log", "l", "", "日志文件地址") + CmdScanPort.Flags().IntVarP(&scanport.Retry, "retry", "r", 2, "重试次数") + Cmd.AddCommand(CmdScanPort) + } var CmdNatPClient = &cobra.Command{ @@ -147,3 +181,56 @@ var CmdNetTrace = &cobra.Command{ } }, } + +var CmdNatThrough = &cobra.Command{ + Use: "natt", + Short: "nat tcp直接穿透", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + starlog.Errorf("请按照如下格式输入:\n [远端地址] 或 [本地地址|远端地址] 或 [名字@本地地址|远端地址] 或 [协议|名字|本地地址|远端地址]\n") + return + } + if err := natt.Parse(args); err != nil { + starlog.Errorln(err) + return + } + if err := natt.Run(); err != nil { + starlog.Errorln(err) + } + }, +} + +var CmdScanIP = &cobra.Command{ + Use: "scanip", + Short: "扫描IP", + Run: func(cmd *cobra.Command, args []string) { + if scanip.Host == "" { + cmd.Help() + return + } + if scanip.ScanType == "icmp" { + scanip.ICMP() + } else { + scanip.TCP(scanip.Port) + } + }, +} + +var CmdScanPort = &cobra.Command{ + Use: "scanport", + Short: "扫描端口", + Run: func(cmd *cobra.Command, args []string) { + if scanport.Host == "" { + cmd.Help() + return + } + if len(args) != 1 { + starlog.Errorln("请指定端口范围,如:80,443,1000-2000") + } + if err := scanport.Parse(args[0]); err != nil { + starlog.Errorln(err) + return + } + scanport.Run() + }, +} diff --git a/net/natt.go b/net/natt.go new file mode 100644 index 0000000..4d2d561 --- /dev/null +++ b/net/natt.go @@ -0,0 +1,642 @@ +package net + +import ( + "b612.me/apps/b612/netforward" + "b612.me/starlog" + "context" + "encoding/binary" + "encoding/json" + "fmt" + "github.com/huin/goupnp/dcps/internetgateway2" + "math/rand" + "net" + "net/http" + "strconv" + "strings" + "sync" + "time" +) + +type NatThroughs struct { + Lists []*NatThrough + WebPort int + AutoUPnP bool + KeepAlivePeriod int + KeepAliveIdel int + KeepAliveCount int + STUN string + Remote string + HealthCheckInterval int +} + +func (n *NatThroughs) Close() { + for _, v := range n.Lists { + v.Close() + } +} + +func (n *NatThroughs) Parse(reqs []string) error { + if n.KeepAlivePeriod == 0 { + n.KeepAlivePeriod = 10 + } + if n.KeepAliveIdel == 0 { + n.KeepAliveIdel = 30 + } + if n.KeepAliveCount == 0 { + n.KeepAliveCount = 5 + } + if n.STUN == "" { + n.STUN = "turn.b612.me:3478" + } + for _, v := range reqs { + var req = NatThrough{ + Forward: netforward.NetForward{ + LocalAddr: "0.0.0.0", + DialTimeout: 3000, + UDPTimeout: 20000, + KeepAlivePeriod: n.KeepAlivePeriod, + KeepAliveIdel: n.KeepAliveIdel, + KeepAliveCount: n.KeepAliveCount, + UsingKeepAlive: true, + EnableTCP: true, + }, + Type: "tcp", + STUN: n.STUN, + Remote: n.Remote, + KeepAlivePeriod: n.KeepAlivePeriod, + KeepAliveIdel: n.KeepAliveIdel, + KeepAliveCount: n.KeepAliveCount, + AutoUPnP: n.AutoUPnP, + HealthCheckInterval: n.HealthCheckInterval, + } + strs := strings.Split(v, ",") + switch len(strs) { + case 1: + req.Type = "tcp" + req.Forward.RemoteURI = strs[0] + case 2: + ipport := strings.Split(strs[0], ":") + if len(ipport) == 1 { + port, err := strconv.Atoi(ipport[0]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } else { + req.Forward.LocalAddr = ipport[0] + port, err := strconv.Atoi(ipport[1]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } + req.Type = "tcp" + req.Forward.RemoteURI = strs[1] + case 3: + ipport := strings.Split(strs[1], ":") + if len(ipport) == 1 { + port, err := strconv.Atoi(ipport[0]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } else { + req.Forward.LocalAddr = ipport[0] + port, err := strconv.Atoi(ipport[1]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } + req.Type = "tcp" + req.Forward.RemoteURI = strs[2] + req.Name = strs[0] + case 4: + ipport := strings.Split(strs[2], ":") + if len(ipport) == 1 { + port, err := strconv.Atoi(ipport[0]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } else { + req.Forward.LocalAddr = ipport[0] + port, err := strconv.Atoi(ipport[1]) + if err != nil { + return err + } + req.Forward.LocalPort = port + } + req.Type = strings.ToLower(strs[0]) + req.Forward.RemoteURI = strs[3] + req.Name = strs[1] + } + n.Lists = append(n.Lists, &req) + } + return nil +} + +func (n *NatThroughs) Run() error { + go n.WebService() + wg := sync.WaitGroup{} + for _, v := range n.Lists { + wg.Add(1) + go func(v *NatThrough) { + defer wg.Done() + if err := v.Run(); err != nil { + starlog.Errorf("Failed to run forward: %v\n", err) + } + v.HealthCheck() + }(v) + } + wg.Wait() + return nil +} + +type nattinfo struct { + Id int `json:"id"` + Name string `json:"name"` + Ext string `json:"ext"` + Local string `json:"local"` + Forward string `json:"forward"` +} + +func (n *NatThroughs) WebService() error { + if n.WebPort == 0 { + return nil + } + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", n.WebPort)) + if err != nil { + return err + } + starlog.Infof("Web service listen on %d\n", n.WebPort) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var str string + for k, v := range n.Lists { + str += fmt.Sprintf("id:%d name:%s : %s <----> %s <-----> %s\n", k, v.Name, v.ExtUrl, v.localipport, v.Forward.RemoteURI) + } + w.Write([]byte(str)) + }) + http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) { + var res []nattinfo + for k, v := range n.Lists { + res = append(res, nattinfo{ + Id: k, + Name: v.Name, + Ext: v.ExtUrl, + Local: v.localipport, + Forward: v.Forward.RemoteURI, + }) + } + w.Header().Set("Content-Type", "application/json") + data, _ := json.Marshal(res) + w.Write(data) + return + }) + http.HandleFunc("/jump", func(w http.ResponseWriter, r *http.Request) { + types := "https://" + name := r.URL.Query().Get("name") + if name == "" { + w.Write([]byte("id is empty")) + return + } + if r.URL.Query().Get("type") == "http" { + types = "http://" + } + for _, v := range n.Lists { + if v.Name == name { + http.Redirect(w, r, types+v.ExtUrl, http.StatusFound) + return + } + } + }) + return http.Serve(listener, nil) +} + +// NatThrough 类似于natter.py 是一个用于Full Cone NAT直接穿透的工具 +type NatThrough struct { + Name string + OriginLocalPort int + Forward netforward.NetForward + Type string + STUN string + Remote string + KeepAlivePeriod int + KeepAliveIdel int + KeepAliveCount int + AutoUPnP bool + isOk bool + ExtUrl string + localipport string + keepaliveConn net.Conn + HealthCheckInterval int + stopFn context.CancelFunc + stopCtx context.Context +} + +func (n *NatThrough) Close() { + n.stopFn() + n.Forward.Close() +} + +func (c *NatThrough) Run() error { + c.isOk = false + c.stopCtx, c.stopFn = context.WithCancel(context.Background()) + c.OriginLocalPort = c.Forward.LocalPort + if c.Forward.LocalPort == 0 { + listener, err := net.Listen(c.Type, c.Forward.LocalAddr+":0") + if err != nil { + return fmt.Errorf("Failed to listen on %s: %v", c.Forward.LocalAddr, err) + } + if c.Type == "tcp" { + c.Forward.LocalPort = listener.Addr().(*net.TCPAddr).Port + } else { + c.Forward.LocalPort = listener.Addr().(*net.UDPAddr).Port + } + listener.Close() + } + if c.Type == "tcp" { + c.Forward.EnableTCP = true + c.Forward.EnableUDP = false + } else { + c.Forward.EnableTCP = false + c.Forward.EnableUDP = true + } + starlog.Infof("Local Port: %d\n", c.Forward.LocalPort) + starlog.Infof("Keepalive To: %s\n", c.Remote) + starlog.Infof("Forward To: %s\n", c.Forward.RemoteURI) + + innerIp, extIp, err := c.GetIPPortFromSTUN(c.Type, c.Forward.LocalAddr, c.Forward.LocalPort, c.STUN) + if err != nil { + return fmt.Errorf("Failed to get external IP and port: %v", err) + } + starlog.Infof("Internal Addr: %s \n", innerIp.String()) + starlog.Infof("External Addr: %s \n", extIp.String()) + getIP := func(ip net.Addr) string { + switch ip.(type) { + case *net.TCPAddr: + return ip.(*net.TCPAddr).IP.String() + case *net.UDPAddr: + return ip.(*net.UDPAddr).IP.String() + default: + return "" + } + } + getPort := func(ip net.Addr) int { + switch ip.(type) { + case *net.TCPAddr: + return ip.(*net.TCPAddr).Port + case *net.UDPAddr: + return ip.(*net.UDPAddr).Port + default: + return 0 + } + } + go func() { + if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil { + starlog.Errorf("Failed to run forward: %v\n", err) + c.Forward.Close() + c.stopFn() + } + }() + innerIp, extIp, err = c.GetIPPortFromSTUN(c.Type, c.Forward.LocalAddr, c.Forward.LocalPort, c.STUN) + if err != nil { + return fmt.Errorf("Failed to get external IP and port: %v", err) + } + starlog.Infof("Retest Internal Addr: %s \n", innerIp.String()) + starlog.Infof("Retest External Addr: %s \n", extIp.String()) + if c.AutoUPnP { + go c.HandleUPnP(getIP(innerIp), uint16(getPort(extIp))) + } + err = c.Forward.Run() + if err != nil { + return fmt.Errorf("Failed to run forward: %v", err) + } + c.isOk = true + c.localipport = fmt.Sprintf("%s:%d", getIP(innerIp), getPort(innerIp)) + c.ExtUrl = fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp)) + return nil +} + +func (c *NatThrough) HealthCheck() { + count := 0 + if c.HealthCheckInterval == 0 { + c.HealthCheckInterval = 30 + } + for { + select { + case <-c.stopCtx.Done(): + return + case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)): + } + if c.Type == "udp" { + continue + } + conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2) + if err != nil { + starlog.Warningf("Health Check Fail: %v\n", err) + count++ + } else { + starlog.Infof("Health Check Ok\n") + conn.(*net.TCPConn).SetLinger(0) + conn.Close() + } + if count >= 3 { + count = 0 + starlog.Errorf("Failed to connect to remote, close connection retrying\n") + c.stopFn() + c.keepaliveConn.Close() + c.Forward.Close() + forward := netforward.NetForward{ + LocalAddr: c.Forward.LocalAddr, + LocalPort: c.OriginLocalPort, + RemoteURI: c.Forward.RemoteURI, + KeepAlivePeriod: c.KeepAlivePeriod, + KeepAliveIdel: c.KeepAliveIdel, + KeepAliveCount: c.KeepAliveCount, + UsingKeepAlive: true, + } + time.Sleep(time.Second * 22) + c.Forward = forward + c.Run() + } + } +} + +func (c *NatThrough) KeepAlive(localAddr string, localPort int) error { + for { + select { + case <-c.stopCtx.Done(): + return nil + default: + } + if c.Type == "tcp" { + dialer := net.Dialer{ + Control: netforward.ControlSetReUseAddr, + LocalAddr: &net.TCPAddr{IP: net.ParseIP(localAddr), Port: localPort}, + } + conn, err := dialer.Dial("tcp", c.Remote) + if err != nil { + starlog.Errorf("Failed to dial remote: %v\n", err) + time.Sleep(time.Second * 5) + continue + } + c.keepaliveConn = conn + conn.(*net.TCPConn).SetLinger(0) + netforward.SetTcpInfo(conn.(*net.TCPConn), true, c.KeepAliveIdel, c.KeepAlivePeriod, c.KeepAliveCount, 0) + starlog.Infof("Keepalive local:%s remote: %s\n", conn.LocalAddr().String(), conn.RemoteAddr().String()) + go func() { + for { + str := fmt.Sprintf("HEAD /keep-alive HTTP/1.1\r\n"+ + "Host: %s\r\n"+ + "User-Agent: curl/8.0.0 (B612)\r\n"+ + "Accept: */*\r\n"+ + "Connection: keep-alive\r\n\r\n", strings.Split(c.Remote, ":")[0]) + //fmt.Println(str) + if _, err = conn.Write([]byte(str)); err != nil { + fmt.Println(err) + } + time.Sleep(time.Second * 20) + } + }() + for { + _, err := conn.Read(make([]byte, 4096)) + if err != nil { + starlog.Warningf("Failed to keepalive remote: %v\n", err) + conn.Close() + break + } + } + } else if c.Type == "udp" { + rmtUdpAddr, err := net.ResolveUDPAddr("udp", c.Remote) + if err != nil { + return err + } + conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(localAddr), Port: localPort}, rmtUdpAddr) + if err != nil { + starlog.Errorf("Failed to dial remote: %v\n", err) + time.Sleep(time.Second * 5) + continue + } + c.keepaliveConn = conn + for { + _, err = conn.Write([]byte("b612 tcp nat through")) + if err != nil { + conn.Close() + starlog.Warningf("Failed to keepalive remote: %v\n", err) + break + } + time.Sleep(time.Second * 30) + } + } + } +} + +func (c *NatThrough) HandleUPnP(localaddr string, extPort uint16) { + for { + select { + case <-c.stopCtx.Done(): + return + default: + } + client, err := c.FoundUsableUPnP() + if err != nil { + starlog.Errorf("Failed to find UPnP device: %v\n", err) + time.Sleep(time.Second * 20) + continue + } + starlog.Infof("Found UPnP device!\n") + _, _, _, _, _, err = client.GetSpecificPortMappingEntry("", uint16(c.Forward.LocalPort), "TCP") + if err == nil { + starlog.Infof("Port mapping Ok\n") + time.Sleep(time.Second * 20) + continue + } + err = client.AddPortMapping("", uint16(c.Forward.LocalPort), strings.ToUpper(c.Type), uint16(c.Forward.LocalPort), localaddr, true, "B612 TCP Nat PassThrough", 75) + if err != nil { + starlog.Errorf("Failed to add port mapping: %v\n", err) + time.Sleep(time.Second * 20) + continue + } + starlog.Infof("Port mapping added:externalPort %d,localAddr %s,localPort %d\n", extPort, localaddr, c.Forward.LocalPort) + time.Sleep(time.Second * 20) + } +} + +func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort int, stunServer string) (net.Addr, net.Addr, error) { + // 替换为你的 TURN 服务器地址 + stunAddr, err := net.ResolveUDPAddr("udp", stunServer) + if err != nil { + return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err) + } + var conn net.Conn + if netType == "tcp" { + dialer := net.Dialer{ + Control: netforward.ControlSetReUseAddr, + LocalAddr: &net.TCPAddr{IP: net.ParseIP(localip), Port: localPort}, + } + conn, err = dialer.Dial("tcp", stunAddr.String()) + if err != nil { + return nil, nil, err + } + conn.(*net.TCPConn).SetLinger(0) + } + if netType == "udp" { + conn, err = net.DialUDP(netType, &net.UDPAddr{IP: net.ParseIP(localip), Port: localPort}, stunAddr) + if err != nil { + return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err) + } + } + defer conn.Close() + + innerAddr := conn.LocalAddr() + + // Create STUN request + transactionID := make([]byte, 12) + rand.Read(transactionID) + stunRequest := make([]byte, 20) + binary.BigEndian.PutUint16(stunRequest[0:], 0x0001) // Message Type: Binding Request + binary.BigEndian.PutUint16(stunRequest[2:], 0x0000) // Message Length + copy(stunRequest[4:], []byte{0x21, 0x12, 0xa4, 0x42}) // Magic Cookie + copy(stunRequest[8:], transactionID) // Transaction ID + + _, err = conn.Write(stunRequest) + if err != nil { + return nil, nil, fmt.Errorf("failed to send STUN request: %v", err) + } + + buf := make([]byte, 1500) + conn.SetReadDeadline(time.Now().Add(3 * time.Second)) + n, err := conn.Read(buf) + if err != nil { + return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err) + } + + // Parse STUN response + if n < 20 { + return nil, nil, fmt.Errorf("invalid STUN response") + } + + payload := buf[20:n] + var ip uint32 + var port uint16 + for len(payload) > 0 { + attrType := binary.BigEndian.Uint16(payload[0:]) + attrLen := binary.BigEndian.Uint16(payload[2:]) + if len(payload) < int(4+attrLen) { + return nil, nil, fmt.Errorf("invalid STUN attribute length") + } + + if attrType == 0x0001 || attrType == 0x0020 { + port = binary.BigEndian.Uint16(payload[6:]) + ip = binary.BigEndian.Uint32(payload[8:]) + if attrType == 0x0020 { + port ^= 0x2112 + ip ^= 0x2112a442 + } + break + } + payload = payload[4+attrLen:] + } + + if ip == 0 || port == 0 { + return nil, nil, fmt.Errorf("invalid STUN response") + } + + outerAddr := &net.UDPAddr{ + IP: net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)), + Port: int(port), + } + + return innerAddr, outerAddr, nil +} + +func (c *NatThrough) GetMyOutIP() string { + tmp, err := net.Dial("udp", "8.8.8.8:53") + if err != nil { + return "" + } + return tmp.LocalAddr().(*net.UDPAddr).IP.String() +} +func (c *NatThrough) FoundUsableUPnP() (RouterClient, error) { + wg := sync.WaitGroup{} + found := false + result := make(chan RouterClient, 3) + defer close(result) + wg.Add(3) + go func() { + defer wg.Done() + clients, errors, err := internetgateway2.NewWANIPConnection2Clients() + if err != nil { + return + } + if len(errors) > 0 { + return + } + if len(clients) == 0 { + return + } + starlog.Infof("Found WANIPConnection2 clients:%s\n", clients[0].Location.String()) + found = true + + result <- clients[0] + }() + go func() { + defer wg.Done() + clients, errors, err := internetgateway2.NewWANIPConnection1Clients() + if err != nil { + return + } + if len(errors) > 0 { + return + } + if len(clients) == 0 { + return + } + starlog.Infof("Found WANIPConnection1 clients:%s\n", clients[0].Location.String()) + found = true + result <- clients[0] + }() + go func() { + defer wg.Done() + clients, errors, err := internetgateway2.NewWANPPPConnection1Clients() + if err != nil { + return + } + if len(errors) > 0 { + return + } + if len(clients) == 0 { + return + } + starlog.Infof("Found WANPPPConnection1 clients:%s\n", clients[0].Location.String()) + found = true + result <- clients[0] + }() + wg.Wait() + if found { + return <-result, nil + } + return nil, fmt.Errorf("no UPnP devices discovered") +} + +type RouterClient interface { + AddPortMapping( + NewRemoteHost string, + NewExternalPort uint16, + NewProtocol string, + NewInternalPort uint16, + NewInternalClient string, + NewEnabled bool, + NewPortMappingDescription string, + NewLeaseDuration uint32, + ) (err error) + + GetExternalIPAddress() ( + NewExternalIPAddress string, + err error, + ) + + DeletePortMapping(NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) + GetSpecificPortMappingEntry(NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (NewInternalPort uint16, NewInternalClient string, NewEnabled bool, NewPortMappingDescription string, NewLeaseDuration uint32, err error) +} diff --git a/net/natt_test.go b/net/natt_test.go new file mode 100644 index 0000000..3f59611 --- /dev/null +++ b/net/natt_test.go @@ -0,0 +1,50 @@ +package net + +import ( + "b612.me/apps/b612/netforward" + "fmt" + "testing" + "time" +) + +func TestNathrough(t *testing.T) { + var n = NatThrough{ + Forward: netforward.NetForward{ + LocalAddr: "0.0.0.0", + LocalPort: 0, + RemoteURI: "127.0.0.1:88", + EnableTCP: true, + EnableUDP: false, + DelayMilSec: 0, + DelayToward: 0, + StdinMode: false, + IgnoreEof: false, + DialTimeout: 3000, + UDPTimeout: 3000, + KeepAlivePeriod: 30, + KeepAliveIdel: 30, + KeepAliveCount: 5, + UserTimeout: 0, + UsingKeepAlive: true, + }, + Type: "tcp", + STUN: "turn.b612.me:3478", + Remote: "baidu.com:80", + KeepAlivePeriod: 3000, + KeepAliveIdel: 3000, + KeepAliveCount: 5, + AutoUPnP: true, + stopFn: nil, + stopCtx: nil, + } + go func() { + time.Sleep(time.Second * 10) + fmt.Println(n.ExtUrl) + }() + if err := n.Run(); err != nil { + fmt.Println(err) + t.Error(err) + } + n.HealthCheck() + time.Sleep(time.Second * 5) +} diff --git a/net/scan_test.go b/net/scan_test.go new file mode 100644 index 0000000..031b9df --- /dev/null +++ b/net/scan_test.go @@ -0,0 +1,33 @@ +package net + +import ( + "testing" +) + +func TestScan(t *testing.T) { + s := ScanPort{ + Host: "192.168.2.109", + Timeout: 2000, + Threads: 5000, + } + if err := s.Parse("1-65535"); err != nil { + t.Error(err) + } + if err := s.Run(); err != nil { + t.Error(err) + } +} + +func TestScanIP(t *testing.T) { + s := ScanIP{ + Host: "192.168.2.1", + CIDR: 23, + Timeout: 2000, + Threads: 5000, + ScanType: "icmp", + WithHostname: true, + } + if err := s.ICMP(); err != nil { + t.Error(err) + } +} diff --git a/net/scanip.go b/net/scanip.go new file mode 100644 index 0000000..ef473ba --- /dev/null +++ b/net/scanip.go @@ -0,0 +1,279 @@ +package net + +import ( + "b612.me/apps/b612/netforward" + "b612.me/stario" + "b612.me/starlog" + "b612.me/starnet" + "fmt" + "math" + "net" + "sync/atomic" + "time" +) + +type ScanIP struct { + Host string + CIDR int + Port int + Mask string + Threads int + Timeout int + ScanType string + ipNet *net.IPNet + Log string + Retry int + WithHostname bool +} + +func (s *ScanIP) Parse() error { + if s.CIDR == 0 && s.Mask == "" { + return fmt.Errorf("CIDR or Mask must be set") + + } + if s.CIDR != 0 { + return nil + } + //mask to cidr + ipMask := net.IPMask(net.ParseIP(s.Mask).To4()) + if ipMask == nil { + return fmt.Errorf("invalid mask") + } + s.CIDR, _ = ipMask.Size() + return nil +} + +func (s *ScanIP) nextIP(ipStr string) (net.IP, error) { + var err error + if s.ipNet == nil { + _, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR)) + if err != nil { + return nil, fmt.Errorf("invalid CIDR: %v", err) + } + } + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, fmt.Errorf("invalid IP: %v", ipStr) + } + + // Convert IP to 4-byte representation + ip = ip.To4() + if ip == nil { + return nil, fmt.Errorf("non-IPv4 address: %v", ipStr) + } + + // Increment IP + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] > 0 { + break + } + } + + // Check if incremented IP is still in range + if !s.ipNet.Contains(ip) { + return nil, nil + } + + return ip, nil +} + +func (s *ScanIP) NetSize() (int, error) { + var err error + if s.ipNet == nil { + _, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR)) + if err != nil { + return 0, fmt.Errorf("invalid CIDR: %v", err) + } + } + + maskSize, _ := s.ipNet.Mask.Size() + return int(math.Pow(2, float64(32-maskSize))) - 2, nil +} + +func (s *ScanIP) FirstLastIP() (net.IP, net.IP, error) { + var err error + if s.ipNet == nil { + _, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR)) + if err != nil { + return nil, nil, fmt.Errorf("invalid CIDR: %v", err) + } + } + + firstIP := s.ipNet.IP.Mask(s.ipNet.Mask) + lastIP := make(net.IP, len(firstIP)) + copy(lastIP, firstIP) + for i := range firstIP { + lastIP[i] = firstIP[i] | ^s.ipNet.Mask[i] + } + + return firstIP, lastIP, nil +} + +func (s *ScanIP) ICMP() error { + if s.ScanType != "icmp" { + return fmt.Errorf("scan type must be icmp") + } + if err := s.Parse(); err != nil { + return err + } + if s.Log != "" { + starlog.SetLogFile(s.Log, starlog.Std, true) + } + firstIP, lastIP, err := s.FirstLastIP() + if err != nil { + return err + } + ns, _ := s.NetSize() + starlog.Infof("Scan %s/%d\n", s.Host, s.CIDR) + starlog.Infof("Scan %s-%s\n", firstIP.String(), lastIP.String()) + starlog.Infof("There are %d hosts\n", ns) + starlog.Infof("Threads: %d\n", s.Threads) + + wg := stario.NewWaitGroup(s.Threads) + count := int32(0) + allcount := int32(0) + interrupt := make(chan [2]string) + go func() { + for { + select { + case <-time.After(time.Second * 2): + fmt.Printf("scan %d ips, %d up\r", allcount, count) + case ip, opened := <-interrupt: + if !opened { + return + } + if s.WithHostname { + starlog.Infof("Host %v is up, Name:%v\n", ip[0], ip[1]) + } else { + starlog.Infof("Host %v is up\n", ip[0]) + } + } + + } + }() + idx := 0 + for { + ip := firstIP.String() + if ip == lastIP.String() { + break + } + idx++ + wg.Add(1) + go func(ip string, idx int) { + defer func() { + atomic.AddInt32(&allcount, 1) + }() + defer wg.Done() + for i := 0; i < s.Retry+1; i++ { + _, err := starnet.Ping(ip, idx, time.Duration(s.Timeout)*time.Millisecond) + if err == nil { + atomic.AddInt32(&count, 1) + if s.WithHostname { + hostname, err := net.LookupAddr(ip) + if err == nil { + interrupt <- [2]string{ip, hostname[0]} + return + } + } + interrupt <- [2]string{ip, ""} + return + } + } + }(ip, idx) + firstIP, _ = s.nextIP(ip) + } + wg.Wait() + close(interrupt) + starlog.Infof("scan %d ips, %d up\n", ns, count) + return nil +} + +func (s *ScanIP) TCP(port int) error { + if s.ScanType != "tcp" { + return fmt.Errorf("scan type must be tcp") + } + if err := s.Parse(); err != nil { + return err + } + if s.Log != "" { + starlog.SetLogFile(s.Log, starlog.Std, true) + } + firstIP, lastIP, err := s.FirstLastIP() + if err != nil { + return err + } + ns, _ := s.NetSize() + starlog.Infof("Scan %s/%d\n", s.Host, s.CIDR) + starlog.Infof("Scan %s-%s\n", firstIP.String(), lastIP.String()) + starlog.Infof("There are %d hosts\n", ns) + starlog.Infof("Threads: %d\n", s.Threads) + + wg := stario.NewWaitGroup(s.Threads) + count := int32(0) + allcount := int32(0) + interrupt := make(chan [2]string) + go func() { + for { + select { + case <-time.After(time.Second * 2): + fmt.Printf("scan %d ips, %d up\r", allcount, count) + case ip, opened := <-interrupt: + if !opened { + return + } + if s.WithHostname { + starlog.Infof("Host %v is up, Name:%v\n", ip[0], ip[1]) + } else { + starlog.Infof("Host %v is up\n", ip[0]) + } + } + + } + }() + idx := 0 + localAddr, err := net.ResolveTCPAddr("tcp", ":0") + if err != nil { + starlog.Errorln("ResolveTCPAddr error, ", err) + return err + } + for { + ip := firstIP.String() + if ip == lastIP.String() { + break + } + idx++ + wg.Add(1) + go func(ip string, idx int) { + defer func() { + atomic.AddInt32(&allcount, 1) + }() + defer wg.Done() + for i := 0; i < s.Retry+1; i++ { + dialer := net.Dialer{ + LocalAddr: localAddr, + Timeout: time.Duration(s.Timeout) * time.Millisecond, + Control: netforward.ControlSetReUseAddr, + } + _, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, port)) + if err == nil { + atomic.AddInt32(&count, 1) + if s.WithHostname { + hostname, err := net.LookupAddr(ip) + if err == nil { + interrupt <- [2]string{ip, hostname[0]} + return + } + } + interrupt <- [2]string{ip, ""} + return + } + } + }(ip, idx) + firstIP, _ = s.nextIP(ip) + } + wg.Wait() + close(interrupt) + starlog.Infof("scan %d ips, %d up\n", ns, count) + return nil +} diff --git a/net/scanport.go b/net/scanport.go new file mode 100644 index 0000000..1466ada --- /dev/null +++ b/net/scanport.go @@ -0,0 +1,131 @@ +package net + +import ( + "b612.me/apps/b612/netforward" + "b612.me/stario" + "b612.me/starlog" + "fmt" + "net" + "sort" + "strconv" + "strings" + "sync/atomic" + "time" +) + +type ScanPort struct { + Host string + Ports []int + Timeout int + Threads int + Log string + Retry int +} + +func (s *ScanPort) Parse(potStr string) error { + ports := strings.Split(potStr, ",") + for _, port := range ports { + port = strings.TrimSpace(port) + if strings.Contains(port, "-") { + // range + r := strings.Split(port, "-") + if len(r) != 2 { + continue + } + start, err := strconv.Atoi(r[0]) + if err != nil { + starlog.Warningf("invalid port: %s\n", r[0]) + continue + } + end, err := strconv.Atoi(r[1]) + if err != nil { + starlog.Warningf("invalid port: %s\n", r[1]) + continue + } + for i := start; i <= end; i++ { + if i < 1 || i > 65535 { + starlog.Warningf("invalid port: %d\n", i) + continue + } + s.Ports = append(s.Ports, i) + } + } else { + // single port + tmp, err := strconv.Atoi(port) + if err != nil { + starlog.Warningf("invalid port: %s\n", port) + continue + } + if tmp < 1 || tmp > 65535 { + starlog.Warningf("invalid port: %d\n", tmp) + continue + } + s.Ports = append(s.Ports, tmp) + } + } + return nil +} + +func (s *ScanPort) Run() error { + if s.Threads < 1 { + s.Threads = 1 + } + if s.Log != "" { + starlog.SetLogFile(s.Log, starlog.Std, true) + } + sort.Ints(s.Ports) + starlog.Infof("scan count %d ports for host %v\n", len(s.Ports), s.Host) + wg := stario.NewWaitGroup(s.Threads) + localAddr, err := net.ResolveTCPAddr("tcp", ":0") + if err != nil { + starlog.Errorln("ResolveTCPAddr error, ", err) + return err + } + count := int32(0) + allcount := int32(0) + interrupt := make(chan int) + go func() { + for { + select { + case <-time.After(time.Second * 2): + fmt.Printf("scan %d ports, %d open\r", atomic.LoadInt32(&allcount), count) + case port, opened := <-interrupt: + if !opened { + return + } + starlog.Infof("port %d is open\n", port) + } + + } + }() + for _, port := range s.Ports { + wg.Add(1) + go func(port int) { + defer wg.Done() + defer func() { + atomic.AddInt32(&allcount, 1) + }() + for i := 0; i < s.Retry+1; i++ { + dialer := net.Dialer{ + LocalAddr: localAddr, + Timeout: time.Duration(s.Timeout) * time.Millisecond, + Control: netforward.ControlSetReUseAddr, + } + conn, err := dialer.Dial("tcp", net.JoinHostPort(s.Host, strconv.Itoa(port))) + if err != nil { + continue + } + conn.(*net.TCPConn).SetLinger(0) + conn.Close() + interrupt <- port + atomic.AddInt32(&count, 1) + return + } + }(port) + + } + wg.Wait() + close(interrupt) + starlog.Infof("scan %d ports, %d open\n", len(s.Ports), count) + return nil +} diff --git a/net/tcpserver.go b/net/tcpserver.go index 77af3d1..748ebf5 100644 --- a/net/tcpserver.go +++ b/net/tcpserver.go @@ -198,7 +198,7 @@ func (s *TcpServer) Run() error { starlog.Errorln("AcceptTCP error:", err) continue } - starlog.Infof("Accept new connection from %s", conn.RemoteAddr().String()) + starlog.Infof("Accept new connection from %s\n", conn.RemoteAddr().String()) s.Lock() s.Clients[conn.RemoteAddr().String()] = s.getTcpConn(conn) s.Unlock() diff --git a/netforward/forward.go b/netforward/forward.go index 0b41135..3698314 100644 --- a/netforward/forward.go +++ b/netforward/forward.go @@ -12,6 +12,7 @@ import ( "strings" "sync" "sync/atomic" + "syscall" "time" ) @@ -127,7 +128,12 @@ func (n *NetForward) Run() error { 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)) + cfg := net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + return c.Control(SetReUseAddr) + }, + } + listen, err := cfg.Listen(context.Background(), "tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) if err != nil { starlog.Errorln("Listening On Tcp Failed:", err) return err diff --git a/netforward/forward_test.go b/netforward/forward_test.go index 9d4d996..f7b8cfb 100644 --- a/netforward/forward_test.go +++ b/netforward/forward_test.go @@ -28,6 +28,7 @@ func TestForward(t *testing.T) { fmt.Println(f.Status()) continue } - return + break } + time.Sleep(time.Second * 5) } diff --git a/netforward/setcpinfo_darwin.go b/netforward/setcpinfo_darwin.go index 08ee890..3859535 100644 --- a/netforward/setcpinfo_darwin.go +++ b/netforward/setcpinfo_darwin.go @@ -3,6 +3,7 @@ package netforward import ( + "golang.org/x/sys/unix" "net" "syscall" ) @@ -33,3 +34,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive } return err } + +func SetReUseAddr(fd uintptr) { + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) +} + +func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) { + if err := c.Control(func(fd uintptr) { + err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if err != nil { + return + } + + err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + if err != nil { + return + } + }); err != nil { + return err + } + return err +} diff --git a/netforward/setcpinfo_linux.go b/netforward/setcpinfo_linux.go index 0d2f68f..d88ba63 100644 --- a/netforward/setcpinfo_linux.go +++ b/netforward/setcpinfo_linux.go @@ -3,6 +3,7 @@ package netforward import ( + "golang.org/x/sys/unix" "net" "syscall" ) @@ -37,3 +38,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive } return err } + +func SetReUseAddr(fd uintptr) { + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) +} + +func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) { + if err := c.Control(func(fd uintptr) { + err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if err != nil { + return + } + + err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + if err != nil { + return + } + }); err != nil { + return err + } + return err +} diff --git a/netforward/setcpinfo_windows.go b/netforward/setcpinfo_windows.go index eee8098..9588f7a 100644 --- a/netforward/setcpinfo_windows.go +++ b/netforward/setcpinfo_windows.go @@ -31,3 +31,16 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive } return conn.SetKeepAlive(false) } + +func SetReUseAddr(fd uintptr) { + syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) +} + +func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) { + if err := c.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + }); err != nil { + return err + } + return +}