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 }