美文网首页
Derek解读Bytom源码-P2P网络 upnp端口映射

Derek解读Bytom源码-P2P网络 upnp端口映射

作者: 比原链Bytom | 来源:发表于2018-09-17 12:47 被阅读0次

    作者:Derek

    简介

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

    Gitee地址:https://gitee.com/BytomBlockchain/bytom

    本章介绍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 简单对象访问协议)
    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
    }
    

    相关文章

      网友评论

          本文标题:Derek解读Bytom源码-P2P网络 upnp端口映射

          本文链接:https://www.haomeiwen.com/subject/wwxlnftx.html