bytom源码分析-P2P网络-upnp端口映射
V刘晨曦
发表于 2022-11-6 13:59:23
86
0
0
https://github.com/Bytom/bytom% k) r' m. }+ m* [8 r& b& Z) I
本章介绍bytom代码P2P网络中upnp端口映射( {# ^7 q! m3 {, K/ E' x
. y9 P) C5 ?1 Y0 V% f- [2 s# g" p
作者使用MacOS操作系统,其他平台也大同小异( I: A( E' m6 G# a5 \' z- N
9 ]- h# W. {0 u& f0 ]5 Z
: w) O/ f0 \( {3 J* O) j
Golang Version: 1.8! M! Q0 A8 {2 W0 a- w! l" q- ]
UPNP介绍3 a' F4 E: h4 v( c
UPNP(Universal Plug and Play)通用即插即用。UPNP端口映射将一个外部端口映射到一个内网ip:port。从而实现p2p网络从外网能够穿透网关访问到内网的bytomd节点。
UPNP协议0 }+ Y* Q' r1 k5 E9 m: I. v/ a" [
SSDP(Simple Service Discovery Protocol 简单服务发现协议)
GENA(Generic Event Notification Architecture 通用事件通知结构) ` [0 [. I: }+ |* ~2 J
SOAP(Simple Object Access Protocol 简单对象访问协议)
XML(Extensible Markup Language 可扩张标记语言)
UPNP代码. y$ ~- V, E8 M; l1 T; K( n6 E
** p2p/upnp/upnp.go **, u- L' Z2 g, T2 t) M3 B+ {/ o1 s) e
发现网络中支持UPNP功能的设备
从网络中发现支持UPNP功能的设备,并得到该设备的location和url等相关信息& u) t# w3 n/ c; \: u' i. g- k# ?
type upnpNAT struct {$ C4 Z: l) ^* A$ P. ^ U
serviceURL string // 设备的描述文件URL,用于得到该设备的描述信息, W% g# V- K1 e2 t# w2 D& j2 v
ourIP string // 节点本地ip地址6 R: a8 j3 I) b6 }. Q
urnDomain string // 设备类型2 u7 m. l- t3 u' l# L+ _
}
func Discover() (nat NAT, err error) {
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900"). u3 T. Y& D! k8 K
if err != nil {- J; W% j; M6 o; a1 M
return. W3 w6 z8 t" H- K
}! |- [ G0 t7 U' h0 S
conn, err := net.ListenPacket("udp4", ":0")6 |6 \" i4 Q' I0 _ W* z/ b
if err != nil {+ d1 V* c! k3 p/ D y% ~5 E
return
}: K( Z6 f$ r5 D4 h9 B5 x
socket := conn.(*net.UDPConn)3 J/ q: K: `, D
defer socket.Close()
err = socket.SetDeadline(time.Now().Add(3 * time.Second)): H; |7 t/ e+ [: Y
if err != nil {
return
}
st := "InternetGatewayDevice:1"
// 多播请求:M-SEARCH SSDP协议定义的发现请求。
buf := bytes.NewBufferString(
"M-SEARCH * HTTP/1.1\r\n" +0 ]0 O/ q% e+ Y5 s4 W1 b8 t5 q
"HOST: 239.255.255.250:1900\r\n" +
"ST: ssdp:all\r\n" +; f9 u4 m9 b$ Q
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n\r\n")" f) \! _6 P2 D- K; ~+ Q+ A0 h
message := buf.Bytes()' I& e5 j9 G9 I: t1 u2 c; f
answerBytes := make([]byte, 1024)
for i := 0; i
添加端口映射" Z4 H: d. u! Z
向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.4 @! `$ \; U6 |) l0 W. r6 C& {
message := "\r\n" +
"" + strconv.Itoa(externalPort)4 @0 g) @0 E, L% G/ H2 l- O2 T
message += "" + protocol + ""8 X. N7 a& K8 f! @$ k
message += "" + strconv.Itoa(internalPort) + "" + a, E& M1 {1 ~* H# P; k' d; y+ C3 m! b
"" + n.ourIP + "" +
"1"
message += description +! _1 c! F; s/ A; [; v6 r
"" + strconv.Itoa(timeout) +( N) f( L" r( {7 t4 O
""+ X0 M. |# R4 Q# D# R
var response *http.Response
response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)( f5 l6 P5 e3 R/ B4 M2 B6 ]
if response != nil {3 w" I3 I, w1 ]/ c3 f* _0 A, O
defer response.Body.Close()/ n( P! \# b2 ^3 g* K
}
if err != nil {* g' c p2 @7 D
return6 @, o: A0 S6 G; r; ^8 k6 I
}
// TODO: check response to see if the port was forwarded
// log.Println(message, response)0 I8 O9 J: Q5 @, ?
// JAE:
// body, err := ioutil.ReadAll(response.Body)# I7 V* D- @5 G8 |
// fmt.Println(string(body), err)
mappedExternalPort = externalPort* m: P. X$ q" Q+ M
_ = response# F8 b; j: J0 z
return
}" h/ s6 [3 {( x: p
删除端口映射
向upnp设备发送一条http post请求,将内部网络ip:port和外部网络ip:port删除映射关系
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {2 k, U- g* h* u
message := "\r\n" +
"" + strconv.Itoa(externalPort) +
"" + protocol + "" +
""& J1 c0 J$ ]9 f9 S9 k6 N; p
var response *http.Response/ |! h/ ]. p! a6 a D
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)$ O8 a! I" d3 i- @) o ^ P
if response != nil {
defer response.Body.Close()) j0 o* y7 j% I5 v2 |2 x6 }
}8 {% g0 R) t0 o4 C
if err != nil {2 C6 L6 R' G. |
return
}; ]) {+ O/ ~9 c
// TODO: check response to see if the port was deleted
// log.Println(message, response)
_ = response" N2 \0 a7 X1 j |$ t4 m( w* @
return1 [' X; F, a! g2 |
}
获取映射后的公网地址
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
info, err := n.getExternalIPAddress()/ a/ _* U, l* O) J- ?
if err != nil {
return
}( U3 h. A, u! b! h: g" C( k
addr = net.ParseIP(info.externalIpAddress)" ?: I$ h! [8 j& M
return; M! U9 P& B* u$ C1 |
}
func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {1 |- Z" @, l' y6 f1 L7 t* c0 d
message := "\r\n" +
""
var response *http.Response
response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
if response != nil {6 M1 F% @1 W& T/ ]# l' w) d
defer response.Body.Close()
}* R3 y+ _8 i8 d! D
if err != nil {
return4 Z* L! i5 q" I/ L* `& Z
}8 m! d5 G+ C! w) a* z/ v
var envelope Envelope
data, err := ioutil.ReadAll(response.Body)
reader := bytes.NewReader(data)
xml.NewDecoder(reader).Decode(&envelope): M' z4 ?4 k8 N: m% I
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}, c2 C% h* D. E* Q/ N' O
if err != nil {% N# k/ l9 d0 Z' v
return
}, u# `* `3 a q
return O1 ^ C6 k2 H1 E: o C
}
感谢比原社区开发者Derek的辛勤写作
如果你对比原项目有兴趣请添加微信号:matrix2140 咨询,备注blockflow
成为第一个吐槽的人