Derek解讀Bytom源碼-P2P網路 upnp連接埠映射

來源:互聯網
上載者:User

作者:Derek

簡介

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockc...

本章介紹bytom代碼P2P網路中upnp連接埠映射

作者使用MacOS作業系統,其他平台也大同小異

Golang Version: 1.8

UPNP介紹

UPNP(Universal Plug and Play)通用隨插即用。UPNP連接埠映射將一個外部連接埠映射到一個內網ip:port。從而實現p2p網路從外網能夠穿透網關訪問到內網的bytomd節點。

UPNP協議

SSDP(Simple Service Discovery Protocol 簡單服務發現協議)
GENA(Generic Event Notification Architecture 通用事件通知結構)
SOAP(Simple Object Access Protocol 簡易物件存取通訊協定 (SOAP))
XML(Extensible Markup Language 可擴張標記語言)

UPNP代碼

p2p/upnp/upnp.go

發現網路中支援UPNP功能的裝置

從網路中發現支援UPNP功能的裝置,並得到該裝置的location和url等相關資訊

type upnpNAT struct {    serviceURL string // 裝置的描述檔案URL,用於得到該裝置的描述資訊    ourIP      string // 節點本地ip地址    urnDomain  string // 裝置類型}func Discover() (nat NAT, err error) {    ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")    if err != nil {        return    }    conn, err := net.ListenPacket("udp4", ":0")    if err != nil {        return    }    socket := conn.(*net.UDPConn)    defer socket.Close()    err = socket.SetDeadline(time.Now().Add(3 * time.Second))    if err != nil {        return    }    st := "InternetGatewayDevice:1"    // 多播請求:M-SEARCH SSDP協議定義的發現請求。    buf := bytes.NewBufferString(        "M-SEARCH * HTTP/1.1\r\n" +            "HOST: 239.255.255.250:1900\r\n" +            "ST: ssdp:all\r\n" +            "MAN: \"ssdp:discover\"\r\n" +            "MX: 2\r\n\r\n")    message := buf.Bytes()    answerBytes := make([]byte, 1024)    for i := 0; i < 3; i++ {        // 向239.255.255.250:1900發送一條多播請求        _, err = socket.WriteToUDP(message, ssdp)        if err != nil {            return        }        // 如果從網路中發現UPNP裝置則會從239.255.255.250:1900收到響應訊息        var n int        n, _, err = socket.ReadFromUDP(answerBytes)        for {            n, _, err = socket.ReadFromUDP(answerBytes)            if err != nil {                break            }            answer := string(answerBytes[0:n])            if strings.Index(answer, st) < 0 {                continue            }            // HTTP header field names are case-insensitive.            // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2            // 獲得裝置location            locString := "\r\nlocation:"            answer = strings.ToLower(answer)            locIndex := strings.Index(answer, locString)            if locIndex < 0 {                continue            }            loc := answer[locIndex+len(locString):]            endIndex := strings.Index(loc, "\r\n")            if endIndex < 0 {                continue            }            // 獲得裝置的描述url和裝置類型            locURL := strings.TrimSpace(loc[0:endIndex])            var serviceURL, urnDomain string            serviceURL, urnDomain, err = getServiceURL(locURL)            if err != nil {                return            }            var ourIP net.IP            ourIP, err = localIPv4()            if err != nil {                return            }            nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}            return        }    }    err = errors.New("UPnP port discovery failed.")    return}

添加連接埠映射

向upnp裝置發送一條http post請求,將內部網路ip:port和外部網路ip:port做映射

func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {    // A single concatenation would break ARM compilation.    message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +        "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)    message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"    message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +        "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +        "<NewEnabled>1</NewEnabled><NewPortMappingDescription>"    message += description +        "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +        "</NewLeaseDuration></u:AddPortMapping>"    var response *http.Response    response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)    if response != nil {        defer response.Body.Close()    }    if err != nil {        return    }    // TODO: check response to see if the port was forwarded    // log.Println(message, response)    // JAE:    // body, err := ioutil.ReadAll(response.Body)    // fmt.Println(string(body), err)    mappedExternalPort = externalPort    _ = response    return}

刪除連接埠映射

向upnp裝置發送一條http post請求,將內部網路ip:port和外部網路ip:port刪除映射關係

func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {    message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +        "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +        "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +        "</u:DeletePortMapping>"    var response *http.Response    response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)    if response != nil {        defer response.Body.Close()    }    if err != nil {        return    }    // TODO: check response to see if the port was deleted    // log.Println(message, response)    _ = response    return}

擷取映射後的公網地址

func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {    info, err := n.getExternalIPAddress()    if err != nil {        return    }    addr = net.ParseIP(info.externalIpAddress)    return}func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {    message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +        "</u:GetExternalIPAddress>"    var response *http.Response    response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)    if response != nil {        defer response.Body.Close()    }    if err != nil {        return    }    var envelope Envelope    data, err := ioutil.ReadAll(response.Body)    reader := bytes.NewReader(data)    xml.NewDecoder(reader).Decode(&envelope)    info = statusInfo{envelope.Soap.ExternalIP.IPAddress}    if err != nil {        return    }    return}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.