├── AddPortMapping.go
├── DelPortMapping.go
├── DeviceDesc.go
├── DeviceStatusInfo.go
├── ExternalIPAddress.go
├── README.md
├── SearchGatewayMsg.go
├── common.go
├── example
├── mapping_custom.go
├── mapping_custom_x64.exe
├── simple.go
├── simpleFlow.go
└── 各种消息格式.txt
├── go.mod
├── go.sum
├── message.go
└── upnp.go
/AddPortMapping.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | // "log"
5 | // "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type AddPortMapping struct {
13 | upnp *Upnp
14 | }
15 |
16 | func (this *AddPortMapping) Send(localPort, remotePort int, protocol string) bool {
17 | request := this.buildRequest(localPort, remotePort, protocol)
18 | response, _ := http.DefaultClient.Do(request)
19 | resultBody, _ := ioutil.ReadAll(response.Body)
20 | if response.StatusCode == 200 {
21 | this.resolve(string(resultBody))
22 | return true
23 | }
24 | return false
25 | }
26 | func (this *AddPortMapping) buildRequest(localPort, remotePort int, protocol string) *http.Request {
27 | //请求头
28 | header := http.Header{}
29 | header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
30 | header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"`)
31 | header.Set("Content-Type", "text/xml")
32 | header.Set("Connection", "Close")
33 | header.Set("Content-Length", "")
34 | //请求体
35 | body := Node{Name: "SOAP-ENV:Envelope",
36 | Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`,
37 | "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}}
38 | childOne := Node{Name: `SOAP-ENV:Body`}
39 | childTwo := Node{Name: `m:AddPortMapping`,
40 | Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}}
41 |
42 | childList1 := Node{Name: "NewExternalPort", Content: strconv.Itoa(remotePort)}
43 | childList2 := Node{Name: "NewInternalPort", Content: strconv.Itoa(localPort)}
44 | childList3 := Node{Name: "NewProtocol", Content: protocol}
45 | childList4 := Node{Name: "NewEnabled", Content: "1"}
46 | childList5 := Node{Name: "NewInternalClient", Content: this.upnp.LocalHost}
47 | childList6 := Node{Name: "NewLeaseDuration", Content: "0"}
48 | childList7 := Node{Name: "NewPortMappingDescription", Content: "mandela"}
49 | childList8 := Node{Name: "NewRemoteHost"}
50 | childTwo.AddChild(childList1)
51 | childTwo.AddChild(childList2)
52 | childTwo.AddChild(childList3)
53 | childTwo.AddChild(childList4)
54 | childTwo.AddChild(childList5)
55 | childTwo.AddChild(childList6)
56 | childTwo.AddChild(childList7)
57 | childTwo.AddChild(childList8)
58 |
59 | childOne.AddChild(childTwo)
60 | body.AddChild(childOne)
61 | bodyStr := body.BuildXML()
62 |
63 | //请求
64 | request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl,
65 | strings.NewReader(bodyStr))
66 | request.Header = header
67 | request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr))))
68 | return request
69 | }
70 |
71 | func (this *AddPortMapping) resolve(resultStr string) {
72 | }
73 |
--------------------------------------------------------------------------------
/DelPortMapping.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "io/ioutil"
5 | // "log"
6 | "net/http"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type DelPortMapping struct {
12 | upnp *Upnp
13 | }
14 |
15 | func (this *DelPortMapping) Send(remotePort int, protocol string) bool {
16 | request := this.buildRequest(remotePort, protocol)
17 | response, _ := http.DefaultClient.Do(request)
18 | resultBody, _ := ioutil.ReadAll(response.Body)
19 | if response.StatusCode == 200 {
20 | // log.Println(string(resultBody))
21 | this.resolve(string(resultBody))
22 | return true
23 | }
24 | return false
25 | }
26 | func (this *DelPortMapping) buildRequest(remotePort int, protocol string) *http.Request {
27 | //请求头
28 | header := http.Header{}
29 | header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
30 | header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"`)
31 | header.Set("Content-Type", "text/xml")
32 | header.Set("Connection", "Close")
33 | header.Set("Content-Length", "")
34 |
35 | //请求体
36 | body := Node{Name: "SOAP-ENV:Envelope",
37 | Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`,
38 | "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}}
39 | childOne := Node{Name: `SOAP-ENV:Body`}
40 | childTwo := Node{Name: `m:DeletePortMapping`,
41 | Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}}
42 | childList1 := Node{Name: "NewExternalPort", Content: strconv.Itoa(remotePort)}
43 | childList2 := Node{Name: "NewProtocol", Content: protocol}
44 | childList3 := Node{Name: "NewRemoteHost"}
45 | childTwo.AddChild(childList1)
46 | childTwo.AddChild(childList2)
47 | childTwo.AddChild(childList3)
48 | childOne.AddChild(childTwo)
49 | body.AddChild(childOne)
50 | bodyStr := body.BuildXML()
51 |
52 | //请求
53 | request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl,
54 | strings.NewReader(bodyStr))
55 | request.Header = header
56 | request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr))))
57 | return request
58 | }
59 |
60 | func (this *DelPortMapping) resolve(resultStr string) {
61 | }
62 |
--------------------------------------------------------------------------------
/DeviceDesc.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "encoding/xml"
5 | "io/ioutil"
6 | "net/http"
7 | "strings"
8 | )
9 |
10 | type DeviceDesc struct {
11 | upnp *Upnp
12 | }
13 |
14 | func (this *DeviceDesc) Send() bool {
15 | request := this.BuildRequest()
16 | response, _ := http.DefaultClient.Do(request)
17 | resultBody, _ := ioutil.ReadAll(response.Body)
18 | if response.StatusCode == 200 {
19 | this.resolve(string(resultBody))
20 | return true
21 | }
22 | return false
23 | }
24 | func (this *DeviceDesc) BuildRequest() *http.Request {
25 | //请求头
26 | header := http.Header{}
27 | header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
28 | header.Set("User-Agent", "preston")
29 | header.Set("Host", this.upnp.Gateway.Host)
30 | header.Set("Connection", "keep-alive")
31 |
32 | //请求
33 | request, _ := http.NewRequest("GET", "http://"+this.upnp.Gateway.Host+this.upnp.Gateway.DeviceDescUrl, nil)
34 | request.Header = header
35 | // request := http.Request{Method: "GET", Proto: "HTTP/1.1",
36 | // Host: this.upnp.Gateway.Host, Url: this.upnp.Gateway.DeviceDescUrl, Header: header}
37 | return request
38 | }
39 |
40 | func (this *DeviceDesc) resolve(resultStr string) {
41 | inputReader := strings.NewReader(resultStr)
42 |
43 | // 从文件读取,如可以如下:
44 | // content, err := ioutil.ReadFile("studygolang.xml")
45 | // decoder := xml.NewDecoder(bytes.NewBuffer(content))
46 |
47 | lastLabel := ""
48 |
49 | ISUpnpServer := false
50 |
51 | IScontrolURL := false
52 | var controlURL string //`controlURL`
53 | // var eventSubURL string //`eventSubURL`
54 | // var SCPDURL string //`SCPDURL`
55 |
56 | decoder := xml.NewDecoder(inputReader)
57 | for t, err := decoder.Token(); err == nil && !IScontrolURL; t, err = decoder.Token() {
58 | switch token := t.(type) {
59 | // 处理元素开始(标签)
60 | case xml.StartElement:
61 | if ISUpnpServer {
62 | name := token.Name.Local
63 | lastLabel = name
64 | }
65 |
66 | // 处理元素结束(标签)
67 | case xml.EndElement:
68 | // log.Println("结束标记:", token.Name.Local)
69 | // 处理字符数据(这里就是元素的文本)
70 | case xml.CharData:
71 | //得到url后其他标记就不处理了
72 | content := string([]byte(token))
73 |
74 | //找到提供端口映射的服务
75 | if content == this.upnp.Gateway.ServiceType {
76 | ISUpnpServer = true
77 | continue
78 | }
79 | //urn:upnp-org:serviceId:WANIPConnection
80 | if ISUpnpServer {
81 | switch lastLabel {
82 | case "controlURL":
83 |
84 | controlURL = content
85 | IScontrolURL = true
86 | case "eventSubURL":
87 | // eventSubURL = content
88 | case "SCPDURL":
89 | // SCPDURL = content
90 | }
91 | }
92 | default:
93 | // ...
94 | }
95 | }
96 | this.upnp.CtrlUrl = controlURL
97 | }
98 |
--------------------------------------------------------------------------------
/DeviceStatusInfo.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | // "log"
5 | // "io/ioutil"
6 | "net/http"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type SearchGatewayReq struct {
12 | host string
13 | resultBody string
14 | ctrlUrl string
15 | upnp *Upnp
16 | }
17 |
18 | func (this SearchGatewayReq) Send() {
19 | // request := this.BuildRequest()
20 | }
21 | func (this SearchGatewayReq) BuildRequest() *http.Request {
22 | //请求头
23 | header := http.Header{}
24 | header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
25 | header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"`)
26 | header.Set("Content-Type", "text/xml")
27 | header.Set("Connection", "Close")
28 | header.Set("Content-Length", "")
29 | //请求体
30 | body := Node{Name: "SOAP-ENV:Envelope",
31 | Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`,
32 | "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}}
33 | childOne := Node{Name: `SOAP-ENV:Body`}
34 | childTwo := Node{Name: `m:GetStatusInfo`,
35 | Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}}
36 | childOne.AddChild(childTwo)
37 | body.AddChild(childOne)
38 | bodyStr := body.BuildXML()
39 | //请求
40 | request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl,
41 | strings.NewReader(bodyStr))
42 | request.Header = header
43 | request.Header.Set("Content-Length", strconv.Itoa(len([]byte(bodyStr))))
44 | return request
45 | }
46 |
--------------------------------------------------------------------------------
/ExternalIPAddress.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "encoding/xml"
5 | "io/ioutil"
6 | // "log"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type ExternalIPAddress struct {
13 | upnp *Upnp
14 | }
15 |
16 | func (this *ExternalIPAddress) Send() bool {
17 | request := this.BuildRequest()
18 | response, _ := http.DefaultClient.Do(request)
19 | resultBody, _ := ioutil.ReadAll(response.Body)
20 | if response.StatusCode == 200 {
21 | this.resolve(string(resultBody))
22 | return true
23 | }
24 | return false
25 | }
26 | func (this *ExternalIPAddress) BuildRequest() *http.Request {
27 | //请求头
28 | header := http.Header{}
29 | header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
30 | header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"`)
31 | header.Set("Content-Type", "text/xml")
32 | header.Set("Connection", "Close")
33 | header.Set("Content-Length", "")
34 | //请求体
35 | body := Node{Name: "SOAP-ENV:Envelope",
36 | Attr: map[string]string{"xmlns:SOAP-ENV": `"http://schemas.xmlsoap.org/soap/envelope/"`,
37 | "SOAP-ENV:encodingStyle": `"http://schemas.xmlsoap.org/soap/encoding/"`}}
38 | childOne := Node{Name: `SOAP-ENV:Body`}
39 | childTwo := Node{Name: `m:GetExternalIPAddress`,
40 | Attr: map[string]string{"xmlns:m": `"urn:schemas-upnp-org:service:WANIPConnection:1"`}}
41 | childOne.AddChild(childTwo)
42 | body.AddChild(childOne)
43 |
44 | bodyStr := body.BuildXML()
45 | //请求
46 | request, _ := http.NewRequest("POST", "http://"+this.upnp.Gateway.Host+this.upnp.CtrlUrl,
47 | strings.NewReader(bodyStr))
48 | request.Header = header
49 | request.Header.Set("Content-Length", strconv.Itoa(len([]byte(body.BuildXML()))))
50 | return request
51 | }
52 |
53 | //NewExternalIPAddress
54 | func (this *ExternalIPAddress) resolve(resultStr string) {
55 | inputReader := strings.NewReader(resultStr)
56 | decoder := xml.NewDecoder(inputReader)
57 | ISexternalIP := false
58 | for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {
59 | switch token := t.(type) {
60 | // 处理元素开始(标签)
61 | case xml.StartElement:
62 | name := token.Name.Local
63 | if name == "NewExternalIPAddress" {
64 | ISexternalIP = true
65 | }
66 | // 处理元素结束(标签)
67 | case xml.EndElement:
68 | // 处理字符数据(这里就是元素的文本)
69 | case xml.CharData:
70 | if ISexternalIP == true {
71 | this.upnp.GatewayOutsideIP = string([]byte(token))
72 | return
73 | }
74 | default:
75 | // ...
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## upnp protocol
2 | ====
3 |
4 | A simple implements UPnP protocol for Golang library. Add port mapping for NAT devices.
5 |
6 | 在内网中搜索网关设备,检查网关是否支持upnp协议,若支持,添加端口映射。
7 |
8 | ====
9 |
10 | ## example:
11 |
12 | ### 1. add a port mapping
13 | ~~~ go
14 | mapping := new(upnp.Upnp)
15 | if err := mapping.AddPortMapping(55789, 55789, "TCP"); err == nil {
16 | fmt.Println("success !")
17 | // remove port mapping in gatway
18 | mapping.Reclaim()
19 | } else {
20 | fmt.Println("fail !")
21 | }
22 | ~~~
23 |
24 | ### 2. search gateway device.
25 | ~~~ go
26 | upnpMan := new(upnp.Upnp)
27 | err := upnpMan.SearchGateway()
28 | if err != nil {
29 | fmt.Println(err.Error())
30 | } else {
31 | fmt.Println("local ip address: ", upnpMan.LocalHost)
32 | fmt.Println("gateway ip address: ", upnpMan.Gateway.Host)
33 | }
34 | ~~~
35 | ### 3. get an internet ip address in gatway.
36 | ~~~ go
37 | upnpMan := new(upnp.Upnp)
38 | err := upnpMan.ExternalIPAddr()
39 | if err != nil {
40 | fmt.Println(err.Error())
41 | } else {
42 | fmt.Println("internet ip address: ", upnpMan.GatewayOutsideIP)
43 | }
44 | ~~~
--------------------------------------------------------------------------------
/SearchGatewayMsg.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "log"
5 | "net"
6 | "strings"
7 | "time"
8 | // "net/http"
9 | )
10 |
11 | type Gateway struct {
12 | GatewayName string //网关名称
13 | Host string //网关ip和端口
14 | DeviceDescUrl string //网关设备描述路径
15 | Cache string //cache
16 | ST string
17 | USN string
18 | deviceType string //设备的urn "urn:schemas-upnp-org:service:WANIPConnection:1"
19 | ControlURL string //设备端口映射请求路径
20 | ServiceType string //提供upnp服务的服务类型
21 | }
22 |
23 | type SearchGateway struct {
24 | searchMessage string
25 | upnp *Upnp
26 | }
27 |
28 | func (this *SearchGateway) Send() bool {
29 | this.buildRequest()
30 | c := make(chan string)
31 | go this.send(c)
32 | result := <-c
33 | if result == "" {
34 | //超时了
35 | this.upnp.Active = false
36 | return false
37 | }
38 | this.resolve(result)
39 |
40 | this.upnp.Gateway.ServiceType = "urn:schemas-upnp-org:service:WANIPConnection:1"
41 | this.upnp.Active = true
42 | return true
43 | }
44 | func (this *SearchGateway) send(c chan string) {
45 | //发送组播消息,要带上端口,格式如:"239.255.255.250:1900"
46 | var conn *net.UDPConn
47 | defer func() {
48 | if r := recover(); r != nil {
49 | //超时了
50 | }
51 | }()
52 | go func(conn *net.UDPConn) {
53 | defer func() {
54 | if r := recover(); r != nil {
55 | //没超时
56 | }
57 | }()
58 | //超时时间为3秒
59 | time.Sleep(time.Second * 3)
60 | c <- ""
61 | conn.Close()
62 | }(conn)
63 | remotAddr, err := net.ResolveUDPAddr("udp", "239.255.255.250:1900")
64 | if err != nil {
65 | log.Println("组播地址格式不正确")
66 | }
67 | locaAddr, err := net.ResolveUDPAddr("udp", this.upnp.LocalHost+":")
68 |
69 | if err != nil {
70 | log.Println("本地ip地址格式不正确")
71 | }
72 | conn, err = net.ListenUDP("udp", locaAddr)
73 | defer conn.Close()
74 | if err != nil {
75 | log.Println("监听udp出错")
76 | }
77 | _, err = conn.WriteToUDP([]byte(this.searchMessage), remotAddr)
78 | if err != nil {
79 | log.Println("发送msg到组播地址出错")
80 | }
81 | buf := make([]byte, 1024)
82 | n, _, err := conn.ReadFromUDP(buf)
83 | if err != nil {
84 | log.Println("从组播地址接搜消息出错")
85 | }
86 |
87 | result := string(buf[:n])
88 | c <- result
89 | }
90 | func (this *SearchGateway) buildRequest() {
91 | this.searchMessage = "M-SEARCH * HTTP/1.1\r\n" +
92 | "HOST: 239.255.255.250:1900\r\n" +
93 | "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n" +
94 | "MAN: \"ssdp:discover\"\r\n" + "MX: 3\r\n\r\n"
95 | }
96 |
97 | func (this *SearchGateway) resolve(result string) {
98 | this.upnp.Gateway = &Gateway{}
99 |
100 | lines := strings.Split(result, "\r\n")
101 | for _, line := range lines {
102 | //按照第一个冒号分为两个字符串
103 | nameValues := strings.SplitAfterN(line, ":", 2)
104 | if len(nameValues) < 2 {
105 | continue
106 | }
107 | switch strings.ToUpper(strings.Trim(strings.Split(nameValues[0], ":")[0], " ")) {
108 | case "ST":
109 | this.upnp.Gateway.ST = nameValues[1]
110 | case "CACHE-CONTROL":
111 | this.upnp.Gateway.Cache = nameValues[1]
112 | case "LOCATION":
113 | urls := strings.Split(strings.Split(nameValues[1], "//")[1], "/")
114 | this.upnp.Gateway.Host = urls[0]
115 | this.upnp.Gateway.DeviceDescUrl = "/" + urls[1]
116 | case "SERVER":
117 | this.upnp.Gateway.GatewayName = nameValues[1]
118 | default:
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/common.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | // "log"
5 | "errors"
6 | "net"
7 | "strings"
8 | )
9 |
10 | //获取本机能联网的ip地址
11 | func GetLocalIntenetIp() string {
12 | /*
13 | 获得所有本机地址
14 | 判断能联网的ip地址
15 | */
16 |
17 | conn, err := net.Dial("udp", "google.com:80")
18 | if err != nil {
19 | panic(errors.New("不能连接网络"))
20 | }
21 | defer conn.Close()
22 | return strings.Split(conn.LocalAddr().String(), ":")[0]
23 | }
24 |
25 | // This returns the list of local ip addresses which other hosts can connect
26 | // to (NOTE: Loopback ip is ignored).
27 | func GetLocalIPs() ([]*net.IP, error) {
28 | addrs, err := net.InterfaceAddrs()
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | ips := make([]*net.IP, 0)
34 | for _, addr := range addrs {
35 | ipnet, ok := addr.(*net.IPNet)
36 | if !ok {
37 | continue
38 | }
39 |
40 | if ipnet.IP.IsLoopback() {
41 | continue
42 | }
43 |
44 | ips = append(ips, &ipnet.IP)
45 | }
46 |
47 | return ips, nil
48 | }
49 |
--------------------------------------------------------------------------------
/example/mapping_custom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "github.com/prestonTao/upnp"
7 | "os"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | var mapping = new(upnp.Upnp)
13 | var reader = bufio.NewReader(os.Stdin)
14 |
15 | var localPort = 1990
16 | var remotePort = 1990
17 |
18 | func init() {
19 |
20 | }
21 |
22 | func main() {
23 | Start()
24 | }
25 |
26 | func Start() {
27 | if !CheckNet() {
28 | fmt.Println("你的路由器不支持upnp协议")
29 | return
30 | }
31 | fmt.Println("本机ip地址:", mapping.LocalHost)
32 |
33 | ExternalIPAddr()
34 |
35 | tag:
36 | if !GetInput() {
37 | goto tag
38 | }
39 | if !AddPortMapping(localPort, remotePort) {
40 | goto tag
41 | }
42 |
43 | fmt.Println("--------------------------------------")
44 | fmt.Println("1. stop 停止程序并回收映射的端口")
45 | fmt.Println("2. add 添加一个端口映射")
46 | fmt.Println("3. del 手动删除一个端口映射")
47 | fmt.Println("\n 注意:此程序映射的端口默认是TCP端口")
48 | fmt.Println(" 需要映射udp端口请访问:")
49 | fmt.Println(" http://github.com/prestonTao/upnp")
50 | fmt.Println("--------------------------------------")
51 |
52 | running := true
53 | for running {
54 | data, _, _ := reader.ReadLine()
55 | commands := strings.Split(string(data), " ")
56 | switch commands[0] {
57 | case "help":
58 |
59 | case "stop":
60 | running = false
61 | mapping.Reclaim()
62 | case "add":
63 | goto tag
64 | case "del":
65 | tagDel:
66 | if !GetInput() {
67 | goto tagDel
68 | }
69 | DelPortMapping(localPort, remotePort)
70 | case "cdp":
71 | case "dump":
72 | }
73 | }
74 |
75 | }
76 |
77 | /*
78 | 检查网络是否支持upnp协议
79 | */
80 | func CheckNet() bool {
81 | err := mapping.SearchGateway()
82 | if err != nil {
83 | return false
84 | } else {
85 | return true
86 | }
87 | }
88 |
89 | //获得公网ip地址
90 | func ExternalIPAddr() {
91 | err := mapping.ExternalIPAddr()
92 | if err != nil {
93 | fmt.Println(err.Error())
94 | } else {
95 | fmt.Println("外网ip地址为:", mapping.GatewayOutsideIP)
96 | }
97 | }
98 |
99 | /*
100 | 得到用户输入的端口
101 | */
102 | func GetInput() bool {
103 | var err error
104 | fmt.Println("请输入要映射的本地端口:")
105 | data, _, _ := reader.ReadLine()
106 | localPort, err = strconv.Atoi(string(data))
107 | if err != nil {
108 | fmt.Println("输入的端口号错误,请输入 0-65535 的数字")
109 | return false
110 | }
111 | if localPort < 0 || localPort > 65535 {
112 | fmt.Println("输入的端口号错误,请输入 0-65535 的数字")
113 | return false
114 | }
115 |
116 | fmt.Println("请输入要映射到外网的端口:")
117 | data, _, _ = reader.ReadLine()
118 | remotePort, err = strconv.Atoi(string(data))
119 | if err != nil {
120 | fmt.Println("输入的端口号错误,请输入 0-65535 的数字")
121 | return false
122 | }
123 | if remotePort < 0 || remotePort > 65535 {
124 | fmt.Println("输入的端口号错误,请输入 0-65535 的数字")
125 | return false
126 | }
127 | return true
128 | }
129 |
130 | /*
131 | 添加一个端口映射
132 | */
133 | func AddPortMapping(localPort, remotePort int) bool {
134 | //添加一个端口映射
135 | if err := mapping.AddPortMapping(localPort, remotePort, "TCP"); err == nil {
136 | fmt.Println("端口映射成功")
137 | return true
138 | } else {
139 | fmt.Println("端口映射失败")
140 | return false
141 | }
142 | }
143 |
144 | /*
145 | 删除一个端口映射
146 | */
147 | func DelPortMapping(localPort, remotePort int) {
148 | mapping.DelPortMapping(remotePort, "TCP")
149 | }
150 |
--------------------------------------------------------------------------------
/example/mapping_custom_x64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prestonTao/upnp/f141651daac611e566187863c816019da45f4838/example/mapping_custom_x64.exe
--------------------------------------------------------------------------------
/example/simple.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // "bufio"
5 | "fmt"
6 | "github.com/prestonTao/upnp"
7 | // "os"
8 | )
9 |
10 | func main() {
11 | SearchGateway()
12 | ExternalIPAddr()
13 | AddPortMapping()
14 | }
15 |
16 | //搜索网关设备
17 | func SearchGateway() {
18 | upnpMan := new(upnp.Upnp)
19 | err := upnpMan.SearchGateway()
20 | if err != nil {
21 | fmt.Println(err.Error())
22 | } else {
23 | fmt.Println("本机ip地址:", upnpMan.LocalHost)
24 | fmt.Println("upnp设备地址:", upnpMan.Gateway.Host)
25 | }
26 | }
27 |
28 | //获得公网ip地址
29 | func ExternalIPAddr() {
30 | upnpMan := new(upnp.Upnp)
31 | err := upnpMan.ExternalIPAddr()
32 | if err != nil {
33 | fmt.Println(err.Error())
34 | } else {
35 | fmt.Println("外网ip地址为:", upnpMan.GatewayOutsideIP)
36 | }
37 | }
38 |
39 | //添加一个端口映射
40 | func AddPortMapping() {
41 | mapping := new(upnp.Upnp)
42 | if err := mapping.AddPortMapping(55789, 55789, "TCP"); err == nil {
43 | fmt.Println("端口映射成功")
44 | mapping.Reclaim()
45 | } else {
46 | fmt.Println("端口映射失败")
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/example/simpleFlow.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net"
7 | "net/http"
8 | "strings"
9 | "time"
10 | )
11 |
12 | func chk(err error) {
13 | if err != nil {
14 | panic(err)
15 | }
16 | }
17 |
18 | func main() {
19 | youleTest()
20 | }
21 |
22 | func youleTest() {
23 | lAddr := "192.168.1.100"
24 | rAddr := "192.168.1.2"
25 | //---------------------------------------------------------
26 | // 搜素网关设备
27 | //---------------------------------------------------------
28 |
29 | searchDevice(lAddr+":9981", "239.255.255.250:1900")
30 |
31 | //---------------------------------------------------------
32 | // 查看设备描述
33 | //---------------------------------------------------------
34 |
35 | // readDeviceDesc(rAddr + ":1900")
36 |
37 | //---------------------------------------------------------
38 | // 查看设备状态 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"\r\n
39 | //---------------------------------------------------------
40 | // getDeviceStatusInfo(rAddr + ":1900")
41 | getDeviceStatusInfo(rAddr + ":56688")
42 |
43 | addPortMapping(rAddr + ":56688")
44 |
45 | time.Sleep(time.Second * 10)
46 |
47 | remotePort(rAddr + ":56688")
48 | }
49 |
50 | func simple1() {
51 | lAddr := "192.168.1.100"
52 | rAddr := "192.168.1.1"
53 | //---------------------------------------------------------
54 | // 搜素网关设备
55 | //---------------------------------------------------------
56 |
57 | searchDevice(lAddr+":9981", "239.255.255.250:1900")
58 |
59 | //---------------------------------------------------------
60 | // 查看设备描述
61 | //---------------------------------------------------------
62 |
63 | // readDeviceDesc(rAddr + ":1900")
64 |
65 | //---------------------------------------------------------
66 | // 查看设备状态 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"\r\n
67 | //---------------------------------------------------------
68 | // getDeviceStatusInfo(rAddr + ":1900")
69 |
70 | addPortMapping(rAddr + ":1900")
71 |
72 | time.Sleep(time.Second * 10)
73 |
74 | remotePort(rAddr + ":1900")
75 | }
76 |
77 | func searchDevice(localAddr, remoteAddr string) string {
78 | fmt.Println("搜素网关设备")
79 | msg := "M-SEARCH * HTTP/1.1\r\n" +
80 | "HOST: 239.255.255.250:1900\r\n" +
81 | "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" +
82 | "MAN: \"ssdp:discover\"\r\n" +
83 | "MX: 3\r\n" + // seconds to delay response
84 | "\r\n"
85 | remotAddr, err := net.ResolveUDPAddr("udp", remoteAddr)
86 | chk(err)
87 | locaAddr, err := net.ResolveUDPAddr("udp", localAddr)
88 | chk(err)
89 | conn, err := net.ListenUDP("udp", locaAddr)
90 | chk(err)
91 | _, err = conn.WriteToUDP([]byte(msg), remotAddr)
92 | chk(err)
93 | buf := make([]byte, 1024)
94 | _, _, err = conn.ReadFromUDP(buf)
95 | chk(err)
96 | defer conn.Close()
97 | fmt.Println(string(buf))
98 | return string(buf)
99 | }
100 |
101 | func readDeviceDesc(rAddr string) string {
102 | fmt.Println("查看设备描述")
103 | msg := "GET /igd.xml HTTP/1.1\r\n" +
104 | "User-Agent: Java/1.7.0_45\r\n" +
105 | "Host: 192.168.1.1:1900\r\n" +
106 | "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" +
107 | "Connection: keep-alive\r\n\r\n"
108 | conn, err := net.Dial("tcp", rAddr)
109 | chk(err)
110 | _, err = conn.Write([]byte(msg))
111 | chk(err)
112 | buf := make([]byte, 1024)
113 | _, err = conn.Read(buf)
114 | chk(err)
115 | fmt.Println(string(buf))
116 | buf = make([]byte, 3048)
117 | _, err = conn.Read(buf)
118 | chk(err)
119 | fmt.Println(string(buf))
120 |
121 | // //查看设备状态
122 | // fmt.Println("查看设备状态")
123 | // statusHeader := `POST /ipc HTTP/1.1\r\n
124 | // Content-Type: text/xml\r\n
125 | // SOAPAction: "urn:schemas-upnp-org:device:InternetGatewayDevice:1#GetStatusInfo"\r\n
126 | // Connection: Close\r\n
127 | // User-Agent: Java/1.7.0_45\r\n
128 | // Host: 192.168.1.1:1900\r\n
129 | // Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n
130 | // Content-Length: 309\r\n
131 | // \r\n`
132 | // statusBody := ``
133 | // _, err = conn.Write([]byte(statusHeader))
134 | // chk(err)
135 | // _, err = conn.Write([]byte(statusBody))
136 | // chk(err)
137 |
138 | // buf = make([]byte, 1024)
139 | // _, err = conn.Read(buf)
140 | // chk(err)
141 | // fmt.Println(string(buf))
142 | // buf = make([]byte, 3048)
143 | // _, err = conn.Read(buf)
144 | // chk(err)
145 | // fmt.Println(string(buf))
146 | return string(buf)
147 | }
148 |
149 | func getDeviceStatusInfo(rAddr string) {
150 |
151 | fmt.Println("查看设备状态")
152 |
153 | readMappingBody := `
154 |
155 |
156 |
157 | `
158 |
159 | client := &http.Client{}
160 | // 第三个参数设置body部分
161 | reqest, _ := http.NewRequest("POST", "http://"+rAddr+"/ipc", strings.NewReader(readMappingBody))
162 | reqest.Proto = "HTTP/1.1"
163 | reqest.Host = rAddr
164 |
165 | reqest.Header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
166 | reqest.Header.Set("Content-Type", "text/xml")
167 | reqest.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo\"")
168 |
169 | reqest.Header.Set("Connection", "Close")
170 | reqest.Header.Set("Content-Length", string(len([]byte(readMappingBody))))
171 |
172 | response, _ := client.Do(reqest)
173 |
174 | body, _ := ioutil.ReadAll(response.Body)
175 | //bodystr := string(body)
176 | fmt.Println(response.StatusCode)
177 | if response.StatusCode == 200 {
178 | fmt.Println(response.Header)
179 | fmt.Println(string(body))
180 | }
181 | }
182 |
183 | func addPortMapping(rAddr string) {
184 |
185 | fmt.Println("添加一个端口映射")
186 |
187 | readMappingBody := `
188 |
189 |
190 |
191 | 6991
192 | 6991
193 | TCP
194 | 1
195 | 192.168.1.100
196 | 0
197 | test
198 |
199 |
200 |
201 | `
202 |
203 | client := &http.Client{}
204 | // 第三个参数设置body部分
205 | reqest, _ := http.NewRequest("POST", "http://"+rAddr+"/ipc", strings.NewReader(readMappingBody))
206 | reqest.Proto = "HTTP/1.1"
207 | reqest.Host = rAddr
208 |
209 | reqest.Header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
210 | reqest.Header.Set("Content-Type", "text/xml")
211 | reqest.Header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"`)
212 |
213 | reqest.Header.Set("Connection", "Close")
214 | reqest.Header.Set("Content-Length", string(len([]byte(readMappingBody))))
215 |
216 | response, _ := client.Do(reqest)
217 |
218 | body, _ := ioutil.ReadAll(response.Body)
219 | //bodystr := string(body)
220 | fmt.Println(response.StatusCode)
221 | if response.StatusCode == 200 {
222 | fmt.Println(response.Header)
223 | fmt.Println(string(body))
224 | }
225 | }
226 |
227 | func remotePort(rAddr string) {
228 | fmt.Println("删除一个端口映射")
229 |
230 | readMappingBody := `
231 |
232 |
233 |
234 | 6991
235 | TCP
236 |
237 |
238 |
239 | `
240 |
241 | client := &http.Client{}
242 | // 第三个参数设置body部分
243 | reqest, _ := http.NewRequest("POST", "http://"+rAddr+"/ipc", strings.NewReader(readMappingBody))
244 | reqest.Proto = "HTTP/1.1"
245 | reqest.Host = rAddr
246 |
247 | reqest.Header.Set("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
248 | reqest.Header.Set("Content-Type", "text/xml")
249 | reqest.Header.Set("SOAPAction", `"urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"`)
250 |
251 | reqest.Header.Set("Connection", "Close")
252 | reqest.Header.Set("Content-Length", string(len([]byte(readMappingBody))))
253 |
254 | response, _ := client.Do(reqest)
255 |
256 | body, _ := ioutil.ReadAll(response.Body)
257 | //bodystr := string(body)
258 | fmt.Println(response.StatusCode)
259 | if response.StatusCode == 200 {
260 | fmt.Println(response.Header)
261 | fmt.Println(string(body))
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/example/各种消息格式.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | 设备描述回复
4 |
5 |
6 |
7 | 1
8 | 0
9 |
10 |
11 |
12 | urn:schemas-upnp-org:device:InternetGatewayDevice:1
13 | http://192.168.1.1:80
14 | Wireless N Router WR845N
15 | TP-LINK
16 | http://www.tp-link.com.cn
17 | TL-WR845N 1.0
18 | TL-WR845N
19 | 1.0
20 | uuid:upnp-InternetGatewayDevice-192168115678900001
21 | 123456789001
22 |
23 |
24 | urn:schemas-upnp-org:service:Layer3Forwarding:1
25 | urn:upnp-org:serviceId:L3Forwarding1
26 | /l3f
27 | /l3f
28 | /l3f.xml
29 |
30 |
31 |
32 |
33 | urn:schemas-upnp-org:device:WANDevice:1
34 | WAN Device
35 | TP-LINK
36 | http://www.tp-link.com.cn
37 | WAN Device
38 | WAN Device
39 | 1
40 |
41 | 12345678900001
42 | uuid:upnp-WANDevice-192168115678900001
43 | 123456789001
44 |
45 |
46 | urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
48 | urn:upnp-org:serviceId:WANCommonInterfaceConfig
49 | /ifc
50 | /ifc
51 | /ifc.xml
52 |
53 |
54 |
55 |
56 | urn:schemas-upnp-org:device:WANConnectionDevice:1
57 | WAN Connection Device
58 | TP-LINK
59 | http://www.tp-link.com.cn
60 | WAN Connection Device
61 | WAN Connection Device
62 | 1
63 |
64 | 12345678900001
65 | uuid:upnp-WANConnectionDevice-192168115678900001
66 | 123456789001
67 |
68 |
69 | urn:schemas-upnp-org:service:WANIPConnection:1
70 | urn:upnp-org:serviceId:WANIPConnection
71 | /ipc
72 | /ipc
73 | /ipc.xml
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 设备状态 请求
85 |
86 | POST /ipc HTTP/1.1
87 | Content-Type: text/xml
88 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"
89 | Connection: Close
90 | User-Agent: Java/1.7.0_45
91 | Host: 192.168.1.1:1900
92 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
93 | Content-Length: 311
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 设备状态 回复
105 |
106 | HTTP/1.1 200 OK
107 | CONNECTION: close
108 | SERVER: Wireless N Router WR845N, UPnP/1.0
109 | CONTENT-LENGTH: 480
110 | CONTENT-TYPE: text/xml; charset="utf-8"
111 |
112 |
113 |
114 |
115 |
116 | Connected
117 | ERROR_NONE
118 | 0 Days, 16:45:17
119 |
120 |
121 |
122 |
123 |
124 |
125 | 请求已经映射的端口号列表
126 |
127 | POST /ipc HTTP/1.1
128 | Content-Type: text/xml
129 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetPortMappingNumberOfEntries"
130 | Connection: Close
131 | User-Agent: Java/1.7.0_45
132 | Host: 192.168.1.1:1900
133 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
134 | Content-Length: 343
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | 返回已经映射到端口号列表
148 |
149 | HTTP/1.1 500 Internal Server Error
150 | CONNECTION: close
151 | CONTENT-LENGTH: 475
152 | CONTENT-TYPE: text/xml; charset="utf-8"
153 | DATE: Sat, 11 Jan 2014 07:17:12 GMT
154 | EXT:
155 | SERVER: Wireless N Router WR845N, UPnP/1.0
156 |
157 |
158 |
159 |
160 | SOAP-ENV:Client
161 | UPnPError
162 |
163 |
164 | 401
165 | Invalid Action
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | 查看普通端口列表 请求
175 |
176 | POST /ipc HTTP/1.1
177 | Content-Type: text/xml
178 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetGenericPortMappingEntry"
179 | Connection: Close
180 | User-Agent: Java/1.7.0_45
181 | Host: 192.168.1.1:1900
182 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
183 | Content-Length: 381
184 |
185 |
186 |
187 |
188 |
189 | 0
190 |
191 |
192 |
193 |
194 |
195 |
196 | 回复
197 |
198 | HTTP/1.1 500 Internal Server Error
199 | CONNECTION: close
200 | CONTENT-LENGTH: 488
201 | CONTENT-TYPE: text/xml; charset="utf-8"
202 | DATE: Sat, 11 Jan 2014 07:17:12 GMT
203 | EXT:
204 | SERVER: Wireless N Router WR845N, UPnP/1.0
205 |
206 |
207 |
208 |
209 | SOAP-ENV:Client
210 | UPnPError
211 |
212 |
213 | 402
214 | Invalid NewPortMappingIndex
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 | 获得外网ip地址 请求
223 |
224 | POST /ipc HTTP/1.1
225 | Content-Type: text/xml
226 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"
227 | Connection: Close
228 | User-Agent: Java/1.7.0_45
229 | Host: 192.168.1.1:1900
230 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
231 | Content-Length: 325
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 | 获得外网ip地址 回复
243 |
244 | HTTP/1.1 200 OK
245 | CONNECTION: close
246 | SERVER: Wireless N Router WR845N, UPnP/1.0
247 | CONTENT-LENGTH: 402
248 | CONTENT-TYPE: text/xml; charset="utf-8"
249 |
250 |
251 |
252 |
253 |
254 | 100.64.71.128
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | 得到明确的端口映射列表 请求
265 |
266 | POST /ipc HTTP/1.1
267 | Content-Type: text/xml
268 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetSpecificPortMappingEntry"
269 | Connection: Close
270 | User-Agent: Java/1.7.0_45
271 | Host: 192.168.1.1:1900
272 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
273 | Content-Length: 439
274 |
275 |
276 |
277 |
278 |
279 | 6991
280 | TCP
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | 回复
289 |
290 | HTTP/1.1 500 Internal Server Error
291 | CONNECTION: close
292 | CONTENT-LENGTH: 481
293 | CONTENT-TYPE: text/xml; charset="utf-8"
294 | DATE: Sat, 11 Jan 2014 07:17:12 GMT
295 | EXT:
296 | SERVER: Wireless N Router WR845N, UPnP/1.0
297 |
298 |
299 |
300 |
301 | SOAP-ENV:Client
302 | UPnPError
303 |
304 |
305 | 402
306 | Invalid ExternalPort
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 | 添加端口映射 请求
316 |
317 | POST /ipc HTTP/1.1
318 | Content-Type: text/xml
319 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
320 | Connection: Close
321 | User-Agent: Java/1.7.0_45
322 | Host: 192.168.1.1:1900
323 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
324 | Content-Length: 627
325 |
326 |
327 |
328 |
329 |
330 | 6991
331 | 6991
332 | TCP
333 | 1
334 | 192.168.1.110
335 | 0
336 | test
337 |
338 |
339 |
340 |
341 |
342 | 回复
343 |
344 | HTTP/1.1 200 OK
345 | CONNECTION: close
346 | SERVER: Wireless N Router WR845N, UPnP/1.0
347 | CONTENT-LENGTH: 332
348 | CONTENT-TYPE: text/xml; charset="utf-8"
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 | 删除端口映射 请求
358 |
359 | POST /ipc HTTP/1.1
360 | Content-Type: text/xml
361 | SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"
362 | Connection: Close
363 | User-Agent: Java/1.7.0_45
364 | Host: 192.168.1.1:1900
365 | Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
366 | Content-Length: 419
367 |
368 |
369 |
370 |
371 |
372 | 6991
373 | TCP
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 | 回复
382 |
383 | HTTP/1.1 200 OK
384 | CONNECTION: close
385 | SERVER: Wireless N Router WR845N, UPnP/1.0
386 | CONTENT-LENGTH: 338
387 | CONTENT-TYPE: text/xml; charset="utf-8"
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/prestonTao/upnp
2 |
3 | go 1.18
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prestonTao/upnp/f141651daac611e566187863c816019da45f4838/go.sum
--------------------------------------------------------------------------------
/message.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "bytes"
5 | )
6 |
7 | type Node struct {
8 | Name string
9 | Content string
10 | Attr map[string]string
11 | Child []Node
12 | }
13 |
14 | func (n *Node) AddChild(node Node) {
15 | n.Child = append(n.Child, node)
16 | }
17 | func (n *Node) BuildXML() string {
18 | buf := bytes.NewBufferString("<")
19 | buf.WriteString(n.Name)
20 | for key, value := range n.Attr {
21 | buf.WriteString(" ")
22 | buf.WriteString(key + "=" + value)
23 | }
24 | buf.WriteString(">" + n.Content)
25 |
26 | for _, node := range n.Child {
27 | buf.WriteString(node.BuildXML())
28 | }
29 | buf.WriteString("" + n.Name + ">")
30 | return buf.String()
31 | }
32 |
--------------------------------------------------------------------------------
/upnp.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | // "fmt"
5 | "errors"
6 | "log"
7 | "sync"
8 | )
9 |
10 | /*
11 | * 得到网关
12 | */
13 |
14 | //对所有的端口进行管理
15 | type MappingPortStruct struct {
16 | lock *sync.Mutex
17 | mappingPorts map[string][][]int
18 | }
19 |
20 | //添加一个端口映射记录
21 | //只对映射进行管理
22 | func (this *MappingPortStruct) addMapping(localPort, remotePort int, protocol string) {
23 |
24 | this.lock.Lock()
25 | defer this.lock.Unlock()
26 | if this.mappingPorts == nil {
27 | one := make([]int, 0)
28 | one = append(one, localPort)
29 | two := make([]int, 0)
30 | two = append(two, remotePort)
31 | portMapping := [][]int{one, two}
32 | this.mappingPorts = map[string][][]int{protocol: portMapping}
33 | return
34 | }
35 | portMapping := this.mappingPorts[protocol]
36 | if portMapping == nil {
37 | one := make([]int, 0)
38 | one = append(one, localPort)
39 | two := make([]int, 0)
40 | two = append(two, remotePort)
41 | this.mappingPorts[protocol] = [][]int{one, two}
42 | return
43 | }
44 | one := portMapping[0]
45 | two := portMapping[1]
46 | one = append(one, localPort)
47 | two = append(two, remotePort)
48 | this.mappingPorts[protocol] = [][]int{one, two}
49 | }
50 |
51 | //删除一个映射记录
52 | //只对映射进行管理
53 | func (this *MappingPortStruct) delMapping(remotePort int, protocol string) {
54 | this.lock.Lock()
55 | defer this.lock.Unlock()
56 | if this.mappingPorts == nil {
57 | return
58 | }
59 | tmp := MappingPortStruct{lock: new(sync.Mutex)}
60 | mappings := this.mappingPorts[protocol]
61 | for i := 0; i < len(mappings[0]); i++ {
62 | if mappings[1][i] == remotePort {
63 | //要删除的映射
64 | break
65 | }
66 | tmp.addMapping(mappings[0][i], mappings[1][i], protocol)
67 | }
68 | this.mappingPorts = tmp.mappingPorts
69 | }
70 | func (this *MappingPortStruct) GetAllMapping() map[string][][]int {
71 | return this.mappingPorts
72 | }
73 |
74 | type Upnp struct {
75 | Active bool //这个upnp协议是否可用
76 | LocalHost string //本机ip地址
77 | GatewayInsideIP string //局域网网关ip
78 | GatewayOutsideIP string //网关公网ip
79 | OutsideMappingPort map[string]int //映射外部端口
80 | InsideMappingPort map[string]int //映射本机端口
81 | Gateway *Gateway //网关信息
82 | CtrlUrl string //控制请求url
83 | MappingPort MappingPortStruct //已经添加了的映射 {"TCP":[1990],"UDP":[1991]}
84 | }
85 |
86 | //得到本地联网的ip地址
87 | //得到局域网网关ip
88 | func (this *Upnp) SearchGateway() (err error) {
89 | defer func(err error) {
90 | if errTemp := recover(); errTemp != nil {
91 | log.Println("upnp模块报错了", errTemp)
92 | err = errTemp.(error)
93 | }
94 | }(err)
95 |
96 | if this.LocalHost == "" {
97 | this.MappingPort = MappingPortStruct{
98 | lock: new(sync.Mutex),
99 | // mappingPorts: map[string][][]int{},
100 | }
101 | this.LocalHost = GetLocalIntenetIp()
102 | }
103 | searchGateway := SearchGateway{upnp: this}
104 | if searchGateway.Send() {
105 | return nil
106 | }
107 | return errors.New("未发现网关设备")
108 | }
109 |
110 | func (this *Upnp) deviceStatus() {
111 |
112 | }
113 |
114 | //查看设备描述,得到控制请求url
115 | func (this *Upnp) deviceDesc() (err error) {
116 | if this.GatewayInsideIP == "" {
117 | if err := this.SearchGateway(); err != nil {
118 | return err
119 | }
120 | }
121 | device := DeviceDesc{upnp: this}
122 | device.Send()
123 | this.Active = true
124 | // log.Println("获得控制请求url:", this.CtrlUrl)
125 | return
126 | }
127 |
128 | //查看公网ip地址
129 | func (this *Upnp) ExternalIPAddr() (err error) {
130 | if this.CtrlUrl == "" {
131 | if err := this.deviceDesc(); err != nil {
132 | return err
133 | }
134 | }
135 | eia := ExternalIPAddress{upnp: this}
136 | eia.Send()
137 | return nil
138 | // log.Println("获得公网ip地址为:", this.GatewayOutsideIP)
139 | }
140 |
141 | //添加一个端口映射
142 | func (this *Upnp) AddPortMapping(localPort, remotePort int, protocol string) (err error) {
143 | defer func(err error) {
144 | if errTemp := recover(); errTemp != nil {
145 | log.Println("upnp模块报错了", errTemp)
146 | err = errTemp.(error)
147 | }
148 | }(err)
149 | if this.GatewayOutsideIP == "" {
150 | if err := this.ExternalIPAddr(); err != nil {
151 | return err
152 | }
153 | }
154 | addPort := AddPortMapping{upnp: this}
155 | if issuccess := addPort.Send(localPort, remotePort, protocol); issuccess {
156 | this.MappingPort.addMapping(localPort, remotePort, protocol)
157 | // log.Println("添加一个端口映射:protocol:", protocol, "local:", localPort, "remote:", remotePort)
158 | return nil
159 | } else {
160 | this.Active = false
161 | // log.Println("添加一个端口映射失败")
162 | return errors.New("添加一个端口映射失败")
163 | }
164 | }
165 |
166 | func (this *Upnp) DelPortMapping(remotePort int, protocol string) bool {
167 | delMapping := DelPortMapping{upnp: this}
168 | issuccess := delMapping.Send(remotePort, protocol)
169 | if issuccess {
170 | this.MappingPort.delMapping(remotePort, protocol)
171 | log.Println("删除了一个端口映射: remote:", remotePort)
172 | }
173 | return issuccess
174 | }
175 |
176 | //回收端口
177 | func (this *Upnp) Reclaim() {
178 | mappings := this.MappingPort.GetAllMapping()
179 | tcpMapping, ok := mappings["TCP"]
180 | if ok {
181 | for i := 0; i < len(tcpMapping[0]); i++ {
182 | this.DelPortMapping(tcpMapping[1][i], "TCP")
183 | }
184 | }
185 | udpMapping, ok := mappings["UDP"]
186 | if ok {
187 | for i := 0; i < len(udpMapping[0]); i++ {
188 | this.DelPortMapping(udpMapping[0][i], "UDP")
189 | }
190 | }
191 | }
192 |
193 | func (this *Upnp) GetAllMapping() map[string][][]int {
194 | return this.MappingPort.GetAllMapping()
195 | }
196 |
--------------------------------------------------------------------------------