bytom源码分析-P2P网络-upnp端口映射
V刘晨曦
发表于 2022-11-6 13:59:23
105
0
0
https://github.com/Bytom/bytom7 A& v3 O/ S3 Q# R9 D; b
本章介绍bytom代码P2P网络中upnp端口映射' w$ l' y/ K S: g2 m7 G
! D% r6 N: c7 V" M4 ]4 M
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.88 W" Y8 U" @5 v: D+ X* ?2 T
$ ~9 B% g7 ~9 G5 i
UPNP介绍
UPNP(Universal Plug and Play)通用即插即用。UPNP端口映射将一个外部端口映射到一个内网ip:port。从而实现p2p网络从外网能够穿透网关访问到内网的bytomd节点。& |$ J! N! B- s
UPNP协议
SSDP(Simple Service Discovery Protocol 简单服务发现协议)
GENA(Generic Event Notification Architecture 通用事件通知结构)
SOAP(Simple Object Access Protocol 简单对象访问协议)3 o2 w7 u9 a5 y" C1 J8 {0 O
XML(Extensible Markup Language 可扩张标记语言)
UPNP代码
** p2p/upnp/upnp.go **- B' v) v) E3 ~' v7 l8 d$ X
发现网络中支持UPNP功能的设备
从网络中发现支持UPNP功能的设备,并得到该设备的location和url等相关信息( z6 ?2 R0 b) e! U k& K. A
type upnpNAT struct {3 ~! J% \+ F1 Y0 {# {4 r0 M
serviceURL string // 设备的描述文件URL,用于得到该设备的描述信息
ourIP string // 节点本地ip地址* \0 O- m$ a5 I" Q t
urnDomain string // 设备类型
}) h) z4 a% h5 r7 Y- i( x% B+ Q
func Discover() (nat NAT, err error) {
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
if err != nil {8 C5 e+ I9 z) x" Y/ \
return3 R+ ~$ f+ L- @9 v) B
}+ ~# a9 n j7 L/ ?, X! ? G( }, `
conn, err := net.ListenPacket("udp4", ":0"): l, h+ Q+ \5 ~$ j6 N8 u; O* O
if err != nil {
return8 |* r2 ~6 K7 M* R* W: D' t. P
}7 ?7 H+ H% T h; P
socket := conn.(*net.UDPConn)) k# c: V4 n7 \' S7 k' B, L8 r
defer socket.Close()) s6 M! B, w8 g( K1 G4 _
err = socket.SetDeadline(time.Now().Add(3 * time.Second))
if err != nil {3 ^1 c( o% {. l0 W7 x5 q
return
}
st := "InternetGatewayDevice:1"
// 多播请求:M-SEARCH SSDP协议定义的发现请求。1 U6 \3 W x4 j5 Z
buf := bytes.NewBufferString(
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST: ssdp:all\r\n" +. T. a- c: ~: G# I
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n\r\n")
message := buf.Bytes(); m/ N0 `) w7 F/ O* T4 ~
answerBytes := make([]byte, 1024)
for i := 0; i
添加端口映射
向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 := "\r\n" +
"" + strconv.Itoa(externalPort), ~0 G& ? a; X0 O, k7 h" {* D# W8 `
message += "" + protocol + ""
message += "" + strconv.Itoa(internalPort) + "" +3 c& h8 w+ p% M6 v, \# [
"" + n.ourIP + "" +6 P- X3 I: k9 y8 A
"1"
message += description +9 [" u2 V* v. u3 i1 g
"" + strconv.Itoa(timeout) +
""4 `! I' U+ c @0 b M
var response *http.Response% _ ]+ I, @( @% b( w8 ~
response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close(). s+ H9 b6 ?8 |. s# C' {
}
if err != nil {
return
}5 E! u7 F. u7 N) c, j* c
// TODO: check response to see if the port was forwarded
// log.Println(message, response). d% y8 ^! Q2 @4 S7 d+ e
// JAE:
// body, err := ioutil.ReadAll(response.Body)
// fmt.Println(string(body), err)
mappedExternalPort = externalPort
_ = response
return0 S8 r; V' l3 V
}! `) t4 S' Z, I8 w
删除端口映射- X: d0 @& h/ J+ r
向upnp设备发送一条http post请求,将内部网络ip:port和外部网络ip:port删除映射关系6 q7 D& ~2 l# n1 o
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {6 Y5 Q$ \$ S* |0 @. ?5 S
message := "\r\n" +
"" + strconv.Itoa(externalPort) +
"" + protocol + "" +1 \* |% K& i4 a; C
""5 l9 z5 S) I5 Z* k/ p% a
var response *http.Response4 }5 o& w, O L+ K h# I3 R
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close()
}+ l+ Y; W, t/ }) i
if err != nil {
return, X9 V4 j' G/ Y0 B, u! w$ C# ?
}* x, W9 ~$ Z( L) x. M e
// TODO: check response to see if the port was deleted
// log.Println(message, response)9 i" _' z) e6 e3 q" t7 f
_ = response
return
}4 e1 b( G! q k; |, y3 w. b+ R2 z
获取映射后的公网地址
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {4 b$ \ A1 j& n. z0 _7 X3 c6 G
info, err := n.getExternalIPAddress()
if err != nil {
return
}9 z9 K/ u S1 r! m( W2 \
addr = net.ParseIP(info.externalIpAddress)- q' }7 d% {- _8 x1 b9 g g! P
return
}; B2 e8 n/ L9 W! ]/ s3 e- H3 H2 C
func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
message := "\r\n" +
"", [% ?: ]- ^3 d
var response *http.Response5 ~: s: w6 N' o4 e) N, G* W
response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
if response != nil {( R2 {1 X- \, L0 ?2 h3 T
defer response.Body.Close()
}
if err != nil {
return1 M) R0 a% @6 _; a# M$ S+ s5 r
}- E5 ?( z* Q. Z4 R. }
var envelope Envelope
data, err := ioutil.ReadAll(response.Body): V; }0 y( }$ Z. O6 a; T
reader := bytes.NewReader(data)
xml.NewDecoder(reader).Decode(&envelope)/ y# I% Y& f/ K2 a1 q
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
if err != nil {
return
}
return# L3 c/ q$ T3 U3 L" F4 x
}
感谢比原社区开发者Derek的辛勤写作
如果你对比原项目有兴趣请添加微信号:matrix2140 咨询,备注blockflow
成为第一个吐槽的人