diff --git a/FyneApp.toml b/FyneApp.toml index d95b8ec..f2f84d2 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -4,5 +4,5 @@ Website = "https://goodlink.kony.vip" Icon = "theme/favicon.png" Name = "goodlink-windows-amd64-ui" ID = "goodlink.kony.vip" - Version = "1.14.25" + Version = "1.14.26" Build = 0 diff --git a/config/args.go b/config/args.go index b8c9879..3e56e85 100644 --- a/config/args.go +++ b/config/args.go @@ -38,8 +38,8 @@ func Help() { flag.IntVar(&Arg_p2p_timeout, "time_out", 30, "最大连接超时, 单位: 秒") flag.IntVar(&Arg_conn_type, "conn", 0, "若超过10分钟无法连接, 可尝试更换连接方式: 0: 主动; 1: 被动") - flag.IntVar(&Arg_conn_n0, "n0", 64, "dev n0") - flag.IntVar(&Arg_conn_n1, "n1", 16, "dev n1") + flag.IntVar(&Arg_conn_n0, "n0", 256, "dev n0") + flag.IntVar(&Arg_conn_n1, "n1", 1, "dev n1") /* 没有用到的参数 */ flag.Bool("fork", false, "子进程") diff --git a/pro/comm.go b/pro/comm.go index 3f3af4d..5414b9f 100644 --- a/pro/comm.go +++ b/pro/comm.go @@ -41,13 +41,13 @@ func Init(m_cli_redis_addr, m_cli_redis_pass string, m_cli_redis_id int) error { } m_redis_db = redis.NewClient(&redis.Options{ - Addr: m_cli_redis_addr, - Password: m_cli_redis_pass, - DB: m_cli_redis_id, - MaxRetries: 99, - DialTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, + Addr: m_cli_redis_addr, + Password: m_cli_redis_pass, + DB: m_cli_redis_id, + //MaxRetries: 99, + DialTimeout: 5 * time.Second, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, }) if m_redis_db == nil { return errors.New("Redis失败, 请重启程序") diff --git a/pro/local.go b/pro/local.go index 93646ca..bbd615a 100644 --- a/pro/local.go +++ b/pro/local.go @@ -80,12 +80,13 @@ func GetLocalQuicConn(conn_type int, count int) (quic.Connection, quic.Stream, e } m_tun_passive = tun.CteateTunPassive(conn, redisJson.ServerIP, redisJson.ServerPort1, redisJson.ServerPort2, redisJson.SendPortCount) - m_tun_passive.Start() redisJson.State = 2 gogo.Log().DebugF("%d: 发送本端地址: %v", redisJson.State, redisJson) RedisSet(redisJson.RedisTimeOut, &redisJson) + m_tun_passive.Start() + default: if m_tun_active != nil { m_tun_active.Release() diff --git a/pro/remote.go b/pro/remote.go index f3903ec..66f3acb 100644 --- a/pro/remote.go +++ b/pro/remote.go @@ -99,13 +99,15 @@ func GetRemoteQuicConn(time_out time.Duration) (quic.Connection, quic.Stream) { m_tun_active = nil m_tun_passive = tun.CteateTunPassive(conn, redisJson.ClientIP, redisJson.ClientPort1, redisJson.ClientPort2, 0x100) - m_tun_passive.Start() tun_passive_chain = m_tun_passive.GetChain() redisJson.State = 1 gogo.Log().DebugF("%d: 发送本端地址: %v", redisJson.State, redisJson) RedisSet(redisJson.RedisTimeOut, &redisJson) + + m_tun_passive.Start() + } case 2: diff --git a/upnp/AddPortMapping.go b/upnp/AddPortMapping.go new file mode 100644 index 0000000..6ddef7f --- /dev/null +++ b/upnp/AddPortMapping.go @@ -0,0 +1,72 @@ +package upnp + +import ( + // "log" + // "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" +) + +type AddPortMapping struct { + upnp *Upnp +} + +func (this *AddPortMapping) Send(localPort, remotePort int, protocol string) bool { + request := this.buildRequest(localPort, remotePort, protocol) + response, _ := http.DefaultClient.Do(request) + resultBody, _ := ioutil.ReadAll(response.Body) + if response.StatusCode == 200 { + this.resolve(string(resultBody)) + return true + } + return false +} +func (this *AddPortMapping) buildRequest(localPort, remotePort int, protocol string) *http.Request { + //请求头 + header := http.Header{} + header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") + header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"`) + header.Set("Content-Type", "text/xml") + header.Set("Connection", "Close") + header.Set("Content-Length", "") + //请求体 + body := Node{Name: "SOAP-ENV:Envelope", + Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`, + "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}} + childOne := Node{Name: `SOAP-ENV:Body`} + childTwo := Node{Name: `m:AddPortMapping`, + Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}} + + childList1 := Node{Name: "NewExternalPort", Content: strconv.Itoa(remotePort)} + childList2 := Node{Name: "NewInternalPort", Content: strconv.Itoa(localPort)} + childList3 := Node{Name: "NewProtocol", Content: protocol} + childList4 := Node{Name: "NewEnabled", Content: "1"} + childList5 := Node{Name: "NewInternalClient", Content: this.upnp.LocalHost} + childList6 := Node{Name: "NewLeaseDuration", Content: "0"} + childList7 := Node{Name: "NewPortMappingDescription", Content: "mandela"} + childList8 := Node{Name: "NewRemoteHost"} + childTwo.AddChild(childList1) + childTwo.AddChild(childList2) + childTwo.AddChild(childList3) + childTwo.AddChild(childList4) + childTwo.AddChild(childList5) + childTwo.AddChild(childList6) + childTwo.AddChild(childList7) + childTwo.AddChild(childList8) + + childOne.AddChild(childTwo) + body.AddChild(childOne) + bodyStr := body.BuildXML() + + //请求 + request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl, + strings.NewReader(bodyStr)) + request.Header = header + request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr)))) + return request +} + +func (this *AddPortMapping) resolve(resultStr string) { +} diff --git a/upnp/DelPortMapping.go b/upnp/DelPortMapping.go new file mode 100644 index 0000000..3cbd61f --- /dev/null +++ b/upnp/DelPortMapping.go @@ -0,0 +1,61 @@ +package upnp + +import ( + "io/ioutil" + // "log" + "net/http" + "strconv" + "strings" +) + +type DelPortMapping struct { + upnp *Upnp +} + +func (this *DelPortMapping) Send(remotePort int, protocol string) bool { + request := this.buildRequest(remotePort, protocol) + response, _ := http.DefaultClient.Do(request) + resultBody, _ := ioutil.ReadAll(response.Body) + if response.StatusCode == 200 { + // log.Println(string(resultBody)) + this.resolve(string(resultBody)) + return true + } + return false +} +func (this *DelPortMapping) buildRequest(remotePort int, protocol string) *http.Request { + //请求头 + header := http.Header{} + header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") + header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"`) + header.Set("Content-Type", "text/xml") + header.Set("Connection", "Close") + header.Set("Content-Length", "") + + //请求体 + body := Node{Name: "SOAP-ENV:Envelope", + Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`, + "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}} + childOne := Node{Name: `SOAP-ENV:Body`} + childTwo := Node{Name: `m:DeletePortMapping`, + Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}} + childList1 := Node{Name: "NewExternalPort", Content: strconv.Itoa(remotePort)} + childList2 := Node{Name: "NewProtocol", Content: protocol} + childList3 := Node{Name: "NewRemoteHost"} + childTwo.AddChild(childList1) + childTwo.AddChild(childList2) + childTwo.AddChild(childList3) + childOne.AddChild(childTwo) + body.AddChild(childOne) + bodyStr := body.BuildXML() + + //请求 + request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl, + strings.NewReader(bodyStr)) + request.Header = header + request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr)))) + return request +} + +func (this *DelPortMapping) resolve(resultStr string) { +} diff --git a/upnp/DeviceDesc.go b/upnp/DeviceDesc.go new file mode 100644 index 0000000..6ad10b7 --- /dev/null +++ b/upnp/DeviceDesc.go @@ -0,0 +1,97 @@ +package upnp + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + "strings" +) + +type DeviceDesc struct { + upnp *Upnp +} + +func (this *DeviceDesc) Send() bool { + request := this.BuildRequest() + response, _ := http.DefaultClient.Do(request) + resultBody, _ := ioutil.ReadAll(response.Body) + if response.StatusCode == 200 { + this.resolve(string(resultBody)) + return true + } + return false +} +func (this *DeviceDesc) BuildRequest() *http.Request { + //请求头 + header := http.Header{} + header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") + header.Set("User-Agent", "preston") + header.Set("Host", this.upnp.Gateway.Host) + header.Set("Connection", "keep-alive") + + //请求 + request, _ := http.NewRequest("GET", "http://"+this.upnp.Gateway.Host+this.upnp.Gateway.DeviceDescUrl, nil) + request.Header = header + // request := http.Request{Method: "GET", Proto: "HTTP/1.1", + // Host: this.upnp.Gateway.Host, Url: this.upnp.Gateway.DeviceDescUrl, Header: header} + return request +} + +func (this *DeviceDesc) resolve(resultStr string) { + inputReader := strings.NewReader(resultStr) + + // 从文件读取,如可以如下: + // content, err := ioutil.ReadFile("studygolang.xml") + // decoder := xml.NewDecoder(bytes.NewBuffer(content)) + + lastLabel := "" + + ISUpnpServer := false + + IScontrolURL := false + var controlURL string //`controlURL` + // var eventSubURL string //`eventSubURL` + // var SCPDURL string //`SCPDURL` + + decoder := xml.NewDecoder(inputReader) + for t, err := decoder.Token(); err == nil && !IScontrolURL; t, err = decoder.Token() { + switch token := t.(type) { + // 处理元素开始(标签) + case xml.StartElement: + if ISUpnpServer { + name := token.Name.Local + lastLabel = name + } + + // 处理元素结束(标签) + case xml.EndElement: + // log.Println("结束标记:", token.Name.Local) + // 处理字符数据(这里就是元素的文本) + case xml.CharData: + //得到url后其他标记就不处理了 + content := string([]byte(token)) + + //找到提供端口映射的服务 + if content == this.upnp.Gateway.ServiceType { + ISUpnpServer = true + continue + } + //urn:upnp-org:serviceId:WANIPConnection + if ISUpnpServer { + switch lastLabel { + case "controlURL": + + controlURL = content + IScontrolURL = true + case "eventSubURL": + // eventSubURL = content + case "SCPDURL": + // SCPDURL = content + } + } + default: + // ... + } + } + this.upnp.CtrlUrl = controlURL +} diff --git a/upnp/DeviceStatusInfo.go b/upnp/DeviceStatusInfo.go new file mode 100644 index 0000000..88ca831 --- /dev/null +++ b/upnp/DeviceStatusInfo.go @@ -0,0 +1,45 @@ +package upnp + +import ( + // "log" + // "io/ioutil" + "net/http" + "strconv" + "strings" +) + +type SearchGatewayReq struct { + host string + resultBody string + ctrlUrl string + upnp *Upnp +} + +func (this SearchGatewayReq) Send() { + // request := this.BuildRequest() +} +func (this SearchGatewayReq) BuildRequest() *http.Request { + //请求头 + header := http.Header{} + header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") + header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"`) + header.Set("Content-Type", "text/xml") + header.Set("Connection", "Close") + header.Set("Content-Length", "") + //请求体 + body := Node{Name: "SOAP-ENV:Envelope", + Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`, + "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}} + childOne := Node{Name: `SOAP-ENV:Body`} + childTwo := Node{Name: `m:GetStatusInfo`, + Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}} + childOne.AddChild(childTwo) + body.AddChild(childOne) + bodyStr := body.BuildXML() + //请求 + request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl, + strings.NewReader(bodyStr)) + request.Header = header + request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr)))) + return request +} diff --git a/upnp/ExternalIPAddress.go b/upnp/ExternalIPAddress.go new file mode 100644 index 0000000..5bbc09c --- /dev/null +++ b/upnp/ExternalIPAddress.go @@ -0,0 +1,78 @@ +package upnp + +import ( + "encoding/xml" + "io/ioutil" + // "log" + "net/http" + "strconv" + "strings" +) + +type ExternalIPAddress struct { + upnp *Upnp +} + +func (this *ExternalIPAddress) Send() bool { + request := this.BuildRequest() + response, _ := http.DefaultClient.Do(request) + resultBody, _ := ioutil.ReadAll(response.Body) + if response.StatusCode == 200 { + this.resolve(string(resultBody)) + return true + } + return false +} +func (this *ExternalIPAddress) BuildRequest() *http.Request { + //请求头 + header := http.Header{} + header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") + header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"`) + header.Set("Content-Type", "text/xml") + header.Set("Connection", "Close") + header.Set("Content-Length", "") + //请求体 + body := Node{Name: "SOAP-ENV:Envelope", + Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`, + "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}} + childOne := Node{Name: `SOAP-ENV:Body`} + childTwo := Node{Name: `m:GetExternalIPAddress`, + Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}} + childOne.AddChild(childTwo) + body.AddChild(childOne) + + bodyStr := body.BuildXML() + //请求 + request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl, + strings.NewReader(bodyStr)) + request.Header = header + request.Header.Set("Content-Length", strconv.Itoa(len([]byte(body.BuildXML())))) + return request +} + +//NewExternalIPAddress +func (this *ExternalIPAddress) resolve(resultStr string) { + inputReader := strings.NewReader(resultStr) + decoder := xml.NewDecoder(inputReader) + ISexternalIP := false + for t, err := decoder.Token(); err == nil; t, err = decoder.Token() { + switch token := t.(type) { + // 处理元素开始(标签) + case xml.StartElement: + name := token.Name.Local + if name == "NewExternalIPAddress" { + ISexternalIP = true + } + // 处理元素结束(标签) + case xml.EndElement: + // 处理字符数据(这里就是元素的文本) + case xml.CharData: + if ISexternalIP == true { + this.upnp.GatewayOutsideIP = string([]byte(token)) + return + } + default: + // ... + } + } +} diff --git a/upnp/SearchGatewayMsg.go b/upnp/SearchGatewayMsg.go new file mode 100644 index 0000000..b9e2702 --- /dev/null +++ b/upnp/SearchGatewayMsg.go @@ -0,0 +1,121 @@ +package upnp + +import ( + "log" + "net" + "strings" + "time" + // "net/http" +) + +type Gateway struct { + GatewayName string //网关名称 + Host string //网关ip和端口 + DeviceDescUrl string //网关设备描述路径 + Cache string //cache + ST string + USN string + deviceType string //设备的urn "urn:schemas-upnp-org:service:WANIPConnection:1" + ControlURL string //设备端口映射请求路径 + ServiceType string //提供upnp服务的服务类型 +} + +type SearchGateway struct { + searchMessage string + upnp *Upnp +} + +func (this *SearchGateway) Send() bool { + this.buildRequest() + c := make(chan string) + go this.send(c) + result := <-c + if result == "" { + //超时了 + this.upnp.Active = false + return false + } + this.resolve(result) + + this.upnp.Gateway.ServiceType = "urn:schemas-upnp-org:service:WANIPConnection:1" + this.upnp.Active = true + return true +} +func (this *SearchGateway) send(c chan string) { + //发送组播消息,要带上端口,格式如:"239.255.255.250:1900" + var conn *net.UDPConn + defer func() { + if r := recover(); r != nil { + //超时了 + } + }() + go func(conn *net.UDPConn) { + defer func() { + if r := recover(); r != nil { + //没超时 + } + }() + //超时时间为3秒 + time.Sleep(time.Second * 3) + c <- "" + conn.Close() + }(conn) + remotAddr, err := net.ResolveUDPAddr("udp", "239.255.255.250:1900") + if err != nil { + log.Println("组播地址格式不正确") + } + locaAddr, err := net.ResolveUDPAddr("udp", this.upnp.LocalHost+":") + + if err != nil { + log.Println("本地ip地址格式不正确") + } + conn, err = net.ListenUDP("udp", locaAddr) + defer conn.Close() + if err != nil { + log.Println("监听udp出错") + } + _, err = conn.WriteToUDP([]byte(this.searchMessage), remotAddr) + if err != nil { + log.Println("发送msg到组播地址出错") + } + buf := make([]byte, 1024) + n, _, err := conn.ReadFromUDP(buf) + if err != nil { + log.Println("从组播地址接搜消息出错") + } + + result := string(buf[:n]) + c <- result +} +func (this *SearchGateway) buildRequest() { + this.searchMessage = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n" + + "MAN: \"ssdp:discover\"\r\n" + "MX: 3\r\n\r\n" +} + +func (this *SearchGateway) resolve(result string) { + this.upnp.Gateway = &Gateway{} + + lines := strings.Split(result, "\r\n") + for _, line := range lines { + //按照第一个冒号分为两个字符串 + nameValues := strings.SplitAfterN(line, ":", 2) + if len(nameValues) < 2 { + continue + } + switch strings.ToUpper(strings.Trim(strings.Split(nameValues[0], ":")[0], " ")) { + case "ST": + this.upnp.Gateway.ST = nameValues[1] + case "CACHE-CONTROL": + this.upnp.Gateway.Cache = nameValues[1] + case "LOCATION": + urls := strings.Split(strings.Split(nameValues[1], "//")[1], "/") + this.upnp.Gateway.Host = urls[0] + this.upnp.Gateway.DeviceDescUrl = "/" + urls[1] + case "SERVER": + this.upnp.Gateway.GatewayName = nameValues[1] + default: + } + } +} diff --git a/upnp/common.go b/upnp/common.go new file mode 100644 index 0000000..77974c7 --- /dev/null +++ b/upnp/common.go @@ -0,0 +1,51 @@ +package upnp + +import ( + // "log" + "errors" + "log" + "net" + "strings" +) + +// 获取本机能联网的ip地址 +func GetLocalIntenetIp() string { + /* + 获得所有本机地址 + 判断能联网的ip地址 + */ + + conn, err := net.Dial("udp4", "124.223.50.150:13478") + if err != nil { + panic(errors.New("不能连接网络")) + } + defer conn.Close() + log.Println(conn.LocalAddr().String()) + log.Println(strings.Split(conn.LocalAddr().String(), ":")[0]) + return strings.Split(conn.LocalAddr().String(), ":")[0] +} + +// This returns the list of local ip addresses which other hosts can connect +// to (NOTE: Loopback ip is ignored). +func GetLocalIPs() ([]*net.IP, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + + ips := make([]*net.IP, 0) + for _, addr := range addrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + continue + } + + if ipnet.IP.IsLoopback() { + continue + } + + ips = append(ips, &ipnet.IP) + } + + return ips, nil +} diff --git a/upnp/message.go b/upnp/message.go new file mode 100644 index 0000000..72fc493 --- /dev/null +++ b/upnp/message.go @@ -0,0 +1,31 @@ +package upnp + +import ( + "bytes" +) + +type Node struct { + Name string + Content string + Attr map[string]string + Child []Node +} + +func (n *Node) AddChild(node Node) { + n.Child = append(n.Child, node) +} +func (n *Node) BuildXML() string { + buf := bytes.NewBufferString("<") + buf.WriteString(n.Name) + for key, value := range n.Attr { + buf.WriteString(" ") + buf.WriteString(key + "=" + value) + } + buf.WriteString(">" + n.Content) + + for _, node := range n.Child { + buf.WriteString(node.BuildXML()) + } + buf.WriteString("") + return buf.String() +} diff --git a/upnp/upnp.go b/upnp/upnp.go new file mode 100644 index 0000000..a0b5098 --- /dev/null +++ b/upnp/upnp.go @@ -0,0 +1,195 @@ +package upnp + +import ( + // "fmt" + "errors" + "log" + "sync" +) + +/* + * 得到网关 + */ + +//对所有的端口进行管理 +type MappingPortStruct struct { + lock *sync.Mutex + mappingPorts map[string][][]int +} + +//添加一个端口映射记录 +//只对映射进行管理 +func (this *MappingPortStruct) addMapping(localPort, remotePort int, protocol string) { + + this.lock.Lock() + defer this.lock.Unlock() + if this.mappingPorts == nil { + one := make([]int, 0) + one = append(one, localPort) + two := make([]int, 0) + two = append(two, remotePort) + portMapping := [][]int{one, two} + this.mappingPorts = map[string][][]int{protocol: portMapping} + return + } + portMapping := this.mappingPorts[protocol] + if portMapping == nil { + one := make([]int, 0) + one = append(one, localPort) + two := make([]int, 0) + two = append(two, remotePort) + this.mappingPorts[protocol] = [][]int{one, two} + return + } + one := portMapping[0] + two := portMapping[1] + one = append(one, localPort) + two = append(two, remotePort) + this.mappingPorts[protocol] = [][]int{one, two} +} + +//删除一个映射记录 +//只对映射进行管理 +func (this *MappingPortStruct) delMapping(remotePort int, protocol string) { + this.lock.Lock() + defer this.lock.Unlock() + if this.mappingPorts == nil { + return + } + tmp := MappingPortStruct{lock: new(sync.Mutex)} + mappings := this.mappingPorts[protocol] + for i := 0; i < len(mappings[0]); i++ { + if mappings[1][i] == remotePort { + //要删除的映射 + break + } + tmp.addMapping(mappings[0][i], mappings[1][i], protocol) + } + this.mappingPorts = tmp.mappingPorts +} +func (this *MappingPortStruct) GetAllMapping() map[string][][]int { + return this.mappingPorts +} + +type Upnp struct { + Active bool //这个upnp协议是否可用 + LocalHost string //本机ip地址 + GatewayInsideIP string //局域网网关ip + GatewayOutsideIP string //网关公网ip + OutsideMappingPort map[string]int //映射外部端口 + InsideMappingPort map[string]int //映射本机端口 + Gateway *Gateway //网关信息 + CtrlUrl string //控制请求url + MappingPort MappingPortStruct //已经添加了的映射 {"TCP":[1990],"UDP":[1991]} +} + +//得到本地联网的ip地址 +//得到局域网网关ip +func (this *Upnp) SearchGateway() (err error) { + defer func(err error) { + if errTemp := recover(); errTemp != nil { + log.Println("upnp模块报错了", errTemp) + err = errTemp.(error) + } + }(err) + + if this.LocalHost == "" { + this.MappingPort = MappingPortStruct{ + lock: new(sync.Mutex), + // mappingPorts: map[string][][]int{}, + } + this.LocalHost = GetLocalIntenetIp() + } + searchGateway := SearchGateway{upnp: this} + if searchGateway.Send() { + return nil + } + return errors.New("未发现网关设备") +} + +func (this *Upnp) deviceStatus() { + +} + +//查看设备描述,得到控制请求url +func (this *Upnp) deviceDesc() (err error) { + if this.GatewayInsideIP == "" { + if err := this.SearchGateway(); err != nil { + return err + } + } + device := DeviceDesc{upnp: this} + device.Send() + this.Active = true + // log.Println("获得控制请求url:", this.CtrlUrl) + return +} + +//查看公网ip地址 +func (this *Upnp) ExternalIPAddr() (err error) { + if this.CtrlUrl == "" { + if err := this.deviceDesc(); err != nil { + return err + } + } + eia := ExternalIPAddress{upnp: this} + eia.Send() + return nil + // log.Println("获得公网ip地址为:", this.GatewayOutsideIP) +} + +//添加一个端口映射 +func (this *Upnp) AddPortMapping(localPort, remotePort int, protocol string) (err error) { + defer func(err error) { + if errTemp := recover(); errTemp != nil { + log.Println("upnp模块报错了", errTemp) + err = errTemp.(error) + } + }(err) + if this.GatewayOutsideIP == "" { + if err := this.ExternalIPAddr(); err != nil { + return err + } + } + addPort := AddPortMapping{upnp: this} + if issuccess := addPort.Send(localPort, remotePort, protocol); issuccess { + this.MappingPort.addMapping(localPort, remotePort, protocol) + // log.Println("添加一个端口映射:protocol:", protocol, "local:", localPort, "remote:", remotePort) + return nil + } else { + this.Active = false + // log.Println("添加一个端口映射失败") + return errors.New("添加一个端口映射失败") + } +} + +func (this *Upnp) DelPortMapping(remotePort int, protocol string) bool { + delMapping := DelPortMapping{upnp: this} + issuccess := delMapping.Send(remotePort, protocol) + if issuccess { + this.MappingPort.delMapping(remotePort, protocol) + log.Println("删除了一个端口映射: remote:", remotePort) + } + return issuccess +} + +//回收端口 +func (this *Upnp) Reclaim() { + mappings := this.MappingPort.GetAllMapping() + tcpMapping, ok := mappings["TCP"] + if ok { + for i := 0; i < len(tcpMapping[0]); i++ { + this.DelPortMapping(tcpMapping[1][i], "TCP") + } + } + udpMapping, ok := mappings["UDP"] + if ok { + for i := 0; i < len(udpMapping[0]); i++ { + this.DelPortMapping(udpMapping[0][i], "UDP") + } + } +} + +func (this *Upnp) GetAllMapping() map[string][][]int { + return this.MappingPort.GetAllMapping() +}