Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

bytom源码分析-P2P网络-地址簿

excel436
100 0 0
简介8 c: [+ ?$ U# K4 N, }) m0 P& M
https://github.com/Bytom/bytom! {$ T% R' u  s1 C: L) i1 }: w$ l
本章介绍bytom代码P2P网络中addrbook地址簿( i4 V/ d: x% K
' u+ l) [; {7 o/ @
作者使用MacOS操作系统,其他平台也大同小异
- I+ a$ q2 U5 k. x# m

/ }, f+ y/ y' H, n
8 Z0 H2 w! r5 E0 S, |Golang Version: 1.82 v" }% N! R$ u1 u
* G; e! e5 {) c. n# n
addrbook介绍; h& u. ^  g  A% j0 o( |
addrbook用于存储P2P网络中保留最近的对端节点地址
! F8 {6 H0 e3 j/ W1 C9 \  C在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
7 U% i/ M' f& q- G地址簿格式" K7 \  ^/ c! y
  1. ** ~/Library/Bytom/addrbook.json **& v7 g9 [. R5 d# U( |
  2. {1 ^6 V* \- M9 X
  3.     "Key": "359be6d08bc0c6e21c84bbb2",& ?1 k0 m0 M) ]0 k) \4 a
  4.     "Addrs": [
    3 P( x. V6 T+ A2 x% E4 C7 i
  5.         {: h( x0 c4 W5 M, @: i
  6.             "Addr": {
    # ~, t# y* @/ [% J( E
  7.                 "IP": "122.224.11.144",
    ) j, t( {. R7 M3 a2 G+ K
  8.                 "Port": 46657! ~1 Y+ f7 U+ y2 p  @
  9.             },
    2 i4 E4 V) t0 w5 [0 E2 g( d1 J1 ^
  10.             "Src": {. D  _/ m8 x1 n8 c4 P8 S3 o% I4 F
  11.                 "IP": "198.74.61.131",$ P9 l; M% u0 Y3 U8 C# n
  12.                 "Port": 466575 a" T' I' T5 \1 b# Y8 G
  13.             },' A4 V# j. {! X3 I: _; |
  14.             "Attempts": 0,0 f5 T2 g; n  {! T4 u
  15.             "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",
    + F3 y5 U& f8 i
  16.             "LastSuccess": "0001-01-01T00:00:00Z",
    1 O# s% c, E+ z3 h( O) [5 j
  17.             "BucketType": 1,
    / F8 g- G+ `( g8 L& ]9 u
  18.             "Buckets": [6 d0 U( C- q0 L' v% t# i
  19.                 181,
    % r& u$ q$ V* C' M* ~6 B2 `
  20.                 10- ~' `+ v: U& Y' ~/ k3 O+ g
  21.             ]& w" e! Y$ o. Q( _
  22.         }+ L, n& b# T+ ^! H; f5 d8 z$ O
  23.     ]/ ]0 B+ M  G0 {* n
  24. }
复制代码

$ |2 K& J& u0 h* \) p3 e% A地址类型
" J( X2 G8 Z5 C. o) {1 j4 t在addrbook中存储的地址有两种:
" ?! B% ]- x2 a+ }* v( {4 O( v8 c
  1. ** p2p/addrbook.go **6 J# `1 j9 V! ~6 c. T
  2. const (
    ( K" n" G4 _* a5 }4 P/ V
  3.         bucketTypeNew = 0x01  // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
    * {* N1 D2 v3 R! b: L
  4.         bucketTypeOld = 0x02  // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个* N; i5 z  p, L* m
  5. )
复制代码
4 b* |; w! d" n8 Y
. q# `4 Z: I" J; {+ Q
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题2 n: r6 o9 G# k, q4 ^+ u
4 e6 z+ W8 U6 w% `& A" z1 H
地址簿相关结构体0 D1 _) D6 a0 e/ t; v1 i
地址簿1 g5 T) H2 x! ?$ K8 }
  1. type AddrBook struct {
    ) b* V1 n" m5 H; L
  2.         cmn.BaseService
    9 g  i# u1 G) o9 u, i
  3.         mtx               sync.Mutex7 m- B8 Q5 s0 T) J5 x5 H
  4.         filePath          string  // 地址簿路径' W! A/ A, G8 e( U
  5.         routabilityStrict bool  // 是否可路由,默认为true
    0 o- Z! L% r$ H4 ~
  6.         rand              *rand.Rand 4 K3 d" ?( [# J- y! }: b) }
  7.         key               string  // 地址簿标识,用于计算addrNew和addrOld的索引
      B" Y) n8 w; o
  8.         ourAddrs          map[string]*NetAddress  // 存储本地网络地址,用于添加p2p地址时做排除使用
    ! a* T" |5 @" L2 R
  9.         addrLookup        map[string]*knownAddress // 存储新、旧地址集,用于查询
    - d$ [; f6 H1 j/ w
  10.         addrNew           []map[string]*knownAddress // 存储新地址
    ; H5 N; R' h" n" |' R
  11.         addrOld           []map[string]*knownAddress // 存储旧地址- B# b4 Q  p$ w$ n
  12.         wg                sync.WaitGroup* s: u' l! I& }+ w
  13.         nOld              int // 旧地址数量
    ; x5 @; d7 \, D& ~
  14.         nNew              int // 新地址数量! _' j9 h4 Z( ~1 k8 U
  15. }
复制代码

( z/ N  o2 y' h已知地址" O5 c3 D/ I3 v7 o/ Q) [, B
  1. type knownAddress struct {2 W) S. A9 T/ A% d' r
  2.         Addr        *NetAddress // 已知peer的addr
      k) F/ \( A9 o8 Q& [1 t( _: n9 }0 [
  3.         Src         *NetAddress // 已知peer的addr的来源addr
    - H- a. B6 i* k6 m  Y. b5 D
  4.         Attempts    int32 // 连接peer的重试次数
    2 }6 S# F) R& k) q/ {$ k
  5.         LastAttempt time.Time // 最近一次尝试连接的时间  I" E3 d/ X5 O: S3 o: l6 C! D( y
  6.         LastSuccess time.Time // 最近一次尝试成功连接的时间
    5 z, S1 S- D9 [2 Q) ^! l0 g
  7.         BucketType  byte // 地址的类型(表示可靠地址或不可靠地址)
    - f+ n2 f8 G/ ?
  8.         Buckets     []int // 当前addr所属的buckets
    , v  |% w5 s; {  l" W: Q' U1 V9 {
  9. }
复制代码

+ f( a5 J& b) z; U3 T- o3 D& YroutabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
6 o/ o3 C9 _% B% O. f) m6 B$ V8 b初始化地址簿( M$ R+ J( Q1 q" G. `: b( n
  1. // NewAddrBook creates a new address book.
    + d: m# ~4 h0 ], @' ^
  2. // Use Start to begin processing asynchronous address updates.0 b0 N+ x0 g* w% e; w" f) k2 K
  3. func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
    3 S* X: z' l% F1 t) s; X% `
  4.         am := &AddrBook{
    * E6 A' |4 \9 i* A1 E
  5.                 rand:              rand.New(rand.NewSource(time.Now().UnixNano())),/ L" e' ?. H* T. p
  6.                 ourAddrs:          make(map[string]*NetAddress),; h9 n/ u( H7 |5 K) O4 x6 a
  7.                 addrLookup:        make(map[string]*knownAddress),3 R1 }" ?, v6 T# J) W
  8.                 filePath:          filePath,4 ]! x5 X& F5 F8 q; Q
  9.                 routabilityStrict: routabilityStrict,
    + n7 Y6 |$ z& U3 p
  10.         }
    # b9 S0 s  h2 ?0 i3 y. h) R$ a
  11.         am.init(); y/ u. o3 t, V  w4 ]$ g, T. c# V
  12.         am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
    # E- P& Y( I% @# ?+ g! Q0 N' B
  13.         return am
    4 Y% d" `( e$ J. j7 M4 R) c
  14. }) n& {; p. w8 |$ P7 O8 P: S/ Y2 G
  15. // When modifying this, don't forget to update loadFromFile()' Y( Q9 ]& j) A# L( i3 k6 Q& D+ p; p
  16. func (a *AddrBook) init() {
    " u( x2 o: R* a
  17.   // 地址簿唯一标识
    . R, {! e9 k) A
  18.         a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits4 c% K& N" n- x+ N7 P
  19.         // New addr buckets, 默认为256个大小
    * C" W' Z2 q4 |8 J" j# N7 S
  20.         a.addrNew = make([]map[string]*knownAddress, newBucketCount)
    ; q/ O/ L6 g. j7 {  |
  21.         for i := range a.addrNew {
    4 C/ K2 `+ o1 |, `7 @( T3 r
  22.                 a.addrNew<i> = make(map[string]*knownAddress)
    5 z$ Y8 `: B/ P/ n3 l
  23.         }
    4 s$ T: M1 m: e
  24.         // Old addr buckets,默认为64个大小
    2 A* n9 F4 d/ b3 B
  25.         a.addrOld = make([]map[string]*knownAddress, oldBucketCount)5 B- _, g5 W! B
  26.         for i := range a.addrOld {/ @% E# S; g2 N# z6 o6 F& V
  27.                 a.addrOld<i> = make(map[string]*knownAddress)
    : P* |0 k5 |- `8 j) ~) f
  28.         }
    6 Z  K$ Z, s, s$ j/ F: X; y
  29. }</i></i>
复制代码

8 H& x$ G4 M% v/ {+ T/ Z3 I9 Jbytomd启动时加载本地地址簿; {. b$ I% w! c& M0 A, ^
loadFromFile在bytomd启动时,首先会加载本地的地址簿# E( n2 c& R( D2 g
  1. // OnStart implements Service.4 N% V5 W! S5 F1 _
  2. func (a *AddrBook) OnStart() error {4 g2 q/ |* v) i4 C2 t
  3.         a.BaseService.OnStart()9 _9 J( g" T6 p8 `) |6 |) o
  4.         a.loadFromFile(a.filePath)
    , [9 F7 z9 m5 S
  5.         a.wg.Add(1). F& `$ H% j: Z- j1 Q" Y
  6.         go a.saveRoutine()+ \  G: v. o  a! M' V  e
  7.         return nil& f" S$ N! v( f$ W
  8. }' a6 l% C" L/ i1 L" T* g
  9. // Returns false if file does not exist.7 m% i5 @4 b/ }' ]3 q# R  o+ {# u
  10. // cmn.Panics if file is corrupt.
    , |" H4 f( O" G5 S* ^
  11. func (a *AddrBook) loadFromFile(filePath string) bool {
    3 `3 i" c* a  c6 w" S
  12.         // If doesn't exist, do nothing.
    & v) ]% _6 L, ]  o
  13.         // 如果本地地址簿不存在则直接返回1 B% z2 _) E8 e7 ]: V, h6 o# k
  14.         _, err := os.Stat(filePath)2 u+ P4 q* ~: R) v4 e, g
  15.         if os.IsNotExist(err) {! s  m0 b4 ?8 k9 W( g; S
  16.                 return false1 x4 f8 i) [: @
  17.         }% R1 r5 E% F! i# h
  18.   // 加载地址簿json内容* u6 L. o& u- K& k# ^! @2 V. ]
  19.         // Load addrBookJSON{}9 \& _, r) f8 [& P6 g
  20.         r, err := os.Open(filePath)
    2 z4 ^" g1 U; N; T  S' m% l/ U8 P5 Z9 P
  21.         if err != nil {, ]  A9 ~# X) ^+ Y- _: Q
  22.                 cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
    0 d$ S5 q# r/ k* _
  23.         }0 A$ e, w* E0 I0 y+ H4 \* E
  24.         defer r.Close()
    1 o8 l  z8 |- v- A
  25.         aJSON := &addrBookJSON{}3 m. N& q, g" l
  26.         dec := json.NewDecoder(r)2 s) e* U) z: o! s/ n, Q
  27.         err = dec.Decode(aJSON)
    7 P: |4 ]8 ~5 r% i
  28.         if err != nil {
    + q+ V/ Q) u$ l9 |: s/ @1 C
  29.                 cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)); x7 ^7 K$ m. ~  Q; v/ Z4 Q
  30.         }
    : O6 R: i4 w" @5 @' q' |! Y
  31.   // 填充addrNew、addrOld等0 V4 U8 b! B2 I' u8 S2 }# {8 n* D
  32.         // Restore all the fields...$ l  g& k% B$ X6 `- |! V
  33.         // Restore the key, d  \; ~) P% e8 Z2 V8 \
  34.         a.key = aJSON.Key
    1 K7 c  c8 m' J
  35.         // Restore .addrNew & .addrOld
    / }. p+ H- O# v
  36.         for _, ka := range aJSON.Addrs {) }1 l9 n" |) D; `; j# z& M
  37.                 for _, bucketIndex := range ka.Buckets {
    9 U9 Q4 ]! O" b+ I6 s/ y
  38.                         bucket := a.getBucket(ka.BucketType, bucketIndex)) A% M# q$ {' }, ~% i( O4 ?
  39.                         bucket[ka.Addr.String()] = ka
    ( _9 L2 `* w" A5 b& G; ~
  40.                 }# K  }  A: U, {' p* @' U
  41.                 a.addrLookup[ka.Addr.String()] = ka8 h! |& R! Z2 Z/ c4 q2 }  W
  42.                 if ka.BucketType == bucketTypeNew {
    6 T/ h. N/ o  ?! v# V0 f
  43.                         a.nNew++
    & r! K- |3 {5 V( E) Z) }* ~
  44.                 } else {0 j/ M: k& g5 z" J# ~" I) |
  45.                         a.nOld+++ T& v7 g4 d0 B' Y, p" _
  46.                 }, I6 D$ L. Z; e4 _5 f, Y) d
  47.         }' i) j7 e' o7 }: ]: A% w
  48.         return true
    3 R. g1 c  p( s: D8 j+ Q" e0 O5 u
  49. }
复制代码

# P2 |+ X. }* n, B7 a/ @定时更新地址簿
: R: g; D1 F4 B. `0 d1 r* Fbytomd会定时更新本地地址簿,默认2分钟一次
" y; A! X, {" I6 t
  1. func (a *AddrBook) saveRoutine() {
    0 [# d1 N$ H5 L/ F7 `8 Z
  2.         dumpAddressTicker := time.NewTicker(dumpAddressInterval)$ U3 p6 M$ R' ?2 ~  w8 _; a
  3. out:  C2 H" m# O$ ]
  4.         for {
    / B3 v4 M6 c( \
  5.                 select {: ]. v3 H; U' w6 y8 N7 V  d: h, I  R
  6.                 case
复制代码
  u, ^' O8 \! O/ q$ I; |2 [
添加新地址
' @2 B! [7 p$ ?. ]当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中6 Q( z8 C3 D  i1 u2 B
  1. func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {# t5 K3 a0 O& }9 I: f2 J
  2.         a.mtx.Lock()% y6 L% Z9 R$ }1 L
  3.         defer a.mtx.Unlock()7 I  |" O1 K/ s0 J5 e* m
  4.         log.WithFields(log.Fields{9 x5 B- m7 q: }6 n) i3 s
  5.                 "addr": addr,
    9 v' q4 M, C$ I* Z8 b1 w9 O& P
  6.                 "src":  src,
    " D& i( G  O/ Y# \) }: c
  7.         }).Debug("Add address to book")
    ) @3 @+ V+ F9 X
  8.         a.addAddress(addr, src)
    & R0 w/ b0 D8 T  ]7 q, q: Z
  9. }
    ( d; C7 p/ N6 h0 A+ h# e' ^" v9 _6 |
  10. func (a *AddrBook) addAddress(addr, src *NetAddress) {( G/ c4 W" r% G9 b3 j
  11.         // 验证地址是否为可路由地址) `! ~3 C: k6 Z6 Y( ?3 h$ V
  12.         if a.routabilityStrict && !addr.Routable() {  ?4 @" `% f5 a. ?
  13.                 log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
    : _- _" y7 r4 n9 o
  14.                 return
    ( d2 l$ {* s( }2 ^6 V( ^& s
  15.         }, |, t2 F" k' C9 K& y. P6 }4 o  F
  16.         // 验证地址是否为本地节点地址8 e3 G' p/ Q% d
  17.         if _, ok := a.ourAddrs[addr.String()]; ok {
    2 q1 S- W/ M/ u  T+ Q
  18.                 // Ignore our own listener address.
    - O& R$ U2 I; B8 @: a4 T& E# F- X
  19.                 return
    2 G( G# }9 A' Q$ s1 ^) o1 u8 x  d
  20.         }, g5 q1 v$ k) v+ \) A
  21.         // 验证地址是否存在地址集中
    % [- F  S' S- y0 K/ j- N+ s$ y
  22.         // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
    8 I7 ]! [* a+ n9 r3 l& }
  23.         // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
    0 r" X( e* b5 f
  24.         ka := a.addrLookup[addr.String()]; ?) c$ u) l2 C  k, h0 H. v
  25.         if ka != nil {
    : v0 n0 h' `2 f$ i
  26.                 // Already old.+ Z9 Q/ u" ^3 q
  27.                 if ka.isOld() {, n8 }6 v. t3 J6 C6 l
  28.                         return/ Z; Z4 `7 E: `; x+ e
  29.                 }- D$ R$ g3 F% n/ X
  30.                 // Already in max new buckets.
    - t7 M, ]2 S0 ?' r
  31.                 if len(ka.Buckets) == maxNewBucketsPerAddress {/ k( Q3 b0 z; D9 _& L4 h
  32.                         return
    / Q$ C! H# q. a* c9 f' J/ }
  33.                 }
    ) r" F& h( M1 _% H( h  l7 X9 I6 B
  34.                 // The more entries we have, the less likely we are to add more.# p7 q, n& l4 ?( x. j3 ]
  35.                 factor := int32(2 * len(ka.Buckets))
    ; o$ [; \0 E; N7 X9 |1 k6 ^
  36.                 if a.rand.Int31n(factor) != 0 {
    * A! f7 E1 M) O2 _
  37.                         return
    % m& G; \0 ]4 \. I
  38.                 }1 b7 u# l0 ?9 i& _: K! [1 {
  39.         } else {* o  j/ E8 Y& P5 |5 t
  40.                 ka = newKnownAddress(addr, src)& N8 p7 `7 f3 N
  41.         }/ N! B% ^2 J% \6 A& }6 X3 X
  42.         // 找到该地址在地址集的索引位置并添加) V# U: N: W  X8 s. X" q2 I% c+ _
  43.         bucket := a.calcNewBucket(addr, src)
    3 D0 R8 C4 G7 x* i$ I" U! Z5 ?
  44.         a.addToNewBucket(ka, bucket)2 q3 I% e2 l, Q6 z6 q
  45.         log.Info("Added new address ", "address:", addr, " total:", a.size())# W* t& Q" e5 L3 ]4 i
  46. }
复制代码
& S& H: H  r1 \* p
选择最优节点
- M8 ~  D+ A9 l. ]. s) _9 C地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接
) v3 P: N. s  z- R2 k7 L6 mPickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲$ q$ p* o" V3 x7 I! z+ R& }+ w
根据地址评分随机选择地址可增加区块链安全性- Z! @/ y3 b. m4 X3 P; \2 l
  1. // Pick an address to connect to with new/old bias.
    / k1 a2 l% v- X# R
  2. func (a *AddrBook) PickAddress(newBias int) *NetAddress {
    $ s2 r1 a( g* u! Q9 D8 f* r
  3.         a.mtx.Lock()0 W3 K8 f  O+ ~- f1 \$ h. }
  4.         defer a.mtx.Unlock()+ A: S  O1 x2 w. p+ C
  5.         if a.size() == 0 {
      a7 ?" w3 Q: V  Y8 z
  6.                 return nil' \4 ?+ u1 Q  z& ]7 l+ d0 v9 F
  7.         }% \3 q+ _4 b$ t) V
  8.         // newBias地址分数限制在0-100分数之间
    6 J# Q" g- o# `3 ~. l+ l
  9.         if newBias > 100 {
    + l3 g% X" ?6 J
  10.                 newBias = 1006 b3 R, v7 W8 d" u9 |# P" ?
  11.         }: p. V$ d5 U, f& ^0 ~3 p- o# M
  12.         if newBias
复制代码

% s2 \7 l% ~5 g" q. B! R移除一个地址
5 ?7 j% l- q$ P& E0 K( j" x当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
: f/ ^. {) c2 E! q; Z
  1. func (a *AddrBook) MarkBad(addr *NetAddress) {
    $ Q/ c3 d$ n6 Q. P' G5 \2 y7 t( F
  2.         a.RemoveAddress(addr): s7 i& F- T% @) L0 j6 X* x4 x
  3. }
    9 r* k; ?' w) g2 Y- I
  4. // RemoveAddress removes the address from the book.
    : |5 Q* s/ X' x
  5. func (a *AddrBook) RemoveAddress(addr *NetAddress) {' b# k) _7 I, @" x
  6.         a.mtx.Lock()0 Q1 d) ]- N# H7 E
  7.         defer a.mtx.Unlock(): I9 }- v! D) x3 m
  8.         ka := a.addrLookup[addr.String()]
    4 V# U$ j2 m( {; i  |
  9.         if ka == nil {* G" Q$ G; R, j1 r3 f- q3 [! _+ @
  10.                 return. L, `; T( t/ v+ W+ j* t# ^
  11.         }# T/ N6 Q' y1 K
  12.         log.WithField("addr", addr).Info("Remove address from book")
    : R7 Y, o- |+ \- n. h
  13.         a.removeFromAllBuckets(ka)
    5 J6 K* t8 B# K$ Y. r6 A
  14. }
    1 z2 T1 u; Z  h- x' _  i
  15. func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
    , _: q5 K, r: {! a' B6 `
  16.         for _, bucketIdx := range ka.Buckets {
    " i6 q2 ?5 e0 N& B
  17.                 bucket := a.getBucket(ka.BucketType, bucketIdx)
    8 S- ?" p. P/ e# V
  18.                 delete(bucket, ka.Addr.String())
    ( U$ i# L5 L1 {- ~2 k: ]3 V8 M8 i
  19.         }
    : _* }8 d1 d1 m* J9 S, G# j
  20.         ka.Buckets = nil/ |' d' i- }: Z6 ?  l7 N) `
  21.         if ka.BucketType == bucketTypeNew {$ l( t( K, l1 N4 G$ @! m& g
  22.                 a.nNew--9 M/ U6 s, j- D; y: G8 f
  23.         } else {1 b0 r4 e% u1 s( O
  24.                 a.nOld--
    5 ]' M" D. {% `
  25.         }
    ( s  r. Q7 r4 T) W1 N' o2 [7 F5 w
  26.         delete(a.addrLookup, ka.Addr.String())
    - x/ h7 M, W! h# k6 Q4 _! l% _
  27. }
复制代码

- q, X* a6 R4 J3 z; i
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

excel436 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    7