diff --git a/go.mod b/go.mod index d0af76b..f5b23e3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module b612.me/apps/b612 -go 1.19 +go 1.21 + +toolchain go1.21.2 require ( b612.me/notify v1.2.5 diff --git a/net/cmd.go b/net/cmd.go index 9c9cae2..5da128e 100644 --- a/net/cmd.go +++ b/net/cmd.go @@ -17,6 +17,9 @@ func init() { Cmd.AddCommand(netforward.CmdNetforward) } +var nattestc NatTesterClient +var nettests NatTesterServer + var natc NatClient var nats NatServer @@ -28,22 +31,22 @@ var bindAddr string var hideIncorrect bool func init() { - CmdNatClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address") - CmdNatClient.Flags().StringVarP(&natc.CmdTarget, "server", "s", "", "nat server command address") - CmdNatClient.Flags().StringVarP(&natc.Passwd, "passwd", "p", "", "password") - CmdNatClient.Flags().BoolVarP(&natc.enableTCP, "enable-tcp", "T", true, "enable tcp forward") - CmdNatClient.Flags().BoolVarP(&natc.enableUDP, "enable-udp", "U", true, "enable udp forward") - CmdNatClient.Flags().IntVarP(&natc.DialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") - CmdNatClient.Flags().IntVarP(&natc.UdpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") - Cmd.AddCommand(CmdNatClient) + CmdNatPClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address") + CmdNatPClient.Flags().StringVarP(&natc.CmdTarget, "server", "s", "", "nat server command address") + CmdNatPClient.Flags().StringVarP(&natc.Passwd, "passwd", "p", "", "password") + CmdNatPClient.Flags().BoolVarP(&natc.enableTCP, "enable-tcp", "T", true, "enable tcp forward") + CmdNatPClient.Flags().BoolVarP(&natc.enableUDP, "enable-udp", "U", true, "enable udp forward") + CmdNatPClient.Flags().IntVarP(&natc.DialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") + CmdNatPClient.Flags().IntVarP(&natc.UdpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") + Cmd.AddCommand(CmdNatPClient) - CmdNatServer.Flags().StringVarP(&nats.ListenAddr, "listen", "l", "", "listen address") - CmdNatServer.Flags().StringVarP(&nats.Passwd, "passwd", "p", "", "password") - CmdNatServer.Flags().Int64VarP(&nats.UDPTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") - CmdNatServer.Flags().Int64VarP(&nats.NetTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") - CmdNatServer.Flags().BoolVarP(&nats.enableTCP, "enable-tcp", "T", true, "enable tcp forward") - CmdNatServer.Flags().BoolVarP(&nats.enableUDP, "enable-udp", "U", true, "enable udp forward") - Cmd.AddCommand(CmdNatServer) + CmdNatPServer.Flags().StringVarP(&nats.ListenAddr, "listen", "l", "", "listen address") + CmdNatPServer.Flags().StringVarP(&nats.Passwd, "passwd", "p", "", "password") + CmdNatPServer.Flags().Int64VarP(&nats.UDPTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") + CmdNatPServer.Flags().Int64VarP(&nats.NetTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") + CmdNatPServer.Flags().BoolVarP(&nats.enableTCP, "enable-tcp", "T", true, "enable tcp forward") + CmdNatPServer.Flags().BoolVarP(&nats.enableUDP, "enable-udp", "U", true, "enable udp forward") + Cmd.AddCommand(CmdNatPServer) CmdNetTrace.Flags().StringVarP(&dns, "dns", "d", "", "自定义dns服务器") CmdNetTrace.Flags().StringVarP(&ipinfoaddr, "ipinfo", "i", "https://ip.b612.me/{ip}/detail", "自定义ip信息查询地址") @@ -54,9 +57,13 @@ func init() { CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点") Cmd.AddCommand(CmdNetTrace, cmdSSHJar) + CmdNatClient.Flags().IntVarP(&nattestc.RetryTime, "retry", "r", 2, "重试次数") + CmdNatClient.Flags().IntVarP(&nattestc.Timeout, "timeout", "t", 2, "超时时间") + CmdNatClient.Flags().StringSliceVarP(&nattestc.dns, "dns", "d", nil, "自定义dns服务器") + Cmd.AddCommand(CmdNatClient) } -var CmdNatClient = &cobra.Command{ +var CmdNatPClient = &cobra.Command{ Use: "natpc", Short: "nat穿透客户端", Run: func(cmd *cobra.Command, args []string) { @@ -68,7 +75,7 @@ var CmdNatClient = &cobra.Command{ }, } -var CmdNatServer = &cobra.Command{ +var CmdNatPServer = &cobra.Command{ Use: "natps", Short: "nat穿透服务端", Run: func(cmd *cobra.Command, args []string) { @@ -77,6 +84,34 @@ var CmdNatServer = &cobra.Command{ }, } +var CmdNatClient = &cobra.Command{ + Use: "natc", + Short: "nat类型测试工具", + Long: "基于RFC3489的nat类型测试工具", + Run: func(cmd *cobra.Command, args []string) { + if nattestc.dns != nil { + UseCustomeDNS(nattestc.dns) + for _, v := range nattestc.dns { + starlog.Infoln("使用自定义DNS:", v) + } + } + natserver := "nat.b612.me" + if len(args) == 0 { + starlog.Infoln("使用默认NAT测试服务器:nat.b612.me") + } else { + natserver = args[0] + starlog.Infoln("使用NAT测试服务器:", natserver) + } + res, err := nattestc.ServeAndRun(natserver) + if err != nil { + starlog.Errorln("测试NAT类型失败", err) + return + } + fmt.Println("-----------------\n") + starlog.Green("您的NAT类型为%v - %v\nswitch可能显示NAT类型为%v\n%v", res.Code, res.RFC3489, res.NintendoSwitch, res.Desc) + }, +} + var CmdNetTrace = &cobra.Command{ Use: "trace", Short: "网络路径追踪", diff --git a/net/natc.go b/net/natc.go new file mode 100644 index 0000000..c5398a5 --- /dev/null +++ b/net/natc.go @@ -0,0 +1,274 @@ +package net + +import ( + "b612.me/starlog" + "context" + "encoding/json" + "fmt" + "net" + "strings" + "time" +) + +type NatTesterClient struct { + MainPort string `json:"mainport"` + AltPort string `json:"altport"` + MainIP string `json:"mainip"` + AltIP string `json:"altip"` + RetryTime int `json:"retrytime"` + Timeout int `json:"timeout"` + dns []string + ch chan Message +} + +type Message struct { + Success bool + Cmd string + Msg string + Address string +} + +type NatType struct { + Code string + RFC3489 string + NintendoSwitch string + Desc string +} + +func (n *NatTesterClient) GetMsg() Message { + select { + case msg := <-n.ch: + return msg + case <-time.After(time.Second * time.Duration(n.Timeout)): + return Message{Success: false, Msg: "timeout"} + } +} + +func (n *NatTesterClient) RecvMsg(c *net.UDPConn) { + for { + buf := make([]byte, 1024) + num, r, e := c.ReadFromUDP(buf) + if e != nil { + return + } + go n.Analyse(r, strings.Split(string(buf[:num]), "::")) + } +} + +func (n *NatTesterClient) Analyse(r *net.UDPAddr, cmds []string) { + switch cmds[0] { + case "ip": + if len(cmds) == 2 { + n.ch <- Message{Success: true, Cmd: "ip", Msg: cmds[1], Address: r.String()} + } + case "stage1": + n.ch <- Message{Success: true, Cmd: "stage1", Msg: "stage1", Address: r.String()} + case "stage2": + n.ch <- Message{Success: true, Cmd: "stage2", Msg: "stage2", Address: r.String()} + case "stage3": + n.ch <- Message{Success: true, Cmd: "stage3", Msg: "stage3", Address: r.String()} + } +} + +func (n *NatTesterClient) Run() (NatType, error) { + var firstAddr, secondAddr string + var natTypeMap = map[string]NatType{ + "Block": {Code: "Block", RFC3489: "Block", NintendoSwitch: "NAT F", Desc: "您的网络似乎禁止了UDP,无法连接到外部网络"}, + "Open": {Code: "Open", RFC3489: "Open", NintendoSwitch: "Open", Desc: "您的网络为开放网络,拥有最好的上网体验"}, + "OpenBlock": {Code: "OpenBlock", RFC3489: "Symmetric Firewall", NintendoSwitch: "NAT F", Desc: "您的网络虽然时开放网络,但是存在对称型防火墙,可能会遇到严重的连接问题"}, + "NAT1": {Code: "NAT1", RFC3489: "Full Cone", NintendoSwitch: "NAT A", Desc: "您的NAT类型为全锥形NAT,拥有最好的NAT体验"}, + "NAT2": {Code: "NAT2", RFC3489: "Address Restricted Cone", NintendoSwitch: "NAT B", Desc: "您的NAT类型为地址限制锥形NAT,拥有良好的NAT体验"}, + "NAT3": {Code: "NAT3", RFC3489: "Port Restricted Cone", NintendoSwitch: "NAT B/C", Desc: "您的NAT类型为端口限制锥形NAT,可能会遇到一些连接问题"}, + "NAT4": {Code: "NAT4", RFC3489: "Symmetric", NintendoSwitch: "NAT C/D", Desc: "您的NAT类型为对称NAT,可能会遇到严重的连接问题"}, + "Unknown": {Code: "Unknown", RFC3489: "Unknown", NintendoSwitch: "Unknown", Desc: "无法确定您的NAT类型"}, + } + tmp, err := net.Dial("udp", n.MainIP+":80") + if err != nil { + return NatType{}, err + } + curIp := tmp.LocalAddr().(*net.UDPAddr).IP.String() + starlog.Infof("Current Output IP: %s\n", curIp) + localAddr, err := net.ResolveUDPAddr("udp", curIp+":0") + if err != nil { + return NatType{}, err + } + conn, err := net.ListenUDP("udp", localAddr) + if err != nil { + return NatType{}, err + } + starlog.Infof("Listening on %s\n", conn.LocalAddr().String()) + defer conn.Close() + go n.RecvMsg(conn) + n.ch = make(chan Message) + defer close(n.ch) + succ := false + mainAddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.MainPort) + if err != nil { + return NatType{}, err + } + altAddr, err := net.ResolveUDPAddr("udp", n.AltIP+":"+n.AltPort) + if err != nil { + return NatType{}, err + } + starlog.Noticef("Getting IP from NatServer Main\n") + for i := 0; i < n.RetryTime; i++ { + _, err = conn.WriteToUDP([]byte("ip"), mainAddr) + if err != nil { + starlog.Errorln("failed to get main ip,retrying:" + err.Error()) + continue + } + msg := n.GetMsg() + if msg.Success && msg.Cmd == "ip" { + starlog.Noticef("Remote IP: %s\n", msg.Address) + starlog.Infof("Current IP: %s\n", msg.Msg) + succ = true + firstAddr = msg.Msg + break + } + starlog.Errorln("failed to get main ip,retrying:" + msg.Msg) + } + if !succ { + return NatType{}, fmt.Errorf("failed to get current ip") + } + { + starlog.Noticef("Start NAT1 Test\n") + succ = false + for i := 0; i < n.RetryTime; i++ { + _, err = conn.WriteToUDP([]byte("startnat1"), mainAddr) + if err != nil { + starlog.Errorln("failed to send nat1 test data,retrying:" + err.Error()) + continue + } + msg := n.GetMsg() + if msg.Success && msg.Cmd == "stage1" { + starlog.Noticef("Recv Nat1 Data From Remote IP: %s\n", msg.Address) + succ = true + break + } + starlog.Errorln("failed to recv Nat1 data,retrying:" + msg.Msg) + } + if succ { + if strings.Split(firstAddr, ":")[0] == curIp { + starlog.Infof("Current NAT Type: Open\n") + return natTypeMap["Open"], nil + } + starlog.Infof("Current NAT Type: NAT1\n") + return natTypeMap["NAT1"], nil + } else { + if strings.Split(firstAddr, ":")[0] == curIp { + starlog.Infof("Current NAT Type: OpenBlock\n") + return natTypeMap["OpenBlock"], nil + } + } + } + { + starlog.Noticef("Start NAT2 Test\n") + succ = false + for i := 0; i < n.RetryTime; i++ { + _, err = conn.WriteToUDP([]byte("startnat2"), mainAddr) + if err != nil { + starlog.Errorln("failed to send nat2 test data,retrying:" + err.Error()) + continue + } + msg := n.GetMsg() + if msg.Success && msg.Cmd == "stage2" { + starlog.Noticef("Recv Nat2 Data From Remote IP: %s\n", msg.Address) + succ = true + break + } + starlog.Errorln("failed to recv Nat2 data,retrying:" + msg.Msg) + } + if succ { + starlog.Infof("Current NAT Type: NAT2\n") + return natTypeMap["NAT2"], nil + } + } + { + starlog.Noticef("Start NAT3 Test\n") + succ = false + for i := 0; i < n.RetryTime; i++ { + _, err = conn.WriteToUDP([]byte("startnat3"), mainAddr) + if err != nil { + starlog.Errorln("failed to send nat1 test data,retrying:" + err.Error()) + continue + } + msg := n.GetMsg() + if msg.Success && msg.Cmd == "stage3" { + starlog.Noticef("Recv Nat1 Data From Remote IP: %s\n", msg.Address) + succ = true + break + } + starlog.Errorln("failed to recv Nat3 data,retrying:" + msg.Msg) + } + if !succ { + starlog.Errorf("Failed to get NAT Type\n") + return natTypeMap["Unknown"], fmt.Errorf("failed to get nat type") + } + } + succ = false + starlog.Noticef("Gettting IP from NatServer Alt\n") + for i := 0; i < n.RetryTime; i++ { + _, err = conn.WriteToUDP([]byte("ip"), altAddr) + if err != nil { + starlog.Errorln("failed to get alt ip,retrying:" + err.Error()) + continue + } + msg := n.GetMsg() + if msg.Success && msg.Cmd == "ip" { + starlog.Noticef("Remote IP: %s\n", msg.Address) + starlog.Infof("Current IP: %s\n", msg.Msg) + succ = true + secondAddr = msg.Msg + break + } + starlog.Errorln("failed to get alt ip,retrying:" + msg.Msg) + } + if !succ { + starlog.Errorf("Failed to get NAT Type\n") + return natTypeMap["Unknown"], fmt.Errorf("failed to get nat type") + } + starlog.Debugf("First IP: %s, Second IP: %s\n", firstAddr, secondAddr) + starlog.Debugf("Listening on %s\n", conn.LocalAddr().String()) + if firstAddr == secondAddr { + starlog.Infof("Current NAT Type: NAT3\n") + return natTypeMap["NAT3"], nil + } + starlog.Infof("Current NAT Type: NAT4\n") + return natTypeMap["NAT4"], nil +} + +func (n *NatTesterClient) ServeAndRun(addr string) (NatType, error) { + starlog.SetShowFlag(false) + starlog.SetShowFuncName(false) + starlog.SetShowOriginFile(false) + data, err := net.LookupTXT(addr) + if err != nil { + return NatType{}, err + } + if len(data) == 0 { + return NatType{}, fmt.Errorf("no data found") + } + err = json.Unmarshal([]byte(data[0]), n) + if err != nil { + return NatType{}, err + } + starlog.Debugf("MainIP: %s, MainPort: %s, AltIP: %s, AltPort: %s\n", n.MainIP, n.MainPort, n.AltIP, n.AltPort) + return n.Run() +} + +func UseCustomeDNS(dns []string) { + resolver := net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) { + for _, addr := range dns { + if conn, err = net.Dial("udp", addr+":53"); err != nil { + continue + } else { + return conn, nil + } + } + return + }, + } + net.DefaultResolver = &resolver +} diff --git a/net/nats.go b/net/nats.go new file mode 100644 index 0000000..9fbd5c2 --- /dev/null +++ b/net/nats.go @@ -0,0 +1,143 @@ +package net + +import ( + "b612.me/starlog" + "context" + "fmt" + "net" + "strings" + "sync/atomic" +) + +type NatTesterServer struct { + MainPort string + AltPort string + MainIP string + AltIP string + LogPath string + stopCtx context.Context + stopFn context.CancelFunc + maina *net.UDPConn + mainb *net.UDPConn + alt *net.UDPConn + running int32 +} + +func (n *NatTesterServer) Run() error { + if atomic.LoadInt32(&n.running) > 0 { + starlog.Errorln("already running") + return fmt.Errorf("already running") + } + atomic.StoreInt32(&n.running, 1) + defer atomic.StoreInt32(&n.running, 0) + if n.LogPath != "" { + starlog.SetLogFile(n.LogPath, starlog.Std, true) + starlog.Infof("Log file set to %s\n", n.LogPath) + } + starlog.Infof("MainPort: %s\n", n.MainPort) + starlog.Infof("AltPort: %s\n", n.AltPort) + tmp, err := net.Dial("udp", "8.8.8.8:53") + if err != nil { + return err + } + starlog.Infof("Current Output IP: %s\n", tmp.LocalAddr().(*net.UDPAddr).IP.String()) + tmp.Close() + n.stopCtx, n.stopFn = context.WithCancel(context.Background()) + mainaaddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.MainPort) + if err != nil { + return err + } + mainbaddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.AltPort) + if err != nil { + return err + } + n.maina, err = net.ListenUDP("udp", mainaaddr) + if err != nil { + return err + } + starlog.Infof("UDP MainIP:MainPort Listening on %s\n", n.maina.LocalAddr().String()) + n.mainb, err = net.ListenUDP("udp", mainbaddr) + if err != nil { + return err + } + starlog.Infof("UDP MainIP:AltPort Listening on %s\n", n.mainb.LocalAddr().String()) + altaddr, err := net.ResolveUDPAddr("udp", n.AltIP+":"+n.AltPort) + if err != nil { + return err + } + n.alt, err = net.ListenUDP("udp", altaddr) + if err != nil { + return err + } + starlog.Infof("UDP AltIP:AltPort Listening on %s\n", n.alt.LocalAddr().String()) + go func() { + for { + select { + case <-n.stopCtx.Done(): + starlog.Infoln("Stopping,Reason: Context Done") + return + default: + } + buf := make([]byte, 1024) + num, r, e := n.alt.ReadFromUDP(buf) + if e != nil { + continue + } + go n.Analyse(n.alt, r, strings.Split(string(buf[:num]), "::")) + } + }() + for { + select { + case <-n.stopCtx.Done(): + starlog.Infoln("Stopping,Reason: Context Done") + n.maina.Close() + n.mainb.Close() + n.alt.Close() + return nil + default: + } + buf := make([]byte, 1024) + num, r, e := n.maina.ReadFromUDP(buf) + if e != nil { + continue + } + go n.Analyse(n.maina, r, strings.Split(string(buf[:num]), "::")) + } +} + +func (n *NatTesterServer) Analyse(c *net.UDPConn, r *net.UDPAddr, cmds []string) error { + switch cmds[0] { + case "ip": + c.WriteToUDP([]byte("ip::"+r.String()), r) + starlog.Infof("Recv IP Request from %s,Local: %s\n", r.String(), c.LocalAddr().String()) + case "startnat1": + n.alt.WriteToUDP([]byte("stage1"), r) + starlog.Infof("Start NAT1 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.alt.LocalAddr().String()) + case "stage1recv": + n.maina.WriteToUDP([]byte("result::nat1"), r) + starlog.Infof("Recv NAT1 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.maina.LocalAddr().String()) + case "startnat2": + n.mainb.WriteToUDP([]byte("stage2"), r) + starlog.Infof("Start NAT2 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.mainb.LocalAddr().String()) + case "stage2recv": + n.maina.WriteToUDP([]byte("result::nat2"), r) + starlog.Infof("Recv NAT2 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.maina.LocalAddr().String()) + case "startnat3": + n.maina.WriteToUDP([]byte("stage3"), r) + starlog.Infof("Start NAT3 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.maina.LocalAddr().String()) + case "stage3recv": + n.maina.WriteToUDP([]byte("result::nat3"), r) + starlog.Infof("Recv NAT3 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.maina.LocalAddr().String()) + } + return nil +} + +func main() { + server := NatTesterServer{ + MainIP: "10.0.0.5", + AltIP: "10.0.0.2", + MainPort: "41127", + AltPort: "46610", + } + fmt.Println(server.Run()) +}