作者: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 Access Protocol 简单对象访问协议)
(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.1rn\" +
\"HOST: 239.255.255.250:1900rn\" +
\"ST: ssdp:allrn\" +
\"MAN: \"ssdp:discover\"rn\" +
\"MX: 2rnrn\")
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 := \"rnlocation:\"
answer = strings.ToLower(answer)
locIndex := strings.Index(answer, locString)
if locIndex < 0 {
continue
}
loc := answer[locIndex+len(locString):]
endIndex := strings.Index(loc, \"rn\")
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, de ion string, timeout int) (mappedExternalPort int, err error) {
// A single concatenation would break ARM compilation.
message := \"<u:AddPortMapping ns:u=\"urn:\" + n.urnDomain + \":service:WANIPConnection:1\">rn\" +
\"<NewRemoteHost></NewRemoteHost><NewExternalPort>\" + strconv.Itoa(externalPort)
message += \"</NewExternalPort><NewProtocol>\" + protocol + \"</NewProtocol>\"
message += \"<NewInternalPort>\" + strconv.Itoa(internalPort) + \"</NewInternalPort>\" +
\"<NewInternalClient>\" + n.ourIP + \"</NewInternalClient>\" +
\"<NewEnabled>1</NewEnabled><NewPortMappingDe ion>\"
message += de ion +
\"</NewPortMappingDe ion><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 ns:u=\"urn:\" + n.urnDomain + \":service:WANIPConnection:1\">rn\" +
\"<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 ns:u=\"urn:\" + n.urnDomain + \":service:WANIPConnection:1\">rn\" +
\"</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)
.NewDecoder(reader).Decode(&envelope)
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
if err != nil {
return
}
return
} 继续阅读与本文标签相同的文章
-
基于融合计算?蚂蚁金服的在线机器学习是如何做的 | 9月19号栖夜读
2026-05-18栏目: 教程
-
从零开始入门 K8s| 详解 Pod 及容器设计模式
2026-05-18栏目: 教程
-
研发效能提升 36 计第二课:照亮问题,效能提升从可视化交付过程开始
2026-05-18栏目: 教程
-
如何加快 Node.js 应用的启动速度
2026-05-18栏目: 教程
-
生存还是毁灭?一文读懂挖矿木马的战略战术 | 开发者必读(067期)
2026-05-18栏目: 教程
