├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── addrbook.go
├── addrbook_test.go
├── config.go
├── connection.go
├── connection_test.go
├── fuzz.go
├── glide.lock
├── glide.yaml
├── ip_range_counter.go
├── listener.go
├── listener_test.go
├── log.go
├── netaddress.go
├── netaddress_test.go
├── peer.go
├── peer_set.go
├── peer_set_test.go
├── peer_test.go
├── pex_reactor.go
├── pex_reactor_test.go
├── secret_connection.go
├── secret_connection_test.go
├── switch.go
├── switch_test.go
├── types.go
├── upnp
├── README.md
├── log.go
├── probe.go
└── upnp.go
├── util.go
└── version.go
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | .glide
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.5.0 (April 21, 2017)
4 |
5 | BREAKING CHANGES:
6 |
7 | - Remove or unexport methods from FuzzedConnection: Active, Mode, ProbDropRW, ProbDropConn, ProbSleep, MaxDelayMilliseconds, Fuzz
8 | - switch.AddPeerWithConnection is unexported and replaced by switch.AddPeer
9 | - switch.DialPeerWithAddress takes a bool, setting the peer as persistent or not
10 |
11 | FEATURES:
12 |
13 | - Persistent peers: any peer considered a "seed" will be reconnected to when the connection is dropped
14 |
15 |
16 | IMPROVEMENTS:
17 |
18 | - Many more tests and comments
19 | - Refactor configurations for less dependence on go-config. Introduces new structs PeerConfig, MConnConfig, FuzzConnConfig
20 | - New methods on peer: CloseConn, HandshakeTimeout, IsPersistent, Addr, PubKey
21 | - NewNetAddress supports a testing mode where the address defaults to 0.0.0.0:0
22 |
23 |
24 | ## 0.4.0 (March 6, 2017)
25 |
26 | BREAKING CHANGES:
27 |
28 | - DialSeeds now takes an AddrBook and returns an error: `DialSeeds(*AddrBook, []string) error`
29 | - NewNetAddressString now returns an error: `NewNetAddressString(string) (*NetAddress, error)`
30 |
31 | FEATURES:
32 |
33 | - `NewNetAddressStrings([]string) ([]*NetAddress, error)`
34 | - `AddrBook.Save()`
35 |
36 | IMPROVEMENTS:
37 |
38 | - PexReactor responsible for starting and stopping the AddrBook
39 |
40 | BUG FIXES:
41 |
42 | - DialSeeds returns an error instead of panicking on bad addresses
43 |
44 | ## 0.3.5 (January 12, 2017)
45 |
46 | FEATURES
47 |
48 | - Toggle strict routability in the AddrBook
49 |
50 | BUG FIXES
51 |
52 | - Close filtered out connections
53 | - Fixes for MakeConnectedSwitches and Connect2Switches
54 |
55 | ## 0.3.4 (August 10, 2016)
56 |
57 | FEATURES:
58 |
59 | - Optionally filter connections by address or public key
60 |
61 | ## 0.3.3 (May 12, 2016)
62 |
63 | FEATURES:
64 |
65 | - FuzzConn
66 |
67 | ## 0.3.2 (March 12, 2016)
68 |
69 | IMPROVEMENTS:
70 |
71 | - Memory optimizations
72 |
73 | ## 0.3.1 ()
74 |
75 | FEATURES:
76 |
77 | - Configurable parameters
78 |
79 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:latest
2 |
3 | RUN curl https://glide.sh/get | sh
4 |
5 | RUN mkdir -p /go/src/github.com/tendermint/go-p2p
6 | WORKDIR /go/src/github.com/tendermint/go-p2p
7 |
8 | COPY glide.yaml /go/src/github.com/tendermint/go-p2p/
9 | COPY glide.lock /go/src/github.com/tendermint/go-p2p/
10 |
11 | RUN glide install
12 |
13 | COPY . /go/src/github.com/tendermint/go-p2p
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Tendermint Go-P2P
2 | Copyright (C) 2015 Tendermint
3 |
4 |
5 |
6 | Apache License
7 | Version 2.0, January 2004
8 | https://www.apache.org/licenses/
9 |
10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
11 |
12 | 1. Definitions.
13 |
14 | "License" shall mean the terms and conditions for use, reproduction,
15 | and distribution as defined by Sections 1 through 9 of this document.
16 |
17 | "Licensor" shall mean the copyright owner or entity authorized by
18 | the copyright owner that is granting the License.
19 |
20 | "Legal Entity" shall mean the union of the acting entity and all
21 | other entities that control, are controlled by, or are under common
22 | control with that entity. For the purposes of this definition,
23 | "control" means (i) the power, direct or indirect, to cause the
24 | direction or management of such entity, whether by contract or
25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
26 | outstanding shares, or (iii) beneficial ownership of such entity.
27 |
28 | "You" (or "Your") shall mean an individual or Legal Entity
29 | exercising permissions granted by this License.
30 |
31 | "Source" form shall mean the preferred form for making modifications,
32 | including but not limited to software source code, documentation
33 | source, and configuration files.
34 |
35 | "Object" form shall mean any form resulting from mechanical
36 | transformation or translation of a Source form, including but
37 | not limited to compiled object code, generated documentation,
38 | and conversions to other media types.
39 |
40 | "Work" shall mean the work of authorship, whether in Source or
41 | Object form, made available under the License, as indicated by a
42 | copyright notice that is included in or attached to the work
43 | (an example is provided in the Appendix below).
44 |
45 | "Derivative Works" shall mean any work, whether in Source or Object
46 | form, that is based on (or derived from) the Work and for which the
47 | editorial revisions, annotations, elaborations, or other modifications
48 | represent, as a whole, an original work of authorship. For the purposes
49 | of this License, Derivative Works shall not include works that remain
50 | separable from, or merely link (or bind by name) to the interfaces of,
51 | the Work and Derivative Works thereof.
52 |
53 | "Contribution" shall mean any work of authorship, including
54 | the original version of the Work and any modifications or additions
55 | to that Work or Derivative Works thereof, that is intentionally
56 | submitted to Licensor for inclusion in the Work by the copyright owner
57 | or by an individual or Legal Entity authorized to submit on behalf of
58 | the copyright owner. For the purposes of this definition, "submitted"
59 | means any form of electronic, verbal, or written communication sent
60 | to the Licensor or its representatives, including but not limited to
61 | communication on electronic mailing lists, source code control systems,
62 | and issue tracking systems that are managed by, or on behalf of, the
63 | Licensor for the purpose of discussing and improving the Work, but
64 | excluding communication that is conspicuously marked or otherwise
65 | designated in writing by the copyright owner as "Not a Contribution."
66 |
67 | "Contributor" shall mean Licensor and any individual or Legal Entity
68 | on behalf of whom a Contribution has been received by Licensor and
69 | subsequently incorporated within the Work.
70 |
71 | 2. Grant of Copyright License. Subject to the terms and conditions of
72 | this License, each Contributor hereby grants to You a perpetual,
73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
74 | copyright license to reproduce, prepare Derivative Works of,
75 | publicly display, publicly perform, sublicense, and distribute the
76 | Work and such Derivative Works in Source or Object form.
77 |
78 | 3. Grant of Patent License. Subject to the terms and conditions of
79 | this License, each Contributor hereby grants to You a perpetual,
80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
81 | (except as stated in this section) patent license to make, have made,
82 | use, offer to sell, sell, import, and otherwise transfer the Work,
83 | where such license applies only to those patent claims licensable
84 | by such Contributor that are necessarily infringed by their
85 | Contribution(s) alone or by combination of their Contribution(s)
86 | with the Work to which such Contribution(s) was submitted. If You
87 | institute patent litigation against any entity (including a
88 | cross-claim or counterclaim in a lawsuit) alleging that the Work
89 | or a Contribution incorporated within the Work constitutes direct
90 | or contributory patent infringement, then any patent licenses
91 | granted to You under this License for that Work shall terminate
92 | as of the date such litigation is filed.
93 |
94 | 4. Redistribution. You may reproduce and distribute copies of the
95 | Work or Derivative Works thereof in any medium, with or without
96 | modifications, and in Source or Object form, provided that You
97 | meet the following conditions:
98 |
99 | (a) You must give any other recipients of the Work or
100 | Derivative Works a copy of this License; and
101 |
102 | (b) You must cause any modified files to carry prominent notices
103 | stating that You changed the files; and
104 |
105 | (c) You must retain, in the Source form of any Derivative Works
106 | that You distribute, all copyright, patent, trademark, and
107 | attribution notices from the Source form of the Work,
108 | excluding those notices that do not pertain to any part of
109 | the Derivative Works; and
110 |
111 | (d) If the Work includes a "NOTICE" text file as part of its
112 | distribution, then any Derivative Works that You distribute must
113 | include a readable copy of the attribution notices contained
114 | within such NOTICE file, excluding those notices that do not
115 | pertain to any part of the Derivative Works, in at least one
116 | of the following places: within a NOTICE text file distributed
117 | as part of the Derivative Works; within the Source form or
118 | documentation, if provided along with the Derivative Works; or,
119 | within a display generated by the Derivative Works, if and
120 | wherever such third-party notices normally appear. The contents
121 | of the NOTICE file are for informational purposes only and
122 | do not modify the License. You may add Your own attribution
123 | notices within Derivative Works that You distribute, alongside
124 | or as an addendum to the NOTICE text from the Work, provided
125 | that such additional attribution notices cannot be construed
126 | as modifying the License.
127 |
128 | You may add Your own copyright statement to Your modifications and
129 | may provide additional or different license terms and conditions
130 | for use, reproduction, or distribution of Your modifications, or
131 | for any such Derivative Works as a whole, provided Your use,
132 | reproduction, and distribution of the Work otherwise complies with
133 | the conditions stated in this License.
134 |
135 | 5. Submission of Contributions. Unless You explicitly state otherwise,
136 | any Contribution intentionally submitted for inclusion in the Work
137 | by You to the Licensor shall be under the terms and conditions of
138 | this License, without any additional terms or conditions.
139 | Notwithstanding the above, nothing herein shall supersede or modify
140 | the terms of any separate license agreement you may have executed
141 | with Licensor regarding such Contributions.
142 |
143 | 6. Trademarks. This License does not grant permission to use the trade
144 | names, trademarks, service marks, or product names of the Licensor,
145 | except as required for reasonable and customary use in describing the
146 | origin of the Work and reproducing the content of the NOTICE file.
147 |
148 | 7. Disclaimer of Warranty. Unless required by applicable law or
149 | agreed to in writing, Licensor provides the Work (and each
150 | Contributor provides its Contributions) on an "AS IS" BASIS,
151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
152 | implied, including, without limitation, any warranties or conditions
153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
154 | PARTICULAR PURPOSE. You are solely responsible for determining the
155 | appropriateness of using or redistributing the Work and assume any
156 | risks associated with Your exercise of permissions under this License.
157 |
158 | 8. Limitation of Liability. In no event and under no legal theory,
159 | whether in tort (including negligence), contract, or otherwise,
160 | unless required by applicable law (such as deliberate and grossly
161 | negligent acts) or agreed to in writing, shall any Contributor be
162 | liable to You for damages, including any direct, indirect, special,
163 | incidental, or consequential damages of any character arising as a
164 | result of this License or out of the use or inability to use the
165 | Work (including but not limited to damages for loss of goodwill,
166 | work stoppage, computer failure or malfunction, or any and all
167 | other commercial damages or losses), even if such Contributor
168 | has been advised of the possibility of such damages.
169 |
170 | 9. Accepting Warranty or Additional Liability. While redistributing
171 | the Work or Derivative Works thereof, You may choose to offer,
172 | and charge a fee for, acceptance of support, warranty, indemnity,
173 | or other liability obligations and/or rights consistent with this
174 | License. However, in accepting such obligations, You may act only
175 | on Your own behalf and on Your sole responsibility, not on behalf
176 | of any other Contributor, and only if You agree to indemnify,
177 | defend, and hold each Contributor harmless for any liability
178 | incurred by, or claims asserted against, such Contributor by reason
179 | of your accepting any such warranty or additional liability.
180 |
181 | END OF TERMS AND CONDITIONS
182 |
183 | Licensed under the Apache License, Version 2.0 (the "License");
184 | you may not use this file except in compliance with the License.
185 | You may obtain a copy of the License at
186 |
187 | https://www.apache.org/licenses/LICENSE-2.0
188 |
189 | Unless required by applicable law or agreed to in writing, software
190 | distributed under the License is distributed on an "AS IS" BASIS,
191 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
192 | See the License for the specific language governing permissions and
193 | limitations under the License.
194 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `tendermint/go-p2p`
2 |
3 | [](https://circleci.com/gh/tendermint/go-p2p)
4 |
5 | `tendermint/go-p2p` provides an abstraction around peer-to-peer communication.
6 |
7 | ## Peer/MConnection/Channel
8 |
9 | Each peer has one `MConnection` (multiplex connection) instance.
10 |
11 | __multiplex__ *noun* a system or signal involving simultaneous transmission of
12 | several messages along a single channel of communication.
13 |
14 | Each `MConnection` handles message transmission on multiple abstract communication
15 | `Channel`s. Each channel has a globally unique byte id.
16 | The byte id and the relative priorities of each `Channel` are configured upon
17 | initialization of the connection.
18 |
19 | There are two methods for sending messages:
20 | ```go
21 | func (m MConnection) Send(chID byte, msg interface{}) bool {}
22 | func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
23 | ```
24 |
25 | `Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued
26 | for the channel with the given id byte `chID`. The message `msg` is serialized
27 | using the `tendermint/wire` submodule's `WriteBinary()` reflection routine.
28 |
29 | `TrySend(chID, msg)` is a nonblocking call that returns false if the channel's
30 | queue is full.
31 |
32 | `Send()` and `TrySend()` are also exposed for each `Peer`.
33 |
34 | ## Switch/Reactor
35 |
36 | The `Switch` handles peer connections and exposes an API to receive incoming messages
37 | on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one
38 | or more `Channels`. So while sending outgoing messages is typically performed on the peer,
39 | incoming messages are received on the reactor.
40 |
41 | ```go
42 | // Declare a MyReactor reactor that handles messages on MyChannelID.
43 | type MyReactor struct{}
44 |
45 | func (reactor MyReactor) GetChannels() []*ChannelDescriptor {
46 | return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}}
47 | }
48 |
49 | func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
50 | r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error)
51 | msgString := ReadString(r, n, err)
52 | fmt.Println(msgString)
53 | }
54 |
55 | // Other Reactor methods omitted for brevity
56 | ...
57 |
58 | switch := NewSwitch([]Reactor{MyReactor{}})
59 |
60 | ...
61 |
62 | // Send a random message to all outbound connections
63 | for _, peer := range switch.Peers().List() {
64 | if peer.IsOutbound() {
65 | peer.Send(MyChannelID, "Here's a random message")
66 | }
67 | }
68 | ```
69 |
70 | ### PexReactor/AddrBook
71 |
72 | A `PEXReactor` reactor implementation is provided to automate peer discovery.
73 |
74 | ```go
75 | book := p2p.NewAddrBook(addrBookFilePath)
76 | pexReactor := p2p.NewPEXReactor(book)
77 | ...
78 | switch := NewSwitch([]Reactor{pexReactor, myReactor, ...})
79 | ```
80 |
--------------------------------------------------------------------------------
/addrbook.go:
--------------------------------------------------------------------------------
1 | // Modified for Tendermint
2 | // Originally Copyright (c) 2013-2014 Conformal Systems LLC.
3 | // https://github.com/conformal/btcd/blob/master/LICENSE
4 |
5 | package p2p
6 |
7 | import (
8 | "encoding/binary"
9 | "encoding/json"
10 | "math"
11 | "math/rand"
12 | "net"
13 | "os"
14 | "sync"
15 | "time"
16 |
17 | . "github.com/tendermint/go-common"
18 | crypto "github.com/tendermint/go-crypto"
19 | )
20 |
21 | const (
22 | // addresses under which the address manager will claim to need more addresses.
23 | needAddressThreshold = 1000
24 |
25 | // interval used to dump the address cache to disk for future use.
26 | dumpAddressInterval = time.Minute * 2
27 |
28 | // max addresses in each old address bucket.
29 | oldBucketSize = 64
30 |
31 | // buckets we split old addresses over.
32 | oldBucketCount = 64
33 |
34 | // max addresses in each new address bucket.
35 | newBucketSize = 64
36 |
37 | // buckets that we spread new addresses over.
38 | newBucketCount = 256
39 |
40 | // old buckets over which an address group will be spread.
41 | oldBucketsPerGroup = 4
42 |
43 | // new buckets over which an source address group will be spread.
44 | newBucketsPerGroup = 32
45 |
46 | // buckets a frequently seen new address may end up in.
47 | maxNewBucketsPerAddress = 4
48 |
49 | // days before which we assume an address has vanished
50 | // if we have not seen it announced in that long.
51 | numMissingDays = 30
52 |
53 | // tries without a single success before we assume an address is bad.
54 | numRetries = 3
55 |
56 | // max failures we will accept without a success before considering an address bad.
57 | maxFailures = 10
58 |
59 | // days since the last success before we will consider evicting an address.
60 | minBadDays = 7
61 |
62 | // % of total addresses known returned by GetSelection.
63 | getSelectionPercent = 23
64 |
65 | // min addresses that must be returned by GetSelection. Useful for bootstrapping.
66 | minGetSelection = 32
67 |
68 | // max addresses returned by GetSelection
69 | // NOTE: this must match "maxPexMessageSize"
70 | maxGetSelection = 250
71 |
72 | // current version of the on-disk format.
73 | serializationVersion = 1
74 | )
75 |
76 | const (
77 | bucketTypeNew = 0x01
78 | bucketTypeOld = 0x02
79 | )
80 |
81 | // AddrBook - concurrency safe peer address manager.
82 | type AddrBook struct {
83 | BaseService
84 |
85 | mtx sync.Mutex
86 | filePath string
87 | routabilityStrict bool
88 | rand *rand.Rand
89 | key string
90 | ourAddrs map[string]*NetAddress
91 | addrLookup map[string]*knownAddress // new & old
92 | addrNew []map[string]*knownAddress
93 | addrOld []map[string]*knownAddress
94 | wg sync.WaitGroup
95 | nOld int
96 | nNew int
97 | }
98 |
99 | // NewAddrBook creates a new address book.
100 | // Use Start to begin processing asynchronous address updates.
101 | func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
102 | am := &AddrBook{
103 | rand: rand.New(rand.NewSource(time.Now().UnixNano())),
104 | ourAddrs: make(map[string]*NetAddress),
105 | addrLookup: make(map[string]*knownAddress),
106 | filePath: filePath,
107 | routabilityStrict: routabilityStrict,
108 | }
109 | am.init()
110 | am.BaseService = *NewBaseService(log, "AddrBook", am)
111 | return am
112 | }
113 |
114 | // When modifying this, don't forget to update loadFromFile()
115 | func (a *AddrBook) init() {
116 | a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
117 | // New addr buckets
118 | a.addrNew = make([]map[string]*knownAddress, newBucketCount)
119 | for i := range a.addrNew {
120 | a.addrNew[i] = make(map[string]*knownAddress)
121 | }
122 | // Old addr buckets
123 | a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
124 | for i := range a.addrOld {
125 | a.addrOld[i] = make(map[string]*knownAddress)
126 | }
127 | }
128 |
129 | // OnStart implements Service.
130 | func (a *AddrBook) OnStart() error {
131 | a.BaseService.OnStart()
132 | a.loadFromFile(a.filePath)
133 | a.wg.Add(1)
134 | go a.saveRoutine()
135 | return nil
136 | }
137 |
138 | // OnStop implements Service.
139 | func (a *AddrBook) OnStop() {
140 | a.BaseService.OnStop()
141 | }
142 |
143 | func (a *AddrBook) Wait() {
144 | a.wg.Wait()
145 | }
146 |
147 | func (a *AddrBook) AddOurAddress(addr *NetAddress) {
148 | a.mtx.Lock()
149 | defer a.mtx.Unlock()
150 | log.Info("Add our address to book", "addr", addr)
151 | a.ourAddrs[addr.String()] = addr
152 | }
153 |
154 | func (a *AddrBook) OurAddresses() []*NetAddress {
155 | addrs := []*NetAddress{}
156 | for _, addr := range a.ourAddrs {
157 | addrs = append(addrs, addr)
158 | }
159 | return addrs
160 | }
161 |
162 | // NOTE: addr must not be nil
163 | func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
164 | a.mtx.Lock()
165 | defer a.mtx.Unlock()
166 | log.Info("Add address to book", "addr", addr, "src", src)
167 | a.addAddress(addr, src)
168 | }
169 |
170 | func (a *AddrBook) NeedMoreAddrs() bool {
171 | return a.Size() < needAddressThreshold
172 | }
173 |
174 | func (a *AddrBook) Size() int {
175 | a.mtx.Lock()
176 | defer a.mtx.Unlock()
177 | return a.size()
178 | }
179 |
180 | func (a *AddrBook) size() int {
181 | return a.nNew + a.nOld
182 | }
183 |
184 | // Pick an address to connect to with new/old bias.
185 | func (a *AddrBook) PickAddress(newBias int) *NetAddress {
186 | a.mtx.Lock()
187 | defer a.mtx.Unlock()
188 |
189 | if a.size() == 0 {
190 | return nil
191 | }
192 | if newBias > 100 {
193 | newBias = 100
194 | }
195 | if newBias < 0 {
196 | newBias = 0
197 | }
198 |
199 | // Bias between new and old addresses.
200 | oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(newBias))
201 | newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias)
202 |
203 | if (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation {
204 | // pick random Old bucket.
205 | var bucket map[string]*knownAddress = nil
206 | for len(bucket) == 0 {
207 | bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]
208 | }
209 | // pick a random ka from bucket.
210 | randIndex := a.rand.Intn(len(bucket))
211 | for _, ka := range bucket {
212 | if randIndex == 0 {
213 | return ka.Addr
214 | }
215 | randIndex--
216 | }
217 | PanicSanity("Should not happen")
218 | } else {
219 | // pick random New bucket.
220 | var bucket map[string]*knownAddress = nil
221 | for len(bucket) == 0 {
222 | bucket = a.addrNew[a.rand.Intn(len(a.addrNew))]
223 | }
224 | // pick a random ka from bucket.
225 | randIndex := a.rand.Intn(len(bucket))
226 | for _, ka := range bucket {
227 | if randIndex == 0 {
228 | return ka.Addr
229 | }
230 | randIndex--
231 | }
232 | PanicSanity("Should not happen")
233 | }
234 | return nil
235 | }
236 |
237 | func (a *AddrBook) MarkGood(addr *NetAddress) {
238 | a.mtx.Lock()
239 | defer a.mtx.Unlock()
240 | ka := a.addrLookup[addr.String()]
241 | if ka == nil {
242 | return
243 | }
244 | ka.markGood()
245 | if ka.isNew() {
246 | a.moveToOld(ka)
247 | }
248 | }
249 |
250 | func (a *AddrBook) MarkAttempt(addr *NetAddress) {
251 | a.mtx.Lock()
252 | defer a.mtx.Unlock()
253 | ka := a.addrLookup[addr.String()]
254 | if ka == nil {
255 | return
256 | }
257 | ka.markAttempt()
258 | }
259 |
260 | // MarkBad currently just ejects the address. In the future, consider
261 | // blacklisting.
262 | func (a *AddrBook) MarkBad(addr *NetAddress) {
263 | a.RemoveAddress(addr)
264 | }
265 |
266 | // RemoveAddress removes the address from the book.
267 | func (a *AddrBook) RemoveAddress(addr *NetAddress) {
268 | a.mtx.Lock()
269 | defer a.mtx.Unlock()
270 | ka := a.addrLookup[addr.String()]
271 | if ka == nil {
272 | return
273 | }
274 | log.Info("Remove address from book", "addr", addr)
275 | a.removeFromAllBuckets(ka)
276 | }
277 |
278 | /* Peer exchange */
279 |
280 | // GetSelection randomly selects some addresses (old & new). Suitable for peer-exchange protocols.
281 | func (a *AddrBook) GetSelection() []*NetAddress {
282 | a.mtx.Lock()
283 | defer a.mtx.Unlock()
284 |
285 | if a.size() == 0 {
286 | return nil
287 | }
288 |
289 | allAddr := make([]*NetAddress, a.size())
290 | i := 0
291 | for _, v := range a.addrLookup {
292 | allAddr[i] = v.Addr
293 | i++
294 | }
295 |
296 | numAddresses := MaxInt(
297 | MinInt(minGetSelection, len(allAddr)),
298 | len(allAddr)*getSelectionPercent/100)
299 | numAddresses = MinInt(maxGetSelection, numAddresses)
300 |
301 | // Fisher-Yates shuffle the array. We only need to do the first
302 | // `numAddresses' since we are throwing the rest.
303 | for i := 0; i < numAddresses; i++ {
304 | // pick a number between current index and the end
305 | j := rand.Intn(len(allAddr)-i) + i
306 | allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
307 | }
308 |
309 | // slice off the limit we are willing to share.
310 | return allAddr[:numAddresses]
311 | }
312 |
313 | /* Loading & Saving */
314 |
315 | type addrBookJSON struct {
316 | Key string
317 | Addrs []*knownAddress
318 | }
319 |
320 | func (a *AddrBook) saveToFile(filePath string) {
321 | log.Info("Saving AddrBook to file", "size", a.Size())
322 |
323 | a.mtx.Lock()
324 | defer a.mtx.Unlock()
325 | // Compile Addrs
326 | addrs := []*knownAddress{}
327 | for _, ka := range a.addrLookup {
328 | addrs = append(addrs, ka)
329 | }
330 |
331 | aJSON := &addrBookJSON{
332 | Key: a.key,
333 | Addrs: addrs,
334 | }
335 |
336 | jsonBytes, err := json.MarshalIndent(aJSON, "", "\t")
337 | if err != nil {
338 | log.Error("Failed to save AddrBook to file", "err", err)
339 | return
340 | }
341 | err = WriteFileAtomic(filePath, jsonBytes, 0644)
342 | if err != nil {
343 | log.Error("Failed to save AddrBook to file", "file", filePath, "error", err)
344 | }
345 | }
346 |
347 | // Returns false if file does not exist.
348 | // Panics if file is corrupt.
349 | func (a *AddrBook) loadFromFile(filePath string) bool {
350 | // If doesn't exist, do nothing.
351 | _, err := os.Stat(filePath)
352 | if os.IsNotExist(err) {
353 | return false
354 | }
355 |
356 | // Load addrBookJSON{}
357 | r, err := os.Open(filePath)
358 | if err != nil {
359 | PanicCrisis(Fmt("Error opening file %s: %v", filePath, err))
360 | }
361 | defer r.Close()
362 | aJSON := &addrBookJSON{}
363 | dec := json.NewDecoder(r)
364 | err = dec.Decode(aJSON)
365 | if err != nil {
366 | PanicCrisis(Fmt("Error reading file %s: %v", filePath, err))
367 | }
368 |
369 | // Restore all the fields...
370 | // Restore the key
371 | a.key = aJSON.Key
372 | // Restore .addrNew & .addrOld
373 | for _, ka := range aJSON.Addrs {
374 | for _, bucketIndex := range ka.Buckets {
375 | bucket := a.getBucket(ka.BucketType, bucketIndex)
376 | bucket[ka.Addr.String()] = ka
377 | }
378 | a.addrLookup[ka.Addr.String()] = ka
379 | if ka.BucketType == bucketTypeNew {
380 | a.nNew++
381 | } else {
382 | a.nOld++
383 | }
384 | }
385 | return true
386 | }
387 |
388 | // Save saves the book.
389 | func (a *AddrBook) Save() {
390 | log.Info("Saving AddrBook to file", "size", a.Size())
391 | a.saveToFile(a.filePath)
392 | }
393 |
394 | /* Private methods */
395 |
396 | func (a *AddrBook) saveRoutine() {
397 | dumpAddressTicker := time.NewTicker(dumpAddressInterval)
398 | out:
399 | for {
400 | select {
401 | case <-dumpAddressTicker.C:
402 | a.saveToFile(a.filePath)
403 | case <-a.Quit:
404 | break out
405 | }
406 | }
407 | dumpAddressTicker.Stop()
408 | a.saveToFile(a.filePath)
409 | a.wg.Done()
410 | log.Notice("Address handler done")
411 | }
412 |
413 | func (a *AddrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress {
414 | switch bucketType {
415 | case bucketTypeNew:
416 | return a.addrNew[bucketIdx]
417 | case bucketTypeOld:
418 | return a.addrOld[bucketIdx]
419 | default:
420 | PanicSanity("Should not happen")
421 | return nil
422 | }
423 | }
424 |
425 | // Adds ka to new bucket. Returns false if it couldn't do it cuz buckets full.
426 | // NOTE: currently it always returns true.
427 | func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool {
428 | // Sanity check
429 | if ka.isOld() {
430 | log.Warn(Fmt("Cannot add address already in old bucket to a new bucket: %v", ka))
431 | return false
432 | }
433 |
434 | addrStr := ka.Addr.String()
435 | bucket := a.getBucket(bucketTypeNew, bucketIdx)
436 |
437 | // Already exists?
438 | if _, ok := bucket[addrStr]; ok {
439 | return true
440 | }
441 |
442 | // Enforce max addresses.
443 | if len(bucket) > newBucketSize {
444 | log.Notice("new bucket is full, expiring old ")
445 | a.expireNew(bucketIdx)
446 | }
447 |
448 | // Add to bucket.
449 | bucket[addrStr] = ka
450 | if ka.addBucketRef(bucketIdx) == 1 {
451 | a.nNew++
452 | }
453 |
454 | // Ensure in addrLookup
455 | a.addrLookup[addrStr] = ka
456 |
457 | return true
458 | }
459 |
460 | // Adds ka to old bucket. Returns false if it couldn't do it cuz buckets full.
461 | func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool {
462 | // Sanity check
463 | if ka.isNew() {
464 | log.Warn(Fmt("Cannot add new address to old bucket: %v", ka))
465 | return false
466 | }
467 | if len(ka.Buckets) != 0 {
468 | log.Warn(Fmt("Cannot add already old address to another old bucket: %v", ka))
469 | return false
470 | }
471 |
472 | addrStr := ka.Addr.String()
473 | bucket := a.getBucket(bucketTypeNew, bucketIdx)
474 |
475 | // Already exists?
476 | if _, ok := bucket[addrStr]; ok {
477 | return true
478 | }
479 |
480 | // Enforce max addresses.
481 | if len(bucket) > oldBucketSize {
482 | return false
483 | }
484 |
485 | // Add to bucket.
486 | bucket[addrStr] = ka
487 | if ka.addBucketRef(bucketIdx) == 1 {
488 | a.nOld++
489 | }
490 |
491 | // Ensure in addrLookup
492 | a.addrLookup[addrStr] = ka
493 |
494 | return true
495 | }
496 |
497 | func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx int) {
498 | if ka.BucketType != bucketType {
499 | log.Warn(Fmt("Bucket type mismatch: %v", ka))
500 | return
501 | }
502 | bucket := a.getBucket(bucketType, bucketIdx)
503 | delete(bucket, ka.Addr.String())
504 | if ka.removeBucketRef(bucketIdx) == 0 {
505 | if bucketType == bucketTypeNew {
506 | a.nNew--
507 | } else {
508 | a.nOld--
509 | }
510 | delete(a.addrLookup, ka.Addr.String())
511 | }
512 | }
513 |
514 | func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
515 | for _, bucketIdx := range ka.Buckets {
516 | bucket := a.getBucket(ka.BucketType, bucketIdx)
517 | delete(bucket, ka.Addr.String())
518 | }
519 | ka.Buckets = nil
520 | if ka.BucketType == bucketTypeNew {
521 | a.nNew--
522 | } else {
523 | a.nOld--
524 | }
525 | delete(a.addrLookup, ka.Addr.String())
526 | }
527 |
528 | func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress {
529 | bucket := a.getBucket(bucketType, bucketIdx)
530 | var oldest *knownAddress
531 | for _, ka := range bucket {
532 | if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt) {
533 | oldest = ka
534 | }
535 | }
536 | return oldest
537 | }
538 |
539 | func (a *AddrBook) addAddress(addr, src *NetAddress) {
540 | if a.routabilityStrict && !addr.Routable() {
541 | log.Warn(Fmt("Cannot add non-routable address %v", addr))
542 | return
543 | }
544 | if _, ok := a.ourAddrs[addr.String()]; ok {
545 | // Ignore our own listener address.
546 | return
547 | }
548 |
549 | ka := a.addrLookup[addr.String()]
550 |
551 | if ka != nil {
552 | // Already old.
553 | if ka.isOld() {
554 | return
555 | }
556 | // Already in max new buckets.
557 | if len(ka.Buckets) == maxNewBucketsPerAddress {
558 | return
559 | }
560 | // The more entries we have, the less likely we are to add more.
561 | factor := int32(2 * len(ka.Buckets))
562 | if a.rand.Int31n(factor) != 0 {
563 | return
564 | }
565 | } else {
566 | ka = newKnownAddress(addr, src)
567 | }
568 |
569 | bucket := a.calcNewBucket(addr, src)
570 | a.addToNewBucket(ka, bucket)
571 |
572 | log.Notice("Added new address", "address", addr, "total", a.size())
573 | }
574 |
575 | // Make space in the new buckets by expiring the really bad entries.
576 | // If no bad entries are available we remove the oldest.
577 | func (a *AddrBook) expireNew(bucketIdx int) {
578 | for addrStr, ka := range a.addrNew[bucketIdx] {
579 | // If an entry is bad, throw it away
580 | if ka.isBad() {
581 | log.Notice(Fmt("expiring bad address %v", addrStr))
582 | a.removeFromBucket(ka, bucketTypeNew, bucketIdx)
583 | return
584 | }
585 | }
586 |
587 | // If we haven't thrown out a bad entry, throw out the oldest entry
588 | oldest := a.pickOldest(bucketTypeNew, bucketIdx)
589 | a.removeFromBucket(oldest, bucketTypeNew, bucketIdx)
590 | }
591 |
592 | // Promotes an address from new to old.
593 | // TODO: Move to old probabilistically.
594 | // The better a node is, the less likely it should be evicted from an old bucket.
595 | func (a *AddrBook) moveToOld(ka *knownAddress) {
596 | // Sanity check
597 | if ka.isOld() {
598 | log.Warn(Fmt("Cannot promote address that is already old %v", ka))
599 | return
600 | }
601 | if len(ka.Buckets) == 0 {
602 | log.Warn(Fmt("Cannot promote address that isn't in any new buckets %v", ka))
603 | return
604 | }
605 |
606 | // Remember one of the buckets in which ka is in.
607 | freedBucket := ka.Buckets[0]
608 | // Remove from all (new) buckets.
609 | a.removeFromAllBuckets(ka)
610 | // It's officially old now.
611 | ka.BucketType = bucketTypeOld
612 |
613 | // Try to add it to its oldBucket destination.
614 | oldBucketIdx := a.calcOldBucket(ka.Addr)
615 | added := a.addToOldBucket(ka, oldBucketIdx)
616 | if !added {
617 | // No room, must evict something
618 | oldest := a.pickOldest(bucketTypeOld, oldBucketIdx)
619 | a.removeFromBucket(oldest, bucketTypeOld, oldBucketIdx)
620 | // Find new bucket to put oldest in
621 | newBucketIdx := a.calcNewBucket(oldest.Addr, oldest.Src)
622 | added := a.addToNewBucket(oldest, newBucketIdx)
623 | // No space in newBucket either, just put it in freedBucket from above.
624 | if !added {
625 | added := a.addToNewBucket(oldest, freedBucket)
626 | if !added {
627 | log.Warn(Fmt("Could not migrate oldest %v to freedBucket %v", oldest, freedBucket))
628 | }
629 | }
630 | // Finally, add to bucket again.
631 | added = a.addToOldBucket(ka, oldBucketIdx)
632 | if !added {
633 | log.Warn(Fmt("Could not re-add ka %v to oldBucketIdx %v", ka, oldBucketIdx))
634 | }
635 | }
636 | }
637 |
638 | // doublesha256( key + sourcegroup +
639 | // int64(doublesha256(key + group + sourcegroup))%bucket_per_group ) % num_new_buckets
640 | func (a *AddrBook) calcNewBucket(addr, src *NetAddress) int {
641 | data1 := []byte{}
642 | data1 = append(data1, []byte(a.key)...)
643 | data1 = append(data1, []byte(a.groupKey(addr))...)
644 | data1 = append(data1, []byte(a.groupKey(src))...)
645 | hash1 := doubleSha256(data1)
646 | hash64 := binary.BigEndian.Uint64(hash1)
647 | hash64 %= newBucketsPerGroup
648 | var hashbuf [8]byte
649 | binary.BigEndian.PutUint64(hashbuf[:], hash64)
650 | data2 := []byte{}
651 | data2 = append(data2, []byte(a.key)...)
652 | data2 = append(data2, a.groupKey(src)...)
653 | data2 = append(data2, hashbuf[:]...)
654 |
655 | hash2 := doubleSha256(data2)
656 | return int(binary.BigEndian.Uint64(hash2) % newBucketCount)
657 | }
658 |
659 | // doublesha256( key + group +
660 | // int64(doublesha256(key + addr))%buckets_per_group ) % num_old_buckets
661 | func (a *AddrBook) calcOldBucket(addr *NetAddress) int {
662 | data1 := []byte{}
663 | data1 = append(data1, []byte(a.key)...)
664 | data1 = append(data1, []byte(addr.String())...)
665 | hash1 := doubleSha256(data1)
666 | hash64 := binary.BigEndian.Uint64(hash1)
667 | hash64 %= oldBucketsPerGroup
668 | var hashbuf [8]byte
669 | binary.BigEndian.PutUint64(hashbuf[:], hash64)
670 | data2 := []byte{}
671 | data2 = append(data2, []byte(a.key)...)
672 | data2 = append(data2, a.groupKey(addr)...)
673 | data2 = append(data2, hashbuf[:]...)
674 |
675 | hash2 := doubleSha256(data2)
676 | return int(binary.BigEndian.Uint64(hash2) % oldBucketCount)
677 | }
678 |
679 | // Return a string representing the network group of this address.
680 | // This is the /16 for IPv6, the /32 (/36 for he.net) for IPv6, the string
681 | // "local" for a local address and the string "unroutable for an unroutable
682 | // address.
683 | func (a *AddrBook) groupKey(na *NetAddress) string {
684 | if a.routabilityStrict && na.Local() {
685 | return "local"
686 | }
687 | if a.routabilityStrict && !na.Routable() {
688 | return "unroutable"
689 | }
690 |
691 | if ipv4 := na.IP.To4(); ipv4 != nil {
692 | return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String()
693 | }
694 | if na.RFC6145() || na.RFC6052() {
695 | // last four bytes are the ip address
696 | ip := net.IP(na.IP[12:16])
697 | return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
698 | }
699 |
700 | if na.RFC3964() {
701 | ip := net.IP(na.IP[2:7])
702 | return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
703 |
704 | }
705 | if na.RFC4380() {
706 | // teredo tunnels have the last 4 bytes as the v4 address XOR
707 | // 0xff.
708 | ip := net.IP(make([]byte, 4))
709 | for i, byte := range na.IP[12:16] {
710 | ip[i] = byte ^ 0xff
711 | }
712 | return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
713 | }
714 |
715 | // OK, so now we know ourselves to be a IPv6 address.
716 | // bitcoind uses /32 for everything, except for Hurricane Electric's
717 | // (he.net) IP range, which it uses /36 for.
718 | bits := 32
719 | heNet := &net.IPNet{IP: net.ParseIP("2001:470::"),
720 | Mask: net.CIDRMask(32, 128)}
721 | if heNet.Contains(na.IP) {
722 | bits = 36
723 | }
724 |
725 | return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
726 | }
727 |
728 | //-----------------------------------------------------------------------------
729 |
730 | /*
731 | knownAddress
732 |
733 | tracks information about a known network address that is used
734 | to determine how viable an address is.
735 | */
736 | type knownAddress struct {
737 | Addr *NetAddress
738 | Src *NetAddress
739 | Attempts int32
740 | LastAttempt time.Time
741 | LastSuccess time.Time
742 | BucketType byte
743 | Buckets []int
744 | }
745 |
746 | func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress {
747 | return &knownAddress{
748 | Addr: addr,
749 | Src: src,
750 | Attempts: 0,
751 | LastAttempt: time.Now(),
752 | BucketType: bucketTypeNew,
753 | Buckets: nil,
754 | }
755 | }
756 |
757 | func (ka *knownAddress) isOld() bool {
758 | return ka.BucketType == bucketTypeOld
759 | }
760 |
761 | func (ka *knownAddress) isNew() bool {
762 | return ka.BucketType == bucketTypeNew
763 | }
764 |
765 | func (ka *knownAddress) markAttempt() {
766 | now := time.Now()
767 | ka.LastAttempt = now
768 | ka.Attempts += 1
769 | }
770 |
771 | func (ka *knownAddress) markGood() {
772 | now := time.Now()
773 | ka.LastAttempt = now
774 | ka.Attempts = 0
775 | ka.LastSuccess = now
776 | }
777 |
778 | func (ka *knownAddress) addBucketRef(bucketIdx int) int {
779 | for _, bucket := range ka.Buckets {
780 | if bucket == bucketIdx {
781 | log.Warn(Fmt("Bucket already exists in ka.Buckets: %v", ka))
782 | return -1
783 | }
784 | }
785 | ka.Buckets = append(ka.Buckets, bucketIdx)
786 | return len(ka.Buckets)
787 | }
788 |
789 | func (ka *knownAddress) removeBucketRef(bucketIdx int) int {
790 | buckets := []int{}
791 | for _, bucket := range ka.Buckets {
792 | if bucket != bucketIdx {
793 | buckets = append(buckets, bucket)
794 | }
795 | }
796 | if len(buckets) != len(ka.Buckets)-1 {
797 | log.Warn(Fmt("bucketIdx not found in ka.Buckets: %v", ka))
798 | return -1
799 | }
800 | ka.Buckets = buckets
801 | return len(ka.Buckets)
802 | }
803 |
804 | /*
805 | An address is bad if the address in question has not been tried in the last
806 | minute and meets one of the following criteria:
807 |
808 | 1) It claims to be from the future
809 | 2) It hasn't been seen in over a month
810 | 3) It has failed at least three times and never succeeded
811 | 4) It has failed ten times in the last week
812 |
813 | All addresses that meet these criteria are assumed to be worthless and not
814 | worth keeping hold of.
815 | */
816 | func (ka *knownAddress) isBad() bool {
817 | // Has been attempted in the last minute --> good
818 | if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) {
819 | return false
820 | }
821 |
822 | // Over a month old?
823 | if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
824 | return true
825 | }
826 |
827 | // Never succeeded?
828 | if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
829 | return true
830 | }
831 |
832 | // Hasn't succeeded in too long?
833 | if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
834 | ka.Attempts >= maxFailures {
835 | return true
836 | }
837 |
838 | return false
839 | }
840 |
--------------------------------------------------------------------------------
/addrbook_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "math/rand"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func createTempFileName(prefix string) string {
13 | f, err := ioutil.TempFile("", prefix)
14 | if err != nil {
15 | panic(err)
16 | }
17 | fname := f.Name()
18 | err = f.Close()
19 | if err != nil {
20 | panic(err)
21 | }
22 | return fname
23 | }
24 |
25 | func TestAddrBookSaveLoad(t *testing.T) {
26 | fname := createTempFileName("addrbook_test")
27 |
28 | // 0 addresses
29 | book := NewAddrBook(fname, true)
30 | book.saveToFile(fname)
31 |
32 | book = NewAddrBook(fname, true)
33 | book.loadFromFile(fname)
34 |
35 | assert.Zero(t, book.Size())
36 |
37 | // 100 addresses
38 | randAddrs := randNetAddressPairs(t, 100)
39 |
40 | for _, addrSrc := range randAddrs {
41 | book.AddAddress(addrSrc.addr, addrSrc.src)
42 | }
43 |
44 | assert.Equal(t, 100, book.Size())
45 | book.saveToFile(fname)
46 |
47 | book = NewAddrBook(fname, true)
48 | book.loadFromFile(fname)
49 |
50 | assert.Equal(t, 100, book.Size())
51 | }
52 |
53 | func TestAddrBookLookup(t *testing.T) {
54 | fname := createTempFileName("addrbook_test")
55 |
56 | randAddrs := randNetAddressPairs(t, 100)
57 |
58 | book := NewAddrBook(fname, true)
59 | for _, addrSrc := range randAddrs {
60 | addr := addrSrc.addr
61 | src := addrSrc.src
62 | book.AddAddress(addr, src)
63 |
64 | ka := book.addrLookup[addr.String()]
65 | assert.NotNil(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr)
66 |
67 | if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) {
68 | t.Fatalf("KnownAddress doesn't match addr & src")
69 | }
70 | }
71 | }
72 |
73 | func TestAddrBookPromoteToOld(t *testing.T) {
74 | fname := createTempFileName("addrbook_test")
75 |
76 | randAddrs := randNetAddressPairs(t, 100)
77 |
78 | book := NewAddrBook(fname, true)
79 | for _, addrSrc := range randAddrs {
80 | book.AddAddress(addrSrc.addr, addrSrc.src)
81 | }
82 |
83 | // Attempt all addresses.
84 | for _, addrSrc := range randAddrs {
85 | book.MarkAttempt(addrSrc.addr)
86 | }
87 |
88 | // Promote half of them
89 | for i, addrSrc := range randAddrs {
90 | if i%2 == 0 {
91 | book.MarkGood(addrSrc.addr)
92 | }
93 | }
94 |
95 | // TODO: do more testing :)
96 |
97 | selection := book.GetSelection()
98 | t.Logf("selection: %v", selection)
99 |
100 | if len(selection) > book.Size() {
101 | t.Errorf("selection could not be bigger than the book")
102 | }
103 | }
104 |
105 | func TestAddrBookHandlesDuplicates(t *testing.T) {
106 | fname := createTempFileName("addrbook_test")
107 |
108 | book := NewAddrBook(fname, true)
109 |
110 | randAddrs := randNetAddressPairs(t, 100)
111 |
112 | differentSrc := randIPv4Address(t)
113 | for _, addrSrc := range randAddrs {
114 | book.AddAddress(addrSrc.addr, addrSrc.src)
115 | book.AddAddress(addrSrc.addr, addrSrc.src) // duplicate
116 | book.AddAddress(addrSrc.addr, differentSrc) // different src
117 | }
118 |
119 | assert.Equal(t, 100, book.Size())
120 | }
121 |
122 | type netAddressPair struct {
123 | addr *NetAddress
124 | src *NetAddress
125 | }
126 |
127 | func randNetAddressPairs(t *testing.T, n int) []netAddressPair {
128 | randAddrs := make([]netAddressPair, n)
129 | for i := 0; i < n; i++ {
130 | randAddrs[i] = netAddressPair{addr: randIPv4Address(t), src: randIPv4Address(t)}
131 | }
132 | return randAddrs
133 | }
134 |
135 | func randIPv4Address(t *testing.T) *NetAddress {
136 | for {
137 | ip := fmt.Sprintf("%v.%v.%v.%v",
138 | rand.Intn(254)+1,
139 | rand.Intn(255),
140 | rand.Intn(255),
141 | rand.Intn(255),
142 | )
143 | port := rand.Intn(65535-1) + 1
144 | addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port))
145 | assert.Nil(t, err, "error generating rand network address")
146 | if addr.Routable() {
147 | return addr
148 | }
149 | }
150 | }
151 |
152 | func TestAddrBookRemoveAddress(t *testing.T) {
153 | fname := createTempFileName("addrbook_test")
154 | book := NewAddrBook(fname, true)
155 |
156 | addr := randIPv4Address(t)
157 | book.AddAddress(addr, addr)
158 | assert.Equal(t, 1, book.Size())
159 |
160 | book.RemoveAddress(addr)
161 | assert.Equal(t, 0, book.Size())
162 |
163 | nonExistingAddr := randIPv4Address(t)
164 | book.RemoveAddress(nonExistingAddr)
165 | assert.Equal(t, 0, book.Size())
166 | }
167 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | cfg "github.com/tendermint/go-config"
5 | )
6 |
7 | const (
8 | // Switch config keys
9 | configKeyDialTimeoutSeconds = "dial_timeout_seconds"
10 | configKeyHandshakeTimeoutSeconds = "handshake_timeout_seconds"
11 | configKeyMaxNumPeers = "max_num_peers"
12 | configKeyAuthEnc = "authenticated_encryption"
13 |
14 | // MConnection config keys
15 | configKeySendRate = "send_rate"
16 | configKeyRecvRate = "recv_rate"
17 |
18 | // Fuzz params
19 | configFuzzEnable = "fuzz_enable" // use the fuzz wrapped conn
20 | configFuzzMode = "fuzz_mode" // eg. drop, delay
21 | configFuzzMaxDelayMilliseconds = "fuzz_max_delay_milliseconds"
22 | configFuzzProbDropRW = "fuzz_prob_drop_rw"
23 | configFuzzProbDropConn = "fuzz_prob_drop_conn"
24 | configFuzzProbSleep = "fuzz_prob_sleep"
25 | )
26 |
27 | func setConfigDefaults(config cfg.Config) {
28 | // Switch default config
29 | config.SetDefault(configKeyDialTimeoutSeconds, 3)
30 | config.SetDefault(configKeyHandshakeTimeoutSeconds, 20)
31 | config.SetDefault(configKeyMaxNumPeers, 50)
32 | config.SetDefault(configKeyAuthEnc, true)
33 |
34 | // MConnection default config
35 | config.SetDefault(configKeySendRate, 512000) // 500KB/s
36 | config.SetDefault(configKeyRecvRate, 512000) // 500KB/s
37 |
38 | // Fuzz defaults
39 | config.SetDefault(configFuzzEnable, false)
40 | config.SetDefault(configFuzzMode, FuzzModeDrop)
41 | config.SetDefault(configFuzzMaxDelayMilliseconds, 3000)
42 | config.SetDefault(configFuzzProbDropRW, 0.2)
43 | config.SetDefault(configFuzzProbDropConn, 0.00)
44 | config.SetDefault(configFuzzProbSleep, 0.00)
45 | }
46 |
--------------------------------------------------------------------------------
/connection.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "math"
8 | "net"
9 | "runtime/debug"
10 | "sync/atomic"
11 | "time"
12 |
13 | cmn "github.com/tendermint/go-common"
14 | flow "github.com/tendermint/go-flowrate/flowrate"
15 | wire "github.com/tendermint/go-wire"
16 | )
17 |
18 | const (
19 | numBatchMsgPackets = 10
20 | minReadBufferSize = 1024
21 | minWriteBufferSize = 65536
22 | updateState = 2 * time.Second
23 | pingTimeout = 40 * time.Second
24 | flushThrottle = 100 * time.Millisecond
25 |
26 | defaultSendQueueCapacity = 1
27 | defaultSendRate = int64(512000) // 500KB/s
28 | defaultRecvBufferCapacity = 4096
29 | defaultRecvMessageCapacity = 22020096 // 21MB
30 | defaultRecvRate = int64(512000) // 500KB/s
31 | defaultSendTimeout = 10 * time.Second
32 | )
33 |
34 | type receiveCbFunc func(chID byte, msgBytes []byte)
35 | type errorCbFunc func(interface{})
36 |
37 | /*
38 | Each peer has one `MConnection` (multiplex connection) instance.
39 |
40 | __multiplex__ *noun* a system or signal involving simultaneous transmission of
41 | several messages along a single channel of communication.
42 |
43 | Each `MConnection` handles message transmission on multiple abstract communication
44 | `Channel`s. Each channel has a globally unique byte id.
45 | The byte id and the relative priorities of each `Channel` are configured upon
46 | initialization of the connection.
47 |
48 | There are two methods for sending messages:
49 | func (m MConnection) Send(chID byte, msg interface{}) bool {}
50 | func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
51 |
52 | `Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued
53 | for the channel with the given id byte `chID`, or until the request times out.
54 | The message `msg` is serialized using the `tendermint/wire` submodule's
55 | `WriteBinary()` reflection routine.
56 |
57 | `TrySend(chID, msg)` is a nonblocking call that returns false if the channel's
58 | queue is full.
59 |
60 | Inbound message bytes are handled with an onReceive callback function.
61 | */
62 | type MConnection struct {
63 | cmn.BaseService
64 |
65 | conn net.Conn
66 | bufReader *bufio.Reader
67 | bufWriter *bufio.Writer
68 | sendMonitor *flow.Monitor
69 | recvMonitor *flow.Monitor
70 | send chan struct{}
71 | pong chan struct{}
72 | channels []*Channel
73 | channelsIdx map[byte]*Channel
74 | onReceive receiveCbFunc
75 | onError errorCbFunc
76 | errored uint32
77 | config *MConnConfig
78 |
79 | quit chan struct{}
80 | flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled.
81 | pingTimer *cmn.RepeatTimer // send pings periodically
82 | chStatsTimer *cmn.RepeatTimer // update channel stats periodically
83 |
84 | LocalAddress *NetAddress
85 | RemoteAddress *NetAddress
86 | }
87 |
88 | // MConnConfig is a MConnection configuration.
89 | type MConnConfig struct {
90 | SendRate int64
91 | RecvRate int64
92 | }
93 |
94 | // DefaultMConnConfig returns the default config.
95 | func DefaultMConnConfig() *MConnConfig {
96 | return &MConnConfig{
97 | SendRate: defaultSendRate,
98 | RecvRate: defaultRecvRate,
99 | }
100 | }
101 |
102 | // NewMConnection wraps net.Conn and creates multiplex connection
103 | func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection {
104 | return NewMConnectionWithConfig(
105 | conn,
106 | chDescs,
107 | onReceive,
108 | onError,
109 | DefaultMConnConfig())
110 | }
111 |
112 | // NewMConnectionWithConfig wraps net.Conn and creates multiplex connection with a config
113 | func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc, config *MConnConfig) *MConnection {
114 | mconn := &MConnection{
115 | conn: conn,
116 | bufReader: bufio.NewReaderSize(conn, minReadBufferSize),
117 | bufWriter: bufio.NewWriterSize(conn, minWriteBufferSize),
118 | sendMonitor: flow.New(0, 0),
119 | recvMonitor: flow.New(0, 0),
120 | send: make(chan struct{}, 1),
121 | pong: make(chan struct{}),
122 | onReceive: onReceive,
123 | onError: onError,
124 | config: config,
125 |
126 | LocalAddress: NewNetAddress(conn.LocalAddr()),
127 | RemoteAddress: NewNetAddress(conn.RemoteAddr()),
128 | }
129 |
130 | // Create channels
131 | var channelsIdx = map[byte]*Channel{}
132 | var channels = []*Channel{}
133 |
134 | for _, desc := range chDescs {
135 | descCopy := *desc // copy the desc else unsafe access across connections
136 | channel := newChannel(mconn, &descCopy)
137 | channelsIdx[channel.id] = channel
138 | channels = append(channels, channel)
139 | }
140 | mconn.channels = channels
141 | mconn.channelsIdx = channelsIdx
142 |
143 | mconn.BaseService = *cmn.NewBaseService(log, "MConnection", mconn)
144 |
145 | return mconn
146 | }
147 |
148 | func (c *MConnection) OnStart() error {
149 | c.BaseService.OnStart()
150 | c.quit = make(chan struct{})
151 | c.flushTimer = cmn.NewThrottleTimer("flush", flushThrottle)
152 | c.pingTimer = cmn.NewRepeatTimer("ping", pingTimeout)
153 | c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateState)
154 | go c.sendRoutine()
155 | go c.recvRoutine()
156 | return nil
157 | }
158 |
159 | func (c *MConnection) OnStop() {
160 | c.BaseService.OnStop()
161 | c.flushTimer.Stop()
162 | c.pingTimer.Stop()
163 | c.chStatsTimer.Stop()
164 | if c.quit != nil {
165 | close(c.quit)
166 | }
167 | c.conn.Close()
168 | // We can't close pong safely here because
169 | // recvRoutine may write to it after we've stopped.
170 | // Though it doesn't need to get closed at all,
171 | // we close it @ recvRoutine.
172 | // close(c.pong)
173 | }
174 |
175 | func (c *MConnection) String() string {
176 | return fmt.Sprintf("MConn{%v}", c.conn.RemoteAddr())
177 | }
178 |
179 | func (c *MConnection) flush() {
180 | log.Debug("Flush", "conn", c)
181 | err := c.bufWriter.Flush()
182 | if err != nil {
183 | log.Warn("MConnection flush failed", "error", err)
184 | }
185 | }
186 |
187 | // Catch panics, usually caused by remote disconnects.
188 | func (c *MConnection) _recover() {
189 | if r := recover(); r != nil {
190 | stack := debug.Stack()
191 | err := cmn.StackError{r, stack}
192 | c.stopForError(err)
193 | }
194 | }
195 |
196 | func (c *MConnection) stopForError(r interface{}) {
197 | c.Stop()
198 | if atomic.CompareAndSwapUint32(&c.errored, 0, 1) {
199 | if c.onError != nil {
200 | c.onError(r)
201 | }
202 | }
203 | }
204 |
205 | // Queues a message to be sent to channel.
206 | func (c *MConnection) Send(chID byte, msg interface{}) bool {
207 | if !c.IsRunning() {
208 | return false
209 | }
210 |
211 | log.Debug("Send", "channel", chID, "conn", c, "msg", msg) //, "bytes", wire.BinaryBytes(msg))
212 |
213 | // Send message to channel.
214 | channel, ok := c.channelsIdx[chID]
215 | if !ok {
216 | log.Error(cmn.Fmt("Cannot send bytes, unknown channel %X", chID))
217 | return false
218 | }
219 |
220 | success := channel.sendBytes(wire.BinaryBytes(msg))
221 | if success {
222 | // Wake up sendRoutine if necessary
223 | select {
224 | case c.send <- struct{}{}:
225 | default:
226 | }
227 | } else {
228 | log.Warn("Send failed", "channel", chID, "conn", c, "msg", msg)
229 | }
230 | return success
231 | }
232 |
233 | // Queues a message to be sent to channel.
234 | // Nonblocking, returns true if successful.
235 | func (c *MConnection) TrySend(chID byte, msg interface{}) bool {
236 | if !c.IsRunning() {
237 | return false
238 | }
239 |
240 | log.Debug("TrySend", "channel", chID, "conn", c, "msg", msg)
241 |
242 | // Send message to channel.
243 | channel, ok := c.channelsIdx[chID]
244 | if !ok {
245 | log.Error(cmn.Fmt("Cannot send bytes, unknown channel %X", chID))
246 | return false
247 | }
248 |
249 | ok = channel.trySendBytes(wire.BinaryBytes(msg))
250 | if ok {
251 | // Wake up sendRoutine if necessary
252 | select {
253 | case c.send <- struct{}{}:
254 | default:
255 | }
256 | }
257 |
258 | return ok
259 | }
260 |
261 | // CanSend returns true if you can send more data onto the chID, false
262 | // otherwise. Use only as a heuristic.
263 | func (c *MConnection) CanSend(chID byte) bool {
264 | if !c.IsRunning() {
265 | return false
266 | }
267 |
268 | channel, ok := c.channelsIdx[chID]
269 | if !ok {
270 | log.Error(cmn.Fmt("Unknown channel %X", chID))
271 | return false
272 | }
273 | return channel.canSend()
274 | }
275 |
276 | // sendRoutine polls for packets to send from channels.
277 | func (c *MConnection) sendRoutine() {
278 | defer c._recover()
279 |
280 | FOR_LOOP:
281 | for {
282 | var n int
283 | var err error
284 | select {
285 | case <-c.flushTimer.Ch:
286 | // NOTE: flushTimer.Set() must be called every time
287 | // something is written to .bufWriter.
288 | c.flush()
289 | case <-c.chStatsTimer.Ch:
290 | for _, channel := range c.channels {
291 | channel.updateStats()
292 | }
293 | case <-c.pingTimer.Ch:
294 | log.Debug("Send Ping")
295 | wire.WriteByte(packetTypePing, c.bufWriter, &n, &err)
296 | c.sendMonitor.Update(int(n))
297 | c.flush()
298 | case <-c.pong:
299 | log.Debug("Send Pong")
300 | wire.WriteByte(packetTypePong, c.bufWriter, &n, &err)
301 | c.sendMonitor.Update(int(n))
302 | c.flush()
303 | case <-c.quit:
304 | break FOR_LOOP
305 | case <-c.send:
306 | // Send some msgPackets
307 | eof := c.sendSomeMsgPackets()
308 | if !eof {
309 | // Keep sendRoutine awake.
310 | select {
311 | case c.send <- struct{}{}:
312 | default:
313 | }
314 | }
315 | }
316 |
317 | if !c.IsRunning() {
318 | break FOR_LOOP
319 | }
320 | if err != nil {
321 | log.Warn("Connection failed @ sendRoutine", "conn", c, "error", err)
322 | c.stopForError(err)
323 | break FOR_LOOP
324 | }
325 | }
326 |
327 | // Cleanup
328 | }
329 |
330 | // Returns true if messages from channels were exhausted.
331 | // Blocks in accordance to .sendMonitor throttling.
332 | func (c *MConnection) sendSomeMsgPackets() bool {
333 | // Block until .sendMonitor says we can write.
334 | // Once we're ready we send more than we asked for,
335 | // but amortized it should even out.
336 | c.sendMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.config.SendRate), true)
337 |
338 | // Now send some msgPackets.
339 | for i := 0; i < numBatchMsgPackets; i++ {
340 | if c.sendMsgPacket() {
341 | return true
342 | }
343 | }
344 | return false
345 | }
346 |
347 | // Returns true if messages from channels were exhausted.
348 | func (c *MConnection) sendMsgPacket() bool {
349 | // Choose a channel to create a msgPacket from.
350 | // The chosen channel will be the one whose recentlySent/priority is the least.
351 | var leastRatio float32 = math.MaxFloat32
352 | var leastChannel *Channel
353 | for _, channel := range c.channels {
354 | // If nothing to send, skip this channel
355 | if !channel.isSendPending() {
356 | continue
357 | }
358 | // Get ratio, and keep track of lowest ratio.
359 | ratio := float32(channel.recentlySent) / float32(channel.priority)
360 | if ratio < leastRatio {
361 | leastRatio = ratio
362 | leastChannel = channel
363 | }
364 | }
365 |
366 | // Nothing to send?
367 | if leastChannel == nil {
368 | return true
369 | } else {
370 | // log.Info("Found a msgPacket to send")
371 | }
372 |
373 | // Make & send a msgPacket from this channel
374 | n, err := leastChannel.writeMsgPacketTo(c.bufWriter)
375 | if err != nil {
376 | log.Warn("Failed to write msgPacket", "error", err)
377 | c.stopForError(err)
378 | return true
379 | }
380 | c.sendMonitor.Update(int(n))
381 | c.flushTimer.Set()
382 | return false
383 | }
384 |
385 | // recvRoutine reads msgPackets and reconstructs the message using the channels' "recving" buffer.
386 | // After a whole message has been assembled, it's pushed to onReceive().
387 | // Blocks depending on how the connection is throttled.
388 | func (c *MConnection) recvRoutine() {
389 | defer c._recover()
390 |
391 | FOR_LOOP:
392 | for {
393 | // Block until .recvMonitor says we can read.
394 | c.recvMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.config.RecvRate), true)
395 |
396 | /*
397 | // Peek into bufReader for debugging
398 | if numBytes := c.bufReader.Buffered(); numBytes > 0 {
399 | log.Info("Peek connection buffer", "numBytes", numBytes, "bytes", log15.Lazy{func() []byte {
400 | bytes, err := c.bufReader.Peek(MinInt(numBytes, 100))
401 | if err == nil {
402 | return bytes
403 | } else {
404 | log.Warn("Error peeking connection buffer", "error", err)
405 | return nil
406 | }
407 | }})
408 | }
409 | */
410 |
411 | // Read packet type
412 | var n int
413 | var err error
414 | pktType := wire.ReadByte(c.bufReader, &n, &err)
415 | c.recvMonitor.Update(int(n))
416 | if err != nil {
417 | if c.IsRunning() {
418 | log.Warn("Connection failed @ recvRoutine (reading byte)", "conn", c, "error", err)
419 | c.stopForError(err)
420 | }
421 | break FOR_LOOP
422 | }
423 |
424 | // Read more depending on packet type.
425 | switch pktType {
426 | case packetTypePing:
427 | // TODO: prevent abuse, as they cause flush()'s.
428 | log.Debug("Receive Ping")
429 | c.pong <- struct{}{}
430 | case packetTypePong:
431 | // do nothing
432 | log.Debug("Receive Pong")
433 | case packetTypeMsg:
434 | pkt, n, err := msgPacket{}, int(0), error(nil)
435 | wire.ReadBinaryPtr(&pkt, c.bufReader, maxMsgPacketTotalSize, &n, &err)
436 | c.recvMonitor.Update(int(n))
437 | if err != nil {
438 | if c.IsRunning() {
439 | log.Warn("Connection failed @ recvRoutine", "conn", c, "error", err)
440 | c.stopForError(err)
441 | }
442 | break FOR_LOOP
443 | }
444 | channel, ok := c.channelsIdx[pkt.ChannelID]
445 | if !ok || channel == nil {
446 | cmn.PanicQ(cmn.Fmt("Unknown channel %X", pkt.ChannelID))
447 | }
448 | msgBytes, err := channel.recvMsgPacket(pkt)
449 | if err != nil {
450 | if c.IsRunning() {
451 | log.Warn("Connection failed @ recvRoutine", "conn", c, "error", err)
452 | c.stopForError(err)
453 | }
454 | break FOR_LOOP
455 | }
456 | if msgBytes != nil {
457 | log.Debug("Received bytes", "chID", pkt.ChannelID, "msgBytes", msgBytes)
458 | c.onReceive(pkt.ChannelID, msgBytes)
459 | }
460 | default:
461 | cmn.PanicSanity(cmn.Fmt("Unknown message type %X", pktType))
462 | }
463 |
464 | // TODO: shouldn't this go in the sendRoutine?
465 | // Better to send a ping packet when *we* haven't sent anything for a while.
466 | c.pingTimer.Reset()
467 | }
468 |
469 | // Cleanup
470 | close(c.pong)
471 | for _ = range c.pong {
472 | // Drain
473 | }
474 | }
475 |
476 | type ConnectionStatus struct {
477 | SendMonitor flow.Status
478 | RecvMonitor flow.Status
479 | Channels []ChannelStatus
480 | }
481 |
482 | type ChannelStatus struct {
483 | ID byte
484 | SendQueueCapacity int
485 | SendQueueSize int
486 | Priority int
487 | RecentlySent int64
488 | }
489 |
490 | func (c *MConnection) Status() ConnectionStatus {
491 | var status ConnectionStatus
492 | status.SendMonitor = c.sendMonitor.Status()
493 | status.RecvMonitor = c.recvMonitor.Status()
494 | status.Channels = make([]ChannelStatus, len(c.channels))
495 | for i, channel := range c.channels {
496 | status.Channels[i] = ChannelStatus{
497 | ID: channel.id,
498 | SendQueueCapacity: cap(channel.sendQueue),
499 | SendQueueSize: int(channel.sendQueueSize), // TODO use atomic
500 | Priority: channel.priority,
501 | RecentlySent: channel.recentlySent,
502 | }
503 | }
504 | return status
505 | }
506 |
507 | //-----------------------------------------------------------------------------
508 |
509 | type ChannelDescriptor struct {
510 | ID byte
511 | Priority int
512 | SendQueueCapacity int
513 | RecvBufferCapacity int
514 | RecvMessageCapacity int
515 | }
516 |
517 | func (chDesc *ChannelDescriptor) FillDefaults() {
518 | if chDesc.SendQueueCapacity == 0 {
519 | chDesc.SendQueueCapacity = defaultSendQueueCapacity
520 | }
521 | if chDesc.RecvBufferCapacity == 0 {
522 | chDesc.RecvBufferCapacity = defaultRecvBufferCapacity
523 | }
524 | if chDesc.RecvMessageCapacity == 0 {
525 | chDesc.RecvMessageCapacity = defaultRecvMessageCapacity
526 | }
527 | }
528 |
529 | // TODO: lowercase.
530 | // NOTE: not goroutine-safe.
531 | type Channel struct {
532 | conn *MConnection
533 | desc *ChannelDescriptor
534 | id byte
535 | sendQueue chan []byte
536 | sendQueueSize int32 // atomic.
537 | recving []byte
538 | sending []byte
539 | priority int
540 | recentlySent int64 // exponential moving average
541 | }
542 |
543 | func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel {
544 | desc.FillDefaults()
545 | if desc.Priority <= 0 {
546 | cmn.PanicSanity("Channel default priority must be a postive integer")
547 | }
548 | return &Channel{
549 | conn: conn,
550 | desc: desc,
551 | id: desc.ID,
552 | sendQueue: make(chan []byte, desc.SendQueueCapacity),
553 | recving: make([]byte, 0, desc.RecvBufferCapacity),
554 | priority: desc.Priority,
555 | }
556 | }
557 |
558 | // Queues message to send to this channel.
559 | // Goroutine-safe
560 | // Times out (and returns false) after defaultSendTimeout
561 | func (ch *Channel) sendBytes(bytes []byte) bool {
562 | select {
563 | case ch.sendQueue <- bytes:
564 | atomic.AddInt32(&ch.sendQueueSize, 1)
565 | return true
566 | case <-time.After(defaultSendTimeout):
567 | return false
568 | }
569 | }
570 |
571 | // Queues message to send to this channel.
572 | // Nonblocking, returns true if successful.
573 | // Goroutine-safe
574 | func (ch *Channel) trySendBytes(bytes []byte) bool {
575 | select {
576 | case ch.sendQueue <- bytes:
577 | atomic.AddInt32(&ch.sendQueueSize, 1)
578 | return true
579 | default:
580 | return false
581 | }
582 | }
583 |
584 | // Goroutine-safe
585 | func (ch *Channel) loadSendQueueSize() (size int) {
586 | return int(atomic.LoadInt32(&ch.sendQueueSize))
587 | }
588 |
589 | // Goroutine-safe
590 | // Use only as a heuristic.
591 | func (ch *Channel) canSend() bool {
592 | return ch.loadSendQueueSize() < defaultSendQueueCapacity
593 | }
594 |
595 | // Returns true if any msgPackets are pending to be sent.
596 | // Call before calling nextMsgPacket()
597 | // Goroutine-safe
598 | func (ch *Channel) isSendPending() bool {
599 | if len(ch.sending) == 0 {
600 | if len(ch.sendQueue) == 0 {
601 | return false
602 | }
603 | ch.sending = <-ch.sendQueue
604 | }
605 | return true
606 | }
607 |
608 | // Creates a new msgPacket to send.
609 | // Not goroutine-safe
610 | func (ch *Channel) nextMsgPacket() msgPacket {
611 | packet := msgPacket{}
612 | packet.ChannelID = byte(ch.id)
613 | packet.Bytes = ch.sending[:cmn.MinInt(maxMsgPacketPayloadSize, len(ch.sending))]
614 | if len(ch.sending) <= maxMsgPacketPayloadSize {
615 | packet.EOF = byte(0x01)
616 | ch.sending = nil
617 | atomic.AddInt32(&ch.sendQueueSize, -1) // decrement sendQueueSize
618 | } else {
619 | packet.EOF = byte(0x00)
620 | ch.sending = ch.sending[cmn.MinInt(maxMsgPacketPayloadSize, len(ch.sending)):]
621 | }
622 | return packet
623 | }
624 |
625 | // Writes next msgPacket to w.
626 | // Not goroutine-safe
627 | func (ch *Channel) writeMsgPacketTo(w io.Writer) (n int, err error) {
628 | packet := ch.nextMsgPacket()
629 | log.Debug("Write Msg Packet", "conn", ch.conn, "packet", packet)
630 | wire.WriteByte(packetTypeMsg, w, &n, &err)
631 | wire.WriteBinary(packet, w, &n, &err)
632 | if err == nil {
633 | ch.recentlySent += int64(n)
634 | }
635 | return
636 | }
637 |
638 | // Handles incoming msgPackets. Returns a msg bytes if msg is complete.
639 | // Not goroutine-safe
640 | func (ch *Channel) recvMsgPacket(packet msgPacket) ([]byte, error) {
641 | // log.Debug("Read Msg Packet", "conn", ch.conn, "packet", packet)
642 | if ch.desc.RecvMessageCapacity < len(ch.recving)+len(packet.Bytes) {
643 | return nil, wire.ErrBinaryReadOverflow
644 | }
645 | ch.recving = append(ch.recving, packet.Bytes...)
646 | if packet.EOF == byte(0x01) {
647 | msgBytes := ch.recving
648 | // clear the slice without re-allocating.
649 | // http://stackoverflow.com/questions/16971741/how-do-you-clear-a-slice-in-go
650 | // suggests this could be a memory leak, but we might as well keep the memory for the channel until it closes,
651 | // at which point the recving slice stops being used and should be garbage collected
652 | ch.recving = ch.recving[:0] // make([]byte, 0, ch.desc.RecvBufferCapacity)
653 | return msgBytes, nil
654 | }
655 | return nil, nil
656 | }
657 |
658 | // Call this periodically to update stats for throttling purposes.
659 | // Not goroutine-safe
660 | func (ch *Channel) updateStats() {
661 | // Exponential decay of stats.
662 | // TODO: optimize.
663 | ch.recentlySent = int64(float64(ch.recentlySent) * 0.8)
664 | }
665 |
666 | //-----------------------------------------------------------------------------
667 |
668 | const (
669 | maxMsgPacketPayloadSize = 1024
670 | maxMsgPacketOverheadSize = 10 // It's actually lower but good enough
671 | maxMsgPacketTotalSize = maxMsgPacketPayloadSize + maxMsgPacketOverheadSize
672 | packetTypePing = byte(0x01)
673 | packetTypePong = byte(0x02)
674 | packetTypeMsg = byte(0x03)
675 | )
676 |
677 | // Messages in channels are chopped into smaller msgPackets for multiplexing.
678 | type msgPacket struct {
679 | ChannelID byte
680 | EOF byte // 1 means message ends here.
681 | Bytes []byte
682 | }
683 |
684 | func (p msgPacket) String() string {
685 | return fmt.Sprintf("MsgPacket{%X:%X T:%X}", p.ChannelID, p.Bytes, p.EOF)
686 | }
687 |
--------------------------------------------------------------------------------
/connection_test.go:
--------------------------------------------------------------------------------
1 | package p2p_test
2 |
3 | import (
4 | "net"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | p2p "github.com/tendermint/go-p2p"
11 | )
12 |
13 | func createMConnection(conn net.Conn) *p2p.MConnection {
14 | onReceive := func(chID byte, msgBytes []byte) {
15 | }
16 | onError := func(r interface{}) {
17 | }
18 | return createMConnectionWithCallbacks(conn, onReceive, onError)
19 | }
20 |
21 | func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msgBytes []byte), onError func(r interface{})) *p2p.MConnection {
22 | chDescs := []*p2p.ChannelDescriptor{&p2p.ChannelDescriptor{ID: 0x01, Priority: 1, SendQueueCapacity: 1}}
23 | return p2p.NewMConnection(conn, chDescs, onReceive, onError)
24 | }
25 |
26 | func TestMConnectionSend(t *testing.T) {
27 | assert, require := assert.New(t), require.New(t)
28 |
29 | server, client := net.Pipe()
30 | defer server.Close()
31 | defer client.Close()
32 |
33 | mconn := createMConnection(client)
34 | _, err := mconn.Start()
35 | require.Nil(err)
36 | defer mconn.Stop()
37 |
38 | msg := "Ant-Man"
39 | assert.True(mconn.Send(0x01, msg))
40 | // Note: subsequent Send/TrySend calls could pass because we are reading from
41 | // the send queue in a separate goroutine.
42 | server.Read(make([]byte, len(msg)))
43 | assert.True(mconn.CanSend(0x01))
44 |
45 | msg = "Spider-Man"
46 | assert.True(mconn.TrySend(0x01, msg))
47 | server.Read(make([]byte, len(msg)))
48 |
49 | assert.False(mconn.CanSend(0x05), "CanSend should return false because channel is unknown")
50 | assert.False(mconn.Send(0x05, "Absorbing Man"), "Send should return false because channel is unknown")
51 | }
52 |
53 | func TestMConnectionReceive(t *testing.T) {
54 | assert, require := assert.New(t), require.New(t)
55 |
56 | server, client := net.Pipe()
57 | defer server.Close()
58 | defer client.Close()
59 |
60 | receivedCh := make(chan []byte)
61 | errorsCh := make(chan interface{})
62 | onReceive := func(chID byte, msgBytes []byte) {
63 | receivedCh <- msgBytes
64 | }
65 | onError := func(r interface{}) {
66 | errorsCh <- r
67 | }
68 | mconn1 := createMConnectionWithCallbacks(client, onReceive, onError)
69 | _, err := mconn1.Start()
70 | require.Nil(err)
71 | defer mconn1.Stop()
72 |
73 | mconn2 := createMConnection(server)
74 | _, err = mconn2.Start()
75 | require.Nil(err)
76 | defer mconn2.Stop()
77 |
78 | msg := "Cyclops"
79 | assert.True(mconn2.Send(0x01, msg))
80 |
81 | select {
82 | case receivedBytes := <-receivedCh:
83 | assert.Equal([]byte(msg), receivedBytes[2:]) // first 3 bytes are internal
84 | case err := <-errorsCh:
85 | t.Fatalf("Expected %s, got %+v", msg, err)
86 | case <-time.After(500 * time.Millisecond):
87 | t.Fatalf("Did not receive %s message in 500ms", msg)
88 | }
89 | }
90 |
91 | func TestMConnectionStatus(t *testing.T) {
92 | assert, require := assert.New(t), require.New(t)
93 |
94 | server, client := net.Pipe()
95 | defer server.Close()
96 | defer client.Close()
97 |
98 | mconn := createMConnection(client)
99 | _, err := mconn.Start()
100 | require.Nil(err)
101 | defer mconn.Stop()
102 |
103 | status := mconn.Status()
104 | assert.NotNil(status)
105 | assert.Zero(status.Channels[0].SendQueueSize)
106 | }
107 |
108 | func TestMConnectionStopsAndReturnsError(t *testing.T) {
109 | assert, require := assert.New(t), require.New(t)
110 |
111 | server, client := net.Pipe()
112 | defer server.Close()
113 | defer client.Close()
114 |
115 | receivedCh := make(chan []byte)
116 | errorsCh := make(chan interface{})
117 | onReceive := func(chID byte, msgBytes []byte) {
118 | receivedCh <- msgBytes
119 | }
120 | onError := func(r interface{}) {
121 | errorsCh <- r
122 | }
123 | mconn := createMConnectionWithCallbacks(client, onReceive, onError)
124 | _, err := mconn.Start()
125 | require.Nil(err)
126 | defer mconn.Stop()
127 |
128 | client.Close()
129 |
130 | select {
131 | case receivedBytes := <-receivedCh:
132 | t.Fatalf("Expected error, got %v", receivedBytes)
133 | case err := <-errorsCh:
134 | assert.NotNil(err)
135 | assert.False(mconn.IsRunning())
136 | case <-time.After(500 * time.Millisecond):
137 | t.Fatal("Did not receive error in 500ms")
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/fuzz.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "math/rand"
5 | "net"
6 | "sync"
7 | "time"
8 | )
9 |
10 | const (
11 | // FuzzModeDrop is a mode in which we randomly drop reads/writes, connections or sleep
12 | FuzzModeDrop = iota
13 | // FuzzModeDelay is a mode in which we randomly sleep
14 | FuzzModeDelay
15 | )
16 |
17 | // FuzzedConnection wraps any net.Conn and depending on the mode either delays
18 | // reads/writes or randomly drops reads/writes/connections.
19 | type FuzzedConnection struct {
20 | conn net.Conn
21 |
22 | mtx sync.Mutex
23 | start <-chan time.Time
24 | active bool
25 |
26 | config *FuzzConnConfig
27 | }
28 |
29 | // FuzzConnConfig is a FuzzedConnection configuration.
30 | type FuzzConnConfig struct {
31 | Mode int
32 | MaxDelay time.Duration
33 | ProbDropRW float64
34 | ProbDropConn float64
35 | ProbSleep float64
36 | }
37 |
38 | // DefaultFuzzConnConfig returns the default config.
39 | func DefaultFuzzConnConfig() *FuzzConnConfig {
40 | return &FuzzConnConfig{
41 | Mode: FuzzModeDrop,
42 | MaxDelay: 3 * time.Second,
43 | ProbDropRW: 0.2,
44 | ProbDropConn: 0.00,
45 | ProbSleep: 0.00,
46 | }
47 | }
48 |
49 | // FuzzConn creates a new FuzzedConnection. Fuzzing starts immediately.
50 | func FuzzConn(conn net.Conn) net.Conn {
51 | return FuzzConnFromConfig(conn, DefaultFuzzConnConfig())
52 | }
53 |
54 | // FuzzConnFromConfig creates a new FuzzedConnection from a config. Fuzzing
55 | // starts immediately.
56 | func FuzzConnFromConfig(conn net.Conn, config *FuzzConnConfig) net.Conn {
57 | return &FuzzedConnection{
58 | conn: conn,
59 | start: make(<-chan time.Time),
60 | active: true,
61 | config: config,
62 | }
63 | }
64 |
65 | // FuzzConnAfter creates a new FuzzedConnection. Fuzzing starts when the
66 | // duration elapses.
67 | func FuzzConnAfter(conn net.Conn, d time.Duration) net.Conn {
68 | return FuzzConnAfterFromConfig(conn, d, DefaultFuzzConnConfig())
69 | }
70 |
71 | // FuzzConnAfterFromConfig creates a new FuzzedConnection from a config.
72 | // Fuzzing starts when the duration elapses.
73 | func FuzzConnAfterFromConfig(conn net.Conn, d time.Duration, config *FuzzConnConfig) net.Conn {
74 | return &FuzzedConnection{
75 | conn: conn,
76 | start: time.After(d),
77 | active: false,
78 | config: config,
79 | }
80 | }
81 |
82 | // Config returns the connection's config.
83 | func (fc *FuzzedConnection) Config() *FuzzConnConfig {
84 | return fc.config
85 | }
86 |
87 | // Read implements net.Conn.
88 | func (fc *FuzzedConnection) Read(data []byte) (n int, err error) {
89 | if fc.fuzz() {
90 | return 0, nil
91 | }
92 | return fc.conn.Read(data)
93 | }
94 |
95 | // Write implements net.Conn.
96 | func (fc *FuzzedConnection) Write(data []byte) (n int, err error) {
97 | if fc.fuzz() {
98 | return 0, nil
99 | }
100 | return fc.conn.Write(data)
101 | }
102 |
103 | // Close implements net.Conn.
104 | func (fc *FuzzedConnection) Close() error { return fc.conn.Close() }
105 |
106 | // LocalAddr implements net.Conn.
107 | func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() }
108 |
109 | // RemoteAddr implements net.Conn.
110 | func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() }
111 |
112 | // SetDeadline implements net.Conn.
113 | func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) }
114 |
115 | // SetReadDeadline implements net.Conn.
116 | func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error {
117 | return fc.conn.SetReadDeadline(t)
118 | }
119 |
120 | // SetWriteDeadline implements net.Conn.
121 | func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error {
122 | return fc.conn.SetWriteDeadline(t)
123 | }
124 |
125 | func (fc *FuzzedConnection) randomDuration() time.Duration {
126 | maxDelayMillis := int(fc.config.MaxDelay.Nanoseconds() / 1000)
127 | return time.Millisecond * time.Duration(rand.Int()%maxDelayMillis)
128 | }
129 |
130 | // implements the fuzz (delay, kill conn)
131 | // and returns whether or not the read/write should be ignored
132 | func (fc *FuzzedConnection) fuzz() bool {
133 | if !fc.shouldFuzz() {
134 | return false
135 | }
136 |
137 | switch fc.config.Mode {
138 | case FuzzModeDrop:
139 | // randomly drop the r/w, drop the conn, or sleep
140 | r := rand.Float64()
141 | if r <= fc.config.ProbDropRW {
142 | return true
143 | } else if r < fc.config.ProbDropRW+fc.config.ProbDropConn {
144 | // XXX: can't this fail because machine precision?
145 | // XXX: do we need an error?
146 | fc.Close()
147 | return true
148 | } else if r < fc.config.ProbDropRW+fc.config.ProbDropConn+fc.config.ProbSleep {
149 | time.Sleep(fc.randomDuration())
150 | }
151 | case FuzzModeDelay:
152 | // sleep a bit
153 | time.Sleep(fc.randomDuration())
154 | }
155 | return false
156 | }
157 |
158 | func (fc *FuzzedConnection) shouldFuzz() bool {
159 | if fc.active {
160 | return true
161 | }
162 |
163 | fc.mtx.Lock()
164 | defer fc.mtx.Unlock()
165 |
166 | select {
167 | case <-fc.start:
168 | fc.active = true
169 | return true
170 | default:
171 | return false
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/glide.lock:
--------------------------------------------------------------------------------
1 | hash: f3d76bef9548cc37ad6038cb55f0812bac7e64735a99995c9da85010eef27f50
2 | updated: 2017-04-19T00:00:50.949249104-04:00
3 | imports:
4 | - name: github.com/btcsuite/btcd
5 | version: b8df516b4b267acf2de46be593a9d948d1d2c420
6 | subpackages:
7 | - btcec
8 | - name: github.com/btcsuite/fastsha256
9 | version: 637e656429416087660c84436a2a035d69d54e2e
10 | - name: github.com/BurntSushi/toml
11 | version: 99064174e013895bbd9b025c31100bd1d9b590ca
12 | - name: github.com/go-stack/stack
13 | version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
14 | - name: github.com/mattn/go-colorable
15 | version: 9fdad7c47650b7d2e1da50644c1f4ba7f172f252
16 | - name: github.com/mattn/go-isatty
17 | version: 56b76bdf51f7708750eac80fa38b952bb9f32639
18 | - name: github.com/pkg/errors
19 | version: 645ef00459ed84a119197bfb8d8205042c6df63d
20 | - name: github.com/tendermint/ed25519
21 | version: 1f52c6f8b8a5c7908aff4497c186af344b428925
22 | subpackages:
23 | - edwards25519
24 | - extra25519
25 | - name: github.com/tendermint/go-common
26 | version: f9e3db037330c8a8d61d3966de8473eaf01154fa
27 | - name: github.com/tendermint/go-config
28 | version: 620dcbbd7d587cf3599dedbf329b64311b0c307a
29 | - name: github.com/tendermint/go-crypto
30 | version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da
31 | - name: github.com/tendermint/go-data
32 | version: e7fcc6d081ec8518912fcdc103188275f83a3ee5
33 | - name: github.com/tendermint/go-flowrate
34 | version: a20c98e61957faa93b4014fbd902f20ab9317a6a
35 | subpackages:
36 | - flowrate
37 | - name: github.com/tendermint/go-logger
38 | version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2
39 | - name: github.com/tendermint/go-wire
40 | version: c1c9a57ab8038448ddea1714c0698f8051e5748c
41 | - name: github.com/tendermint/log15
42 | version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
43 | subpackages:
44 | - term
45 | - name: golang.org/x/crypto
46 | version: 1f22c0103821b9390939b6776727195525381532
47 | subpackages:
48 | - curve25519
49 | - nacl/box
50 | - nacl/secretbox
51 | - openpgp/armor
52 | - openpgp/errors
53 | - poly1305
54 | - ripemd160
55 | - salsa20/salsa
56 | - name: golang.org/x/sys
57 | version: 50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e
58 | subpackages:
59 | - unix
60 | testImports:
61 | - name: github.com/davecgh/go-spew
62 | version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
63 | subpackages:
64 | - spew
65 | - name: github.com/pmezard/go-difflib
66 | version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
67 | subpackages:
68 | - difflib
69 | - name: github.com/stretchr/testify
70 | version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
71 | subpackages:
72 | - assert
73 | - require
74 |
--------------------------------------------------------------------------------
/glide.yaml:
--------------------------------------------------------------------------------
1 | package: github.com/tendermint/go-p2p
2 | import:
3 | - package: github.com/tendermint/go-common
4 | version: develop
5 | - package: github.com/tendermint/go-config
6 | version: develop
7 | - package: github.com/tendermint/go-crypto
8 | version: develop
9 | - package: github.com/tendermint/go-data
10 | version: develop
11 | - package: github.com/tendermint/go-flowrate
12 | subpackages:
13 | - flowrate
14 | - package: github.com/tendermint/go-logger
15 | version: develop
16 | - package: github.com/tendermint/go-wire
17 | version: develop
18 | - package: github.com/tendermint/log15
19 | - package: golang.org/x/crypto
20 | subpackages:
21 | - nacl/box
22 | - nacl/secretbox
23 | - ripemd160
24 | - package: github.com/pkg/errors
25 | testImport:
26 | - package: github.com/stretchr/testify
27 | subpackages:
28 | - assert
29 | - require
30 |
--------------------------------------------------------------------------------
/ip_range_counter.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // TODO Test
8 | func AddToIPRangeCounts(counts map[string]int, ip string) map[string]int {
9 | changes := make(map[string]int)
10 | ipParts := strings.Split(ip, ":")
11 | for i := 1; i < len(ipParts); i++ {
12 | prefix := strings.Join(ipParts[:i], ":")
13 | counts[prefix] += 1
14 | changes[prefix] = counts[prefix]
15 | }
16 | return changes
17 | }
18 |
19 | // TODO Test
20 | func CheckIPRangeCounts(counts map[string]int, limits []int) bool {
21 | for prefix, count := range counts {
22 | ipParts := strings.Split(prefix, ":")
23 | numParts := len(ipParts)
24 | if limits[numParts] < count {
25 | return false
26 | }
27 | }
28 | return true
29 | }
30 |
--------------------------------------------------------------------------------
/listener.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "strconv"
7 | "time"
8 |
9 | . "github.com/tendermint/go-common"
10 | "github.com/tendermint/go-p2p/upnp"
11 | )
12 |
13 | type Listener interface {
14 | Connections() <-chan net.Conn
15 | InternalAddress() *NetAddress
16 | ExternalAddress() *NetAddress
17 | String() string
18 | Stop() bool
19 | }
20 |
21 | // Implements Listener
22 | type DefaultListener struct {
23 | BaseService
24 |
25 | listener net.Listener
26 | intAddr *NetAddress
27 | extAddr *NetAddress
28 | connections chan net.Conn
29 | }
30 |
31 | const (
32 | numBufferedConnections = 10
33 | defaultExternalPort = 8770
34 | tryListenSeconds = 5
35 | )
36 |
37 | func splitHostPort(addr string) (host string, port int) {
38 | host, portStr, err := net.SplitHostPort(addr)
39 | if err != nil {
40 | PanicSanity(err)
41 | }
42 | port, err = strconv.Atoi(portStr)
43 | if err != nil {
44 | PanicSanity(err)
45 | }
46 | return host, port
47 | }
48 |
49 | // skipUPNP: If true, does not try getUPNPExternalAddress()
50 | func NewDefaultListener(protocol string, lAddr string, skipUPNP bool) Listener {
51 | // Local listen IP & port
52 | lAddrIP, lAddrPort := splitHostPort(lAddr)
53 |
54 | // Create listener
55 | var listener net.Listener
56 | var err error
57 | for i := 0; i < tryListenSeconds; i++ {
58 | listener, err = net.Listen(protocol, lAddr)
59 | if err == nil {
60 | break
61 | } else if i < tryListenSeconds-1 {
62 | time.Sleep(time.Second * 1)
63 | }
64 | }
65 | if err != nil {
66 | PanicCrisis(err)
67 | }
68 | // Actual listener local IP & port
69 | listenerIP, listenerPort := splitHostPort(listener.Addr().String())
70 | log.Info("Local listener", "ip", listenerIP, "port", listenerPort)
71 |
72 | // Determine internal address...
73 | var intAddr *NetAddress
74 | intAddr, err = NewNetAddressString(lAddr)
75 | if err != nil {
76 | PanicCrisis(err)
77 | }
78 |
79 | // Determine external address...
80 | var extAddr *NetAddress
81 | if !skipUPNP {
82 | // If the lAddrIP is INADDR_ANY, try UPnP
83 | if lAddrIP == "" || lAddrIP == "0.0.0.0" {
84 | extAddr = getUPNPExternalAddress(lAddrPort, listenerPort)
85 | }
86 | }
87 | // Otherwise just use the local address...
88 | if extAddr == nil {
89 | extAddr = getNaiveExternalAddress(listenerPort)
90 | }
91 | if extAddr == nil {
92 | PanicCrisis("Could not determine external address!")
93 | }
94 |
95 | dl := &DefaultListener{
96 | listener: listener,
97 | intAddr: intAddr,
98 | extAddr: extAddr,
99 | connections: make(chan net.Conn, numBufferedConnections),
100 | }
101 | dl.BaseService = *NewBaseService(log, "DefaultListener", dl)
102 | dl.Start() // Started upon construction
103 | return dl
104 | }
105 |
106 | func (l *DefaultListener) OnStart() error {
107 | l.BaseService.OnStart()
108 | go l.listenRoutine()
109 | return nil
110 | }
111 |
112 | func (l *DefaultListener) OnStop() {
113 | l.BaseService.OnStop()
114 | l.listener.Close()
115 | }
116 |
117 | // Accept connections and pass on the channel
118 | func (l *DefaultListener) listenRoutine() {
119 | for {
120 | conn, err := l.listener.Accept()
121 |
122 | if !l.IsRunning() {
123 | break // Go to cleanup
124 | }
125 |
126 | // listener wasn't stopped,
127 | // yet we encountered an error.
128 | if err != nil {
129 | PanicCrisis(err)
130 | }
131 |
132 | l.connections <- conn
133 | }
134 |
135 | // Cleanup
136 | close(l.connections)
137 | for _ = range l.connections {
138 | // Drain
139 | }
140 | }
141 |
142 | // A channel of inbound connections.
143 | // It gets closed when the listener closes.
144 | func (l *DefaultListener) Connections() <-chan net.Conn {
145 | return l.connections
146 | }
147 |
148 | func (l *DefaultListener) InternalAddress() *NetAddress {
149 | return l.intAddr
150 | }
151 |
152 | func (l *DefaultListener) ExternalAddress() *NetAddress {
153 | return l.extAddr
154 | }
155 |
156 | // NOTE: The returned listener is already Accept()'ing.
157 | // So it's not suitable to pass into http.Serve().
158 | func (l *DefaultListener) NetListener() net.Listener {
159 | return l.listener
160 | }
161 |
162 | func (l *DefaultListener) String() string {
163 | return fmt.Sprintf("Listener(@%v)", l.extAddr)
164 | }
165 |
166 | /* external address helpers */
167 |
168 | // UPNP external address discovery & port mapping
169 | func getUPNPExternalAddress(externalPort, internalPort int) *NetAddress {
170 | log.Info("Getting UPNP external address")
171 | nat, err := upnp.Discover()
172 | if err != nil {
173 | log.Info("Could not perform UPNP discover", "error", err)
174 | return nil
175 | }
176 |
177 | ext, err := nat.GetExternalAddress()
178 | if err != nil {
179 | log.Info("Could not get UPNP external address", "error", err)
180 | return nil
181 | }
182 |
183 | // UPnP can't seem to get the external port, so let's just be explicit.
184 | if externalPort == 0 {
185 | externalPort = defaultExternalPort
186 | }
187 |
188 | externalPort, err = nat.AddPortMapping("tcp", externalPort, internalPort, "tendermint", 0)
189 | if err != nil {
190 | log.Info("Could not add UPNP port mapping", "error", err)
191 | return nil
192 | }
193 |
194 | log.Info("Got UPNP external address", "address", ext)
195 | return NewNetAddressIPPort(ext, uint16(externalPort))
196 | }
197 |
198 | // TODO: use syscalls: http://pastebin.com/9exZG4rh
199 | func getNaiveExternalAddress(port int) *NetAddress {
200 | addrs, err := net.InterfaceAddrs()
201 | if err != nil {
202 | PanicCrisis(Fmt("Could not fetch interface addresses: %v", err))
203 | }
204 |
205 | for _, a := range addrs {
206 | ipnet, ok := a.(*net.IPNet)
207 | if !ok {
208 | continue
209 | }
210 | v4 := ipnet.IP.To4()
211 | if v4 == nil || v4[0] == 127 {
212 | continue
213 | } // loopback
214 | return NewNetAddressIPPort(ipnet.IP, uint16(port))
215 | }
216 | return nil
217 | }
218 |
--------------------------------------------------------------------------------
/listener_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | func TestListener(t *testing.T) {
9 | // Create a listener
10 | l := NewDefaultListener("tcp", ":8001", true)
11 |
12 | // Dial the listener
13 | lAddr := l.ExternalAddress()
14 | connOut, err := lAddr.Dial()
15 | if err != nil {
16 | t.Fatalf("Could not connect to listener address %v", lAddr)
17 | } else {
18 | t.Logf("Created a connection to listener address %v", lAddr)
19 | }
20 | connIn, ok := <-l.Connections()
21 | if !ok {
22 | t.Fatalf("Could not get inbound connection from listener")
23 | }
24 |
25 | msg := []byte("hi!")
26 | go connIn.Write(msg)
27 | b := make([]byte, 32)
28 | n, err := connOut.Read(b)
29 | if err != nil {
30 | t.Fatalf("Error reading off connection: %v", err)
31 | }
32 |
33 | b = b[:n]
34 | if !bytes.Equal(msg, b) {
35 | t.Fatalf("Got %s, expected %s", b, msg)
36 | }
37 |
38 | // Close the server, no longer needed.
39 | l.Stop()
40 | }
41 |
--------------------------------------------------------------------------------
/log.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "github.com/tendermint/go-logger"
5 | )
6 |
7 | var log = logger.New("module", "p2p")
8 |
--------------------------------------------------------------------------------
/netaddress.go:
--------------------------------------------------------------------------------
1 | // Modified for Tendermint
2 | // Originally Copyright (c) 2013-2014 Conformal Systems LLC.
3 | // https://github.com/conformal/btcd/blob/master/LICENSE
4 |
5 | package p2p
6 |
7 | import (
8 | "errors"
9 | "flag"
10 | "net"
11 | "strconv"
12 | "time"
13 |
14 | cmn "github.com/tendermint/go-common"
15 | )
16 |
17 | // NetAddress defines information about a peer on the network
18 | // including its IP address, and port.
19 | type NetAddress struct {
20 | IP net.IP
21 | Port uint16
22 | str string
23 | }
24 |
25 | // NewNetAddress returns a new NetAddress using the provided TCP
26 | // address. When testing, other net.Addr (except TCP) will result in
27 | // using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will
28 | // panic.
29 | // TODO: socks proxies?
30 | func NewNetAddress(addr net.Addr) *NetAddress {
31 | tcpAddr, ok := addr.(*net.TCPAddr)
32 | if !ok {
33 | if flag.Lookup("test.v") == nil { // normal run
34 | cmn.PanicSanity(cmn.Fmt("Only TCPAddrs are supported. Got: %v", addr))
35 | } else { // in testing
36 | return NewNetAddressIPPort(net.IP("0.0.0.0"), 0)
37 | }
38 | }
39 | ip := tcpAddr.IP
40 | port := uint16(tcpAddr.Port)
41 | return NewNetAddressIPPort(ip, port)
42 | }
43 |
44 | // NewNetAddressString returns a new NetAddress using the provided
45 | // address in the form of "IP:Port". Also resolves the host if host
46 | // is not an IP.
47 | func NewNetAddressString(addr string) (*NetAddress, error) {
48 |
49 | host, portStr, err := net.SplitHostPort(addr)
50 | if err != nil {
51 | return nil, err
52 | }
53 |
54 | ip := net.ParseIP(host)
55 | if ip == nil {
56 | if len(host) > 0 {
57 | ips, err := net.LookupIP(host)
58 | if err != nil {
59 | return nil, err
60 | }
61 | ip = ips[0]
62 | }
63 | }
64 |
65 | port, err := strconv.ParseUint(portStr, 10, 16)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | na := NewNetAddressIPPort(ip, uint16(port))
71 | return na, nil
72 | }
73 |
74 | // NewNetAddressStrings returns an array of NetAddress'es build using
75 | // the provided strings.
76 | func NewNetAddressStrings(addrs []string) ([]*NetAddress, error) {
77 | netAddrs := make([]*NetAddress, len(addrs))
78 | for i, addr := range addrs {
79 | netAddr, err := NewNetAddressString(addr)
80 | if err != nil {
81 | return nil, errors.New(cmn.Fmt("Error in address %s: %v", addr, err))
82 | }
83 | netAddrs[i] = netAddr
84 | }
85 | return netAddrs, nil
86 | }
87 |
88 | // NewNetAddressIPPort returns a new NetAddress using the provided IP
89 | // and port number.
90 | func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress {
91 | na := &NetAddress{
92 | IP: ip,
93 | Port: port,
94 | str: net.JoinHostPort(
95 | ip.String(),
96 | strconv.FormatUint(uint64(port), 10),
97 | ),
98 | }
99 | return na
100 | }
101 |
102 | // Equals reports whether na and other are the same addresses.
103 | func (na *NetAddress) Equals(other interface{}) bool {
104 | if o, ok := other.(*NetAddress); ok {
105 | return na.String() == o.String()
106 | }
107 |
108 | return false
109 | }
110 |
111 | func (na *NetAddress) Less(other interface{}) bool {
112 | if o, ok := other.(*NetAddress); ok {
113 | return na.String() < o.String()
114 | }
115 |
116 | cmn.PanicSanity("Cannot compare unequal types")
117 | return false
118 | }
119 |
120 | // String representation.
121 | func (na *NetAddress) String() string {
122 | if na.str == "" {
123 | na.str = net.JoinHostPort(
124 | na.IP.String(),
125 | strconv.FormatUint(uint64(na.Port), 10),
126 | )
127 | }
128 | return na.str
129 | }
130 |
131 | // Dial calls net.Dial on the address.
132 | func (na *NetAddress) Dial() (net.Conn, error) {
133 | conn, err := net.Dial("tcp", na.String())
134 | if err != nil {
135 | return nil, err
136 | }
137 | return conn, nil
138 | }
139 |
140 | // DialTimeout calls net.DialTimeout on the address.
141 | func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) {
142 | conn, err := net.DialTimeout("tcp", na.String(), timeout)
143 | if err != nil {
144 | return nil, err
145 | }
146 | return conn, nil
147 | }
148 |
149 | // Routable returns true if the address is routable.
150 | func (na *NetAddress) Routable() bool {
151 | // TODO(oga) bitcoind doesn't include RFC3849 here, but should we?
152 | return na.Valid() && !(na.RFC1918() || na.RFC3927() || na.RFC4862() ||
153 | na.RFC4193() || na.RFC4843() || na.Local())
154 | }
155 |
156 | // For IPv4 these are either a 0 or all bits set address. For IPv6 a zero
157 | // address or one that matches the RFC3849 documentation address format.
158 | func (na *NetAddress) Valid() bool {
159 | return na.IP != nil && !(na.IP.IsUnspecified() || na.RFC3849() ||
160 | na.IP.Equal(net.IPv4bcast))
161 | }
162 |
163 | // Local returns true if it is a local address.
164 | func (na *NetAddress) Local() bool {
165 | return na.IP.IsLoopback() || zero4.Contains(na.IP)
166 | }
167 |
168 | // ReachabilityTo checks whenever o can be reached from na.
169 | func (na *NetAddress) ReachabilityTo(o *NetAddress) int {
170 | const (
171 | Unreachable = 0
172 | Default = iota
173 | Teredo
174 | Ipv6_weak
175 | Ipv4
176 | Ipv6_strong
177 | Private
178 | )
179 | if !na.Routable() {
180 | return Unreachable
181 | } else if na.RFC4380() {
182 | if !o.Routable() {
183 | return Default
184 | } else if o.RFC4380() {
185 | return Teredo
186 | } else if o.IP.To4() != nil {
187 | return Ipv4
188 | } else { // ipv6
189 | return Ipv6_weak
190 | }
191 | } else if na.IP.To4() != nil {
192 | if o.Routable() && o.IP.To4() != nil {
193 | return Ipv4
194 | }
195 | return Default
196 | } else /* ipv6 */ {
197 | var tunnelled bool
198 | // Is our v6 is tunnelled?
199 | if o.RFC3964() || o.RFC6052() || o.RFC6145() {
200 | tunnelled = true
201 | }
202 | if !o.Routable() {
203 | return Default
204 | } else if o.RFC4380() {
205 | return Teredo
206 | } else if o.IP.To4() != nil {
207 | return Ipv4
208 | } else if tunnelled {
209 | // only prioritise ipv6 if we aren't tunnelling it.
210 | return Ipv6_weak
211 | }
212 | return Ipv6_strong
213 | }
214 | }
215 |
216 | // RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12)
217 | // RFC3849: IPv6 Documentation address (2001:0DB8::/32)
218 | // RFC3927: IPv4 Autoconfig (169.254.0.0/16)
219 | // RFC3964: IPv6 6to4 (2002::/16)
220 | // RFC4193: IPv6 unique local (FC00::/7)
221 | // RFC4380: IPv6 Teredo tunneling (2001::/32)
222 | // RFC4843: IPv6 ORCHID: (2001:10::/28)
223 | // RFC4862: IPv6 Autoconfig (FE80::/64)
224 | // RFC6052: IPv6 well known prefix (64:FF9B::/96)
225 | // RFC6145: IPv6 IPv4 translated address ::FFFF:0:0:0/96
226 | var rfc1918_10 = net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)}
227 | var rfc1918_192 = net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}
228 | var rfc1918_172 = net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)}
229 | var rfc3849 = net.IPNet{IP: net.ParseIP("2001:0DB8::"), Mask: net.CIDRMask(32, 128)}
230 | var rfc3927 = net.IPNet{IP: net.ParseIP("169.254.0.0"), Mask: net.CIDRMask(16, 32)}
231 | var rfc3964 = net.IPNet{IP: net.ParseIP("2002::"), Mask: net.CIDRMask(16, 128)}
232 | var rfc4193 = net.IPNet{IP: net.ParseIP("FC00::"), Mask: net.CIDRMask(7, 128)}
233 | var rfc4380 = net.IPNet{IP: net.ParseIP("2001::"), Mask: net.CIDRMask(32, 128)}
234 | var rfc4843 = net.IPNet{IP: net.ParseIP("2001:10::"), Mask: net.CIDRMask(28, 128)}
235 | var rfc4862 = net.IPNet{IP: net.ParseIP("FE80::"), Mask: net.CIDRMask(64, 128)}
236 | var rfc6052 = net.IPNet{IP: net.ParseIP("64:FF9B::"), Mask: net.CIDRMask(96, 128)}
237 | var rfc6145 = net.IPNet{IP: net.ParseIP("::FFFF:0:0:0"), Mask: net.CIDRMask(96, 128)}
238 | var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(8, 32)}
239 |
240 | func (na *NetAddress) RFC1918() bool {
241 | return rfc1918_10.Contains(na.IP) ||
242 | rfc1918_192.Contains(na.IP) ||
243 | rfc1918_172.Contains(na.IP)
244 | }
245 | func (na *NetAddress) RFC3849() bool { return rfc3849.Contains(na.IP) }
246 | func (na *NetAddress) RFC3927() bool { return rfc3927.Contains(na.IP) }
247 | func (na *NetAddress) RFC3964() bool { return rfc3964.Contains(na.IP) }
248 | func (na *NetAddress) RFC4193() bool { return rfc4193.Contains(na.IP) }
249 | func (na *NetAddress) RFC4380() bool { return rfc4380.Contains(na.IP) }
250 | func (na *NetAddress) RFC4843() bool { return rfc4843.Contains(na.IP) }
251 | func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) }
252 | func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) }
253 | func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) }
254 |
--------------------------------------------------------------------------------
/netaddress_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "net"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestNewNetAddress(t *testing.T) {
12 | assert, require := assert.New(t), require.New(t)
13 |
14 | tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
15 | require.Nil(err)
16 | addr := NewNetAddress(tcpAddr)
17 |
18 | assert.Equal("127.0.0.1:8080", addr.String())
19 |
20 | assert.NotPanics(func() {
21 | NewNetAddress(&net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000})
22 | }, "Calling NewNetAddress with UDPAddr should not panic in testing")
23 | }
24 |
25 | func TestNewNetAddressString(t *testing.T) {
26 | assert, require := assert.New(t), require.New(t)
27 |
28 | tests := []struct {
29 | addr string
30 | correct bool
31 | }{
32 | {"127.0.0.1:8080", true},
33 | {"127.0.0:8080", false},
34 | {"a", false},
35 | {"127.0.0.1:a", false},
36 | {"a:8080", false},
37 | {"8082", false},
38 | {"127.0.0:8080000", false},
39 | }
40 |
41 | for _, t := range tests {
42 | addr, err := NewNetAddressString(t.addr)
43 | if t.correct {
44 | require.Nil(err)
45 | assert.Equal(t.addr, addr.String())
46 | } else {
47 | require.NotNil(err)
48 | }
49 | }
50 | }
51 |
52 | func TestNewNetAddressStrings(t *testing.T) {
53 | assert, require := assert.New(t), require.New(t)
54 | addrs, err := NewNetAddressStrings([]string{"127.0.0.1:8080", "127.0.0.2:8080"})
55 | require.Nil(err)
56 |
57 | assert.Equal(2, len(addrs))
58 | }
59 |
60 | func TestNewNetAddressIPPort(t *testing.T) {
61 | assert := assert.New(t)
62 | addr := NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8080)
63 |
64 | assert.Equal("127.0.0.1:8080", addr.String())
65 | }
66 |
67 | func TestNetAddressProperties(t *testing.T) {
68 | assert, require := assert.New(t), require.New(t)
69 |
70 | // TODO add more test cases
71 | tests := []struct {
72 | addr string
73 | valid bool
74 | local bool
75 | routable bool
76 | }{
77 | {"127.0.0.1:8080", true, true, false},
78 | {"ya.ru:80", true, false, true},
79 | }
80 |
81 | for _, t := range tests {
82 | addr, err := NewNetAddressString(t.addr)
83 | require.Nil(err)
84 |
85 | assert.Equal(t.valid, addr.Valid())
86 | assert.Equal(t.local, addr.Local())
87 | assert.Equal(t.routable, addr.Routable())
88 | }
89 | }
90 |
91 | func TestNetAddressReachabilityTo(t *testing.T) {
92 | assert, require := assert.New(t), require.New(t)
93 |
94 | // TODO add more test cases
95 | tests := []struct {
96 | addr string
97 | other string
98 | reachability int
99 | }{
100 | {"127.0.0.1:8080", "127.0.0.1:8081", 0},
101 | {"ya.ru:80", "127.0.0.1:8080", 1},
102 | }
103 |
104 | for _, t := range tests {
105 | addr, err := NewNetAddressString(t.addr)
106 | require.Nil(err)
107 |
108 | other, err := NewNetAddressString(t.other)
109 | require.Nil(err)
110 |
111 | assert.Equal(t.reachability, addr.ReachabilityTo(other))
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/peer.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net"
7 | "time"
8 |
9 | "github.com/pkg/errors"
10 | cmn "github.com/tendermint/go-common"
11 | crypto "github.com/tendermint/go-crypto"
12 | wire "github.com/tendermint/go-wire"
13 | )
14 |
15 | // Peer could be marked as persistent, in which case you can use
16 | // Redial function to reconnect. Note that inbound peers can't be
17 | // made persistent. They should be made persistent on the other end.
18 | //
19 | // Before using a peer, you will need to perform a handshake on connection.
20 | type Peer struct {
21 | cmn.BaseService
22 |
23 | outbound bool
24 |
25 | conn net.Conn // source connection
26 | mconn *MConnection // multiplex connection
27 |
28 | persistent bool
29 | config *PeerConfig
30 |
31 | *NodeInfo
32 | Key string
33 | Data *cmn.CMap // User data.
34 | }
35 |
36 | // PeerConfig is a Peer configuration.
37 | type PeerConfig struct {
38 | AuthEnc bool // authenticated encryption
39 |
40 | HandshakeTimeout time.Duration
41 | DialTimeout time.Duration
42 |
43 | MConfig *MConnConfig
44 |
45 | Fuzz bool // fuzz connection (for testing)
46 | FuzzConfig *FuzzConnConfig
47 | }
48 |
49 | // DefaultPeerConfig returns the default config.
50 | func DefaultPeerConfig() *PeerConfig {
51 | return &PeerConfig{
52 | AuthEnc: true,
53 | HandshakeTimeout: 2 * time.Second,
54 | DialTimeout: 3 * time.Second,
55 | MConfig: DefaultMConnConfig(),
56 | Fuzz: false,
57 | FuzzConfig: DefaultFuzzConnConfig(),
58 | }
59 | }
60 |
61 | func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) {
62 | return newOutboundPeerWithConfig(addr, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, DefaultPeerConfig())
63 | }
64 |
65 | func newOutboundPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) {
66 | conn, err := dial(addr, config)
67 | if err != nil {
68 | return nil, errors.Wrap(err, "Error creating peer")
69 | }
70 |
71 | peer, err := newPeerFromConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config)
72 | if err != nil {
73 | conn.Close()
74 | return nil, err
75 | }
76 | return peer, nil
77 | }
78 |
79 | func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) {
80 | return newInboundPeerWithConfig(conn, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, DefaultPeerConfig())
81 | }
82 |
83 | func newInboundPeerWithConfig(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) {
84 | return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config)
85 | }
86 |
87 | func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) {
88 | conn := rawConn
89 |
90 | // Fuzz connection
91 | if config.Fuzz {
92 | // so we have time to do peer handshakes and get set up
93 | conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig)
94 | }
95 |
96 | // Encrypt connection
97 | if config.AuthEnc {
98 | conn.SetDeadline(time.Now().Add(config.HandshakeTimeout))
99 |
100 | var err error
101 | conn, err = MakeSecretConnection(conn, ourNodePrivKey)
102 | if err != nil {
103 | return nil, errors.Wrap(err, "Error creating peer")
104 | }
105 | }
106 |
107 | // Key and NodeInfo are set after Handshake
108 | p := &Peer{
109 | outbound: outbound,
110 | conn: conn,
111 | config: config,
112 | Data: cmn.NewCMap(),
113 | }
114 |
115 | p.mconn = createMConnection(conn, p, reactorsByCh, chDescs, onPeerError, config.MConfig)
116 |
117 | p.BaseService = *cmn.NewBaseService(log, "Peer", p)
118 |
119 | return p, nil
120 | }
121 |
122 | // CloseConn should be used when the peer was created, but never started.
123 | func (p *Peer) CloseConn() {
124 | p.conn.Close()
125 | }
126 |
127 | // makePersistent marks the peer as persistent.
128 | func (p *Peer) makePersistent() {
129 | if !p.outbound {
130 | panic("inbound peers can't be made persistent")
131 | }
132 |
133 | p.persistent = true
134 | }
135 |
136 | // IsPersistent returns true if the peer is persitent, false otherwise.
137 | func (p *Peer) IsPersistent() bool {
138 | return p.persistent
139 | }
140 |
141 | // HandshakeTimeout performs a handshake between a given node and the peer.
142 | // NOTE: blocking
143 | func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) error {
144 | // Set deadline for handshake so we don't block forever on conn.ReadFull
145 | p.conn.SetDeadline(time.Now().Add(timeout))
146 |
147 | var peerNodeInfo = new(NodeInfo)
148 | var err1 error
149 | var err2 error
150 | cmn.Parallel(
151 | func() {
152 | var n int
153 | wire.WriteBinary(ourNodeInfo, p.conn, &n, &err1)
154 | },
155 | func() {
156 | var n int
157 | wire.ReadBinary(peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2)
158 | log.Notice("Peer handshake", "peerNodeInfo", peerNodeInfo)
159 | })
160 | if err1 != nil {
161 | return errors.Wrap(err1, "Error during handshake/write")
162 | }
163 | if err2 != nil {
164 | return errors.Wrap(err2, "Error during handshake/read")
165 | }
166 |
167 | if p.config.AuthEnc {
168 | // Check that the professed PubKey matches the sconn's.
169 | if !peerNodeInfo.PubKey.Equals(p.PubKey()) {
170 | return fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v",
171 | peerNodeInfo.PubKey, p.PubKey())
172 | }
173 | }
174 |
175 | // Remove deadline
176 | p.conn.SetDeadline(time.Time{})
177 |
178 | peerNodeInfo.RemoteAddr = p.Addr().String()
179 |
180 | p.NodeInfo = peerNodeInfo
181 | p.Key = peerNodeInfo.PubKey.KeyString()
182 |
183 | return nil
184 | }
185 |
186 | // Addr returns peer's network address.
187 | func (p *Peer) Addr() net.Addr {
188 | return p.conn.RemoteAddr()
189 | }
190 |
191 | // PubKey returns peer's public key.
192 | func (p *Peer) PubKey() crypto.PubKeyEd25519 {
193 | if p.config.AuthEnc {
194 | return p.conn.(*SecretConnection).RemotePubKey()
195 | }
196 | if p.NodeInfo == nil {
197 | panic("Attempt to get peer's PubKey before calling Handshake")
198 | }
199 | return p.PubKey()
200 | }
201 |
202 | // OnStart implements BaseService.
203 | func (p *Peer) OnStart() error {
204 | p.BaseService.OnStart()
205 | _, err := p.mconn.Start()
206 | return err
207 | }
208 |
209 | // OnStop implements BaseService.
210 | func (p *Peer) OnStop() {
211 | p.BaseService.OnStop()
212 | p.mconn.Stop()
213 | }
214 |
215 | // Connection returns underlying MConnection.
216 | func (p *Peer) Connection() *MConnection {
217 | return p.mconn
218 | }
219 |
220 | // IsOutbound returns true if the connection is outbound, false otherwise.
221 | func (p *Peer) IsOutbound() bool {
222 | return p.outbound
223 | }
224 |
225 | // Send msg to the channel identified by chID byte. Returns false if the send
226 | // queue is full after timeout, specified by MConnection.
227 | func (p *Peer) Send(chID byte, msg interface{}) bool {
228 | if !p.IsRunning() {
229 | // see Switch#Broadcast, where we fetch the list of peers and loop over
230 | // them - while we're looping, one peer may be removed and stopped.
231 | return false
232 | }
233 | return p.mconn.Send(chID, msg)
234 | }
235 |
236 | // TrySend msg to the channel identified by chID byte. Immediately returns
237 | // false if the send queue is full.
238 | func (p *Peer) TrySend(chID byte, msg interface{}) bool {
239 | if !p.IsRunning() {
240 | return false
241 | }
242 | return p.mconn.TrySend(chID, msg)
243 | }
244 |
245 | // CanSend returns true if the send queue is not full, false otherwise.
246 | func (p *Peer) CanSend(chID byte) bool {
247 | if !p.IsRunning() {
248 | return false
249 | }
250 | return p.mconn.CanSend(chID)
251 | }
252 |
253 | // WriteTo writes the peer's public key to w.
254 | func (p *Peer) WriteTo(w io.Writer) (n int64, err error) {
255 | var n_ int
256 | wire.WriteString(p.Key, w, &n_, &err)
257 | n += int64(n_)
258 | return
259 | }
260 |
261 | // String representation.
262 | func (p *Peer) String() string {
263 | if p.outbound {
264 | return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.Key[:12])
265 | }
266 |
267 | return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key[:12])
268 | }
269 |
270 | // Equals reports whenever 2 peers are actually represent the same node.
271 | func (p *Peer) Equals(other *Peer) bool {
272 | return p.Key == other.Key
273 | }
274 |
275 | // Get the data for a given key.
276 | func (p *Peer) Get(key string) interface{} {
277 | return p.Data.Get(key)
278 | }
279 |
280 | func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) {
281 | log.Info("Dialing address", "address", addr)
282 | conn, err := addr.DialTimeout(config.DialTimeout)
283 | if err != nil {
284 | log.Info("Failed dialing address", "address", addr, "error", err)
285 | return nil, err
286 | }
287 | return conn, nil
288 | }
289 |
290 | func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config *MConnConfig) *MConnection {
291 | onReceive := func(chID byte, msgBytes []byte) {
292 | reactor := reactorsByCh[chID]
293 | if reactor == nil {
294 | cmn.PanicSanity(cmn.Fmt("Unknown channel %X", chID))
295 | }
296 | reactor.Receive(chID, p, msgBytes)
297 | }
298 |
299 | onError := func(r interface{}) {
300 | onPeerError(p, r)
301 | }
302 |
303 | return NewMConnectionWithConfig(conn, chDescs, onReceive, onError, config)
304 | }
305 |
--------------------------------------------------------------------------------
/peer_set.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // IPeerSet has a (immutable) subset of the methods of PeerSet.
8 | type IPeerSet interface {
9 | Has(key string) bool
10 | Get(key string) *Peer
11 | List() []*Peer
12 | Size() int
13 | }
14 |
15 | //-----------------------------------------------------------------------------
16 |
17 | // PeerSet is a special structure for keeping a table of peers.
18 | // Iteration over the peers is super fast and thread-safe.
19 | // We also track how many peers per IP range and avoid too many
20 | type PeerSet struct {
21 | mtx sync.Mutex
22 | lookup map[string]*peerSetItem
23 | list []*Peer
24 | }
25 |
26 | type peerSetItem struct {
27 | peer *Peer
28 | index int
29 | }
30 |
31 | func NewPeerSet() *PeerSet {
32 | return &PeerSet{
33 | lookup: make(map[string]*peerSetItem),
34 | list: make([]*Peer, 0, 256),
35 | }
36 | }
37 |
38 | // Returns false if peer with key (PubKeyEd25519) is already in set
39 | // or if we have too many peers from the peer's IP range
40 | func (ps *PeerSet) Add(peer *Peer) error {
41 | ps.mtx.Lock()
42 | defer ps.mtx.Unlock()
43 | if ps.lookup[peer.Key] != nil {
44 | return ErrSwitchDuplicatePeer
45 | }
46 |
47 | index := len(ps.list)
48 | // Appending is safe even with other goroutines
49 | // iterating over the ps.list slice.
50 | ps.list = append(ps.list, peer)
51 | ps.lookup[peer.Key] = &peerSetItem{peer, index}
52 | return nil
53 | }
54 |
55 | func (ps *PeerSet) Has(peerKey string) bool {
56 | ps.mtx.Lock()
57 | defer ps.mtx.Unlock()
58 | _, ok := ps.lookup[peerKey]
59 | return ok
60 | }
61 |
62 | func (ps *PeerSet) Get(peerKey string) *Peer {
63 | ps.mtx.Lock()
64 | defer ps.mtx.Unlock()
65 | item, ok := ps.lookup[peerKey]
66 | if ok {
67 | return item.peer
68 | } else {
69 | return nil
70 | }
71 | }
72 |
73 | func (ps *PeerSet) Remove(peer *Peer) {
74 | ps.mtx.Lock()
75 | defer ps.mtx.Unlock()
76 | item := ps.lookup[peer.Key]
77 | if item == nil {
78 | return
79 | }
80 |
81 | index := item.index
82 | // Copy the list but without the last element.
83 | // (we must copy because we're mutating the list)
84 | newList := make([]*Peer, len(ps.list)-1)
85 | copy(newList, ps.list)
86 | // If it's the last peer, that's an easy special case.
87 | if index == len(ps.list)-1 {
88 | ps.list = newList
89 | delete(ps.lookup, peer.Key)
90 | return
91 | }
92 |
93 | // Move the last item from ps.list to "index" in list.
94 | lastPeer := ps.list[len(ps.list)-1]
95 | lastPeerKey := lastPeer.Key
96 | lastPeerItem := ps.lookup[lastPeerKey]
97 | newList[index] = lastPeer
98 | lastPeerItem.index = index
99 | ps.list = newList
100 | delete(ps.lookup, peer.Key)
101 |
102 | }
103 |
104 | func (ps *PeerSet) Size() int {
105 | ps.mtx.Lock()
106 | defer ps.mtx.Unlock()
107 | return len(ps.list)
108 | }
109 |
110 | // threadsafe list of peers.
111 | func (ps *PeerSet) List() []*Peer {
112 | ps.mtx.Lock()
113 | defer ps.mtx.Unlock()
114 | return ps.list
115 | }
116 |
--------------------------------------------------------------------------------
/peer_set_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "math/rand"
5 | "testing"
6 |
7 | . "github.com/tendermint/go-common"
8 | )
9 |
10 | // Returns an empty dummy peer
11 | func randPeer() *Peer {
12 | return &Peer{
13 | Key: RandStr(12),
14 | NodeInfo: &NodeInfo{
15 | RemoteAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256),
16 | ListenAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256),
17 | },
18 | }
19 | }
20 |
21 | func TestAddRemoveOne(t *testing.T) {
22 | peerSet := NewPeerSet()
23 |
24 | peer := randPeer()
25 | err := peerSet.Add(peer)
26 | if err != nil {
27 | t.Errorf("Failed to add new peer")
28 | }
29 | if peerSet.Size() != 1 {
30 | t.Errorf("Failed to add new peer and increment size")
31 | }
32 |
33 | peerSet.Remove(peer)
34 | if peerSet.Has(peer.Key) {
35 | t.Errorf("Failed to remove peer")
36 | }
37 | if peerSet.Size() != 0 {
38 | t.Errorf("Failed to remove peer and decrement size")
39 | }
40 | }
41 |
42 | func TestAddRemoveMany(t *testing.T) {
43 | peerSet := NewPeerSet()
44 |
45 | peers := []*Peer{}
46 | N := 100
47 | for i := 0; i < N; i++ {
48 | peer := randPeer()
49 | if err := peerSet.Add(peer); err != nil {
50 | t.Errorf("Failed to add new peer")
51 | }
52 | if peerSet.Size() != i+1 {
53 | t.Errorf("Failed to add new peer and increment size")
54 | }
55 | peers = append(peers, peer)
56 | }
57 |
58 | for i, peer := range peers {
59 | peerSet.Remove(peer)
60 | if peerSet.Has(peer.Key) {
61 | t.Errorf("Failed to remove peer")
62 | }
63 | if peerSet.Size() != len(peers)-i-1 {
64 | t.Errorf("Failed to remove peer and decrement size")
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/peer_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | golog "log"
5 | "net"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 |
12 | crypto "github.com/tendermint/go-crypto"
13 | )
14 |
15 | func TestPeerBasic(t *testing.T) {
16 | assert, require := assert.New(t), require.New(t)
17 |
18 | // simulate remote peer
19 | rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()}
20 | rp.Start()
21 | defer rp.Stop()
22 |
23 | p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), DefaultPeerConfig())
24 | require.Nil(err)
25 |
26 | p.Start()
27 | defer p.Stop()
28 |
29 | assert.True(p.IsRunning())
30 | assert.True(p.IsOutbound())
31 | assert.False(p.IsPersistent())
32 | p.makePersistent()
33 | assert.True(p.IsPersistent())
34 | assert.Equal(rp.Addr().String(), p.Addr().String())
35 | assert.Equal(rp.PubKey(), p.PubKey())
36 | }
37 |
38 | func TestPeerWithoutAuthEnc(t *testing.T) {
39 | assert, require := assert.New(t), require.New(t)
40 |
41 | config := DefaultPeerConfig()
42 | config.AuthEnc = false
43 |
44 | // simulate remote peer
45 | rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
46 | rp.Start()
47 | defer rp.Stop()
48 |
49 | p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config)
50 | require.Nil(err)
51 |
52 | p.Start()
53 | defer p.Stop()
54 |
55 | assert.True(p.IsRunning())
56 | }
57 |
58 | func TestPeerSend(t *testing.T) {
59 | assert, require := assert.New(t), require.New(t)
60 |
61 | config := DefaultPeerConfig()
62 | config.AuthEnc = false
63 |
64 | // simulate remote peer
65 | rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
66 | rp.Start()
67 | defer rp.Stop()
68 |
69 | p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config)
70 | require.Nil(err)
71 |
72 | p.Start()
73 | defer p.Stop()
74 |
75 | assert.True(p.CanSend(0x01))
76 | assert.True(p.Send(0x01, "Asylum"))
77 | }
78 |
79 | func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*Peer, error) {
80 | chDescs := []*ChannelDescriptor{
81 | &ChannelDescriptor{ID: 0x01, Priority: 1},
82 | }
83 | reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)}
84 | pk := crypto.GenPrivKeyEd25519()
85 | p, err := newOutboundPeerWithConfig(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk, config)
86 | if err != nil {
87 | return nil, err
88 | }
89 | err = p.HandshakeTimeout(&NodeInfo{
90 | PubKey: pk.PubKey().(crypto.PubKeyEd25519),
91 | Moniker: "host_peer",
92 | Network: "testing",
93 | Version: "123.123.123",
94 | }, 1*time.Second)
95 | if err != nil {
96 | return nil, err
97 | }
98 | return p, nil
99 | }
100 |
101 | type remotePeer struct {
102 | PrivKey crypto.PrivKeyEd25519
103 | Config *PeerConfig
104 | addr *NetAddress
105 | quit chan struct{}
106 | }
107 |
108 | func (p *remotePeer) Addr() *NetAddress {
109 | return p.addr
110 | }
111 |
112 | func (p *remotePeer) PubKey() crypto.PubKeyEd25519 {
113 | return p.PrivKey.PubKey().(crypto.PubKeyEd25519)
114 | }
115 |
116 | func (p *remotePeer) Start() {
117 | l, e := net.Listen("tcp", "127.0.0.1:0") // any available address
118 | if e != nil {
119 | golog.Fatalf("net.Listen tcp :0: %+v", e)
120 | }
121 | p.addr = NewNetAddress(l.Addr())
122 | p.quit = make(chan struct{})
123 | go p.accept(l)
124 | }
125 |
126 | func (p *remotePeer) Stop() {
127 | close(p.quit)
128 | }
129 |
130 | func (p *remotePeer) accept(l net.Listener) {
131 | for {
132 | conn, err := l.Accept()
133 | if err != nil {
134 | golog.Fatalf("Failed to accept conn: %+v", err)
135 | }
136 | peer, err := newInboundPeerWithConfig(conn, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey, p.Config)
137 | if err != nil {
138 | golog.Fatalf("Failed to create a peer: %+v", err)
139 | }
140 | err = peer.HandshakeTimeout(&NodeInfo{
141 | PubKey: p.PrivKey.PubKey().(crypto.PubKeyEd25519),
142 | Moniker: "remote_peer",
143 | Network: "testing",
144 | Version: "123.123.123",
145 | }, 1*time.Second)
146 | if err != nil {
147 | golog.Fatalf("Failed to perform handshake: %+v", err)
148 | }
149 | select {
150 | case <-p.quit:
151 | conn.Close()
152 | return
153 | default:
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/pex_reactor.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "math/rand"
7 | "reflect"
8 | "time"
9 |
10 | cmn "github.com/tendermint/go-common"
11 | wire "github.com/tendermint/go-wire"
12 | )
13 |
14 | const (
15 | // PexChannel is a channel for PEX messages
16 | PexChannel = byte(0x00)
17 |
18 | // period to ensure peers connected
19 | defaultEnsurePeersPeriod = 30 * time.Second
20 | minNumOutboundPeers = 10
21 | maxPexMessageSize = 1048576 // 1MB
22 |
23 | // maximum messages one peer can send to us during `msgCountByPeerFlushInterval`
24 | defaultMaxMsgCountByPeer = 1000
25 | msgCountByPeerFlushInterval = 1 * time.Hour
26 | )
27 |
28 | // PEXReactor handles PEX (peer exchange) and ensures that an
29 | // adequate number of peers are connected to the switch.
30 | //
31 | // It uses `AddrBook` (address book) to store `NetAddress`es of the peers.
32 | //
33 | // ## Preventing abuse
34 | //
35 | // For now, it just limits the number of messages from one peer to
36 | // `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000
37 | // msg/hour).
38 | //
39 | // NOTE [2017-01-17]:
40 | // Limiting is fine for now. Maybe down the road we want to keep track of the
41 | // quality of peer messages so if peerA keeps telling us about peers we can't
42 | // connect to then maybe we should care less about peerA. But I don't think
43 | // that kind of complexity is priority right now.
44 | type PEXReactor struct {
45 | BaseReactor
46 |
47 | sw *Switch
48 | book *AddrBook
49 | ensurePeersPeriod time.Duration
50 |
51 | // tracks message count by peer, so we can prevent abuse
52 | msgCountByPeer *cmn.CMap
53 | maxMsgCountByPeer uint16
54 | }
55 |
56 | // NewPEXReactor creates new PEX reactor.
57 | func NewPEXReactor(b *AddrBook) *PEXReactor {
58 | r := &PEXReactor{
59 | book: b,
60 | ensurePeersPeriod: defaultEnsurePeersPeriod,
61 | msgCountByPeer: cmn.NewCMap(),
62 | maxMsgCountByPeer: defaultMaxMsgCountByPeer,
63 | }
64 | r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r)
65 | return r
66 | }
67 |
68 | // OnStart implements BaseService
69 | func (r *PEXReactor) OnStart() error {
70 | r.BaseReactor.OnStart()
71 | r.book.Start()
72 | go r.ensurePeersRoutine()
73 | go r.flushMsgCountByPeer()
74 | return nil
75 | }
76 |
77 | // OnStop implements BaseService
78 | func (r *PEXReactor) OnStop() {
79 | r.BaseReactor.OnStop()
80 | r.book.Stop()
81 | }
82 |
83 | // GetChannels implements Reactor
84 | func (r *PEXReactor) GetChannels() []*ChannelDescriptor {
85 | return []*ChannelDescriptor{
86 | &ChannelDescriptor{
87 | ID: PexChannel,
88 | Priority: 1,
89 | SendQueueCapacity: 10,
90 | },
91 | }
92 | }
93 |
94 | // AddPeer implements Reactor by adding peer to the address book (if inbound)
95 | // or by requesting more addresses (if outbound).
96 | func (r *PEXReactor) AddPeer(p *Peer) {
97 | if p.IsOutbound() {
98 | // For outbound peers, the address is already in the books.
99 | // Either it was added in DialSeeds or when we
100 | // received the peer's address in r.Receive
101 | if r.book.NeedMoreAddrs() {
102 | r.RequestPEX(p)
103 | }
104 | } else { // For inbound connections, the peer is its own source
105 | addr, err := NewNetAddressString(p.ListenAddr)
106 | if err != nil {
107 | // this should never happen
108 | log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err)
109 | return
110 | }
111 | r.book.AddAddress(addr, addr)
112 | }
113 | }
114 |
115 | // RemovePeer implements Reactor.
116 | func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) {
117 | // If we aren't keeping track of local temp data for each peer here, then we
118 | // don't have to do anything.
119 | }
120 |
121 | // Receive implements Reactor by handling incoming PEX messages.
122 | func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) {
123 | srcAddr := src.Connection().RemoteAddress
124 | srcAddrStr := srcAddr.String()
125 |
126 | r.IncrementMsgCountForPeer(srcAddrStr)
127 | if r.ReachedMaxMsgCountForPeer(srcAddrStr) {
128 | log.Warn("Maximum number of messages reached for peer", "peer", srcAddrStr)
129 | // TODO remove src from peers?
130 | return
131 | }
132 |
133 | _, msg, err := DecodeMessage(msgBytes)
134 | if err != nil {
135 | log.Warn("Error decoding message", "error", err)
136 | return
137 | }
138 | log.Notice("Received message", "msg", msg)
139 |
140 | switch msg := msg.(type) {
141 | case *pexRequestMessage:
142 | // src requested some peers.
143 | r.SendAddrs(src, r.book.GetSelection())
144 | case *pexAddrsMessage:
145 | // We received some peer addresses from src.
146 | // (We don't want to get spammed with bad peers)
147 | for _, addr := range msg.Addrs {
148 | if addr != nil {
149 | r.book.AddAddress(addr, srcAddr)
150 | }
151 | }
152 | default:
153 | log.Warn(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
154 | }
155 | }
156 |
157 | // RequestPEX asks peer for more addresses.
158 | func (r *PEXReactor) RequestPEX(p *Peer) {
159 | p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}})
160 | }
161 |
162 | // SendAddrs sends addrs to the peer.
163 | func (r *PEXReactor) SendAddrs(p *Peer, addrs []*NetAddress) {
164 | p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
165 | }
166 |
167 | // SetEnsurePeersPeriod sets period to ensure peers connected.
168 | func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) {
169 | r.ensurePeersPeriod = d
170 | }
171 |
172 | // SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'.
173 | func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) {
174 | r.maxMsgCountByPeer = v
175 | }
176 |
177 | // ReachedMaxMsgCountForPeer returns true if we received too many
178 | // messages from peer with address `addr`.
179 | // NOTE: assumes the value in the CMap is non-nil
180 | func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool {
181 | return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer
182 | }
183 |
184 | // Increment or initialize the msg count for the peer in the CMap
185 | func (r *PEXReactor) IncrementMsgCountForPeer(addr string) {
186 | var count uint16
187 | countI := r.msgCountByPeer.Get(addr)
188 | if countI != nil {
189 | count = countI.(uint16)
190 | }
191 | count++
192 | r.msgCountByPeer.Set(addr, count)
193 | }
194 |
195 | // Ensures that sufficient peers are connected. (continuous)
196 | func (r *PEXReactor) ensurePeersRoutine() {
197 | // Randomize when routine starts
198 | ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6
199 | time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond)
200 |
201 | // fire once immediately.
202 | r.ensurePeers()
203 |
204 | // fire periodically
205 | ticker := time.NewTicker(r.ensurePeersPeriod)
206 |
207 | for {
208 | select {
209 | case <-ticker.C:
210 | r.ensurePeers()
211 | case <-r.Quit:
212 | ticker.Stop()
213 | return
214 | }
215 | }
216 | }
217 |
218 | // ensurePeers ensures that sufficient peers are connected. (once)
219 | //
220 | // Old bucket / New bucket are arbitrary categories to denote whether an
221 | // address is vetted or not, and this needs to be determined over time via a
222 | // heuristic that we haven't perfected yet, or, perhaps is manually edited by
223 | // the node operator. It should not be used to compute what addresses are
224 | // already connected or not.
225 | //
226 | // TODO Basically, we need to work harder on our good-peer/bad-peer marking.
227 | // What we're currently doing in terms of marking good/bad peers is just a
228 | // placeholder. It should not be the case that an address becomes old/vetted
229 | // upon a single successful connection.
230 | func (r *PEXReactor) ensurePeers() {
231 | numOutPeers, _, numDialing := r.Switch.NumPeers()
232 | numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
233 | log.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
234 | if numToDial <= 0 {
235 | return
236 | }
237 |
238 | toDial := make(map[string]*NetAddress)
239 |
240 | // Try to pick numToDial addresses to dial.
241 | for i := 0; i < numToDial; i++ {
242 | // The purpose of newBias is to first prioritize old (more vetted) peers
243 | // when we have few connections, but to allow for new (less vetted) peers
244 | // if we already have many connections. This algorithm isn't perfect, but
245 | // it somewhat ensures that we prioritize connecting to more-vetted
246 | // peers.
247 | newBias := cmn.MinInt(numOutPeers, 8)*10 + 10
248 | var picked *NetAddress
249 | // Try to fetch a new peer 3 times.
250 | // This caps the maximum number of tries to 3 * numToDial.
251 | for j := 0; j < 3; j++ {
252 | try := r.book.PickAddress(newBias)
253 | if try == nil {
254 | break
255 | }
256 | _, alreadySelected := toDial[try.IP.String()]
257 | alreadyDialing := r.Switch.IsDialing(try)
258 | alreadyConnected := r.Switch.Peers().Has(try.IP.String())
259 | if alreadySelected || alreadyDialing || alreadyConnected {
260 | // log.Info("Cannot dial address", "addr", try,
261 | // "alreadySelected", alreadySelected,
262 | // "alreadyDialing", alreadyDialing,
263 | // "alreadyConnected", alreadyConnected)
264 | continue
265 | } else {
266 | log.Info("Will dial address", "addr", try)
267 | picked = try
268 | break
269 | }
270 | }
271 | if picked == nil {
272 | continue
273 | }
274 | toDial[picked.IP.String()] = picked
275 | }
276 |
277 | // Dial picked addresses
278 | for _, item := range toDial {
279 | go func(picked *NetAddress) {
280 | _, err := r.Switch.DialPeerWithAddress(picked, false)
281 | if err != nil {
282 | r.book.MarkAttempt(picked)
283 | }
284 | }(item)
285 | }
286 |
287 | // If we need more addresses, pick a random peer and ask for more.
288 | if r.book.NeedMoreAddrs() {
289 | if peers := r.Switch.Peers().List(); len(peers) > 0 {
290 | i := rand.Int() % len(peers)
291 | peer := peers[i]
292 | log.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
293 | r.RequestPEX(peer)
294 | }
295 | }
296 | }
297 |
298 | func (r *PEXReactor) flushMsgCountByPeer() {
299 | ticker := time.NewTicker(msgCountByPeerFlushInterval)
300 |
301 | for {
302 | select {
303 | case <-ticker.C:
304 | r.msgCountByPeer.Clear()
305 | case <-r.Quit:
306 | ticker.Stop()
307 | return
308 | }
309 | }
310 | }
311 |
312 | //-----------------------------------------------------------------------------
313 | // Messages
314 |
315 | const (
316 | msgTypeRequest = byte(0x01)
317 | msgTypeAddrs = byte(0x02)
318 | )
319 |
320 | // PexMessage is a primary type for PEX messages. Underneath, it could contain
321 | // either pexRequestMessage, or pexAddrsMessage messages.
322 | type PexMessage interface{}
323 |
324 | var _ = wire.RegisterInterface(
325 | struct{ PexMessage }{},
326 | wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
327 | wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
328 | )
329 |
330 | // DecodeMessage implements interface registered above.
331 | func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
332 | msgType = bz[0]
333 | n := new(int)
334 | r := bytes.NewReader(bz)
335 | msg = wire.ReadBinary(struct{ PexMessage }{}, r, maxPexMessageSize, n, &err).(struct{ PexMessage }).PexMessage
336 | return
337 | }
338 |
339 | /*
340 | A pexRequestMessage requests additional peer addresses.
341 | */
342 | type pexRequestMessage struct {
343 | }
344 |
345 | func (m *pexRequestMessage) String() string {
346 | return "[pexRequest]"
347 | }
348 |
349 | /*
350 | A message with announced peer addresses.
351 | */
352 | type pexAddrsMessage struct {
353 | Addrs []*NetAddress
354 | }
355 |
356 | func (m *pexAddrsMessage) String() string {
357 | return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
358 | }
359 |
--------------------------------------------------------------------------------
/pex_reactor_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "io/ioutil"
5 | "math/rand"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | cmn "github.com/tendermint/go-common"
13 | wire "github.com/tendermint/go-wire"
14 | )
15 |
16 | func TestPEXReactorBasic(t *testing.T) {
17 | assert, require := assert.New(t), require.New(t)
18 |
19 | dir, err := ioutil.TempDir("", "pex_reactor")
20 | require.Nil(err)
21 | defer os.RemoveAll(dir)
22 | book := NewAddrBook(dir+"addrbook.json", true)
23 |
24 | r := NewPEXReactor(book)
25 |
26 | assert.NotNil(r)
27 | assert.NotEmpty(r.GetChannels())
28 | }
29 |
30 | func TestPEXReactorAddRemovePeer(t *testing.T) {
31 | assert, require := assert.New(t), require.New(t)
32 |
33 | dir, err := ioutil.TempDir("", "pex_reactor")
34 | require.Nil(err)
35 | defer os.RemoveAll(dir)
36 | book := NewAddrBook(dir+"addrbook.json", true)
37 |
38 | r := NewPEXReactor(book)
39 |
40 | size := book.Size()
41 | peer := createRandomPeer(false)
42 |
43 | r.AddPeer(peer)
44 | assert.Equal(size+1, book.Size())
45 |
46 | r.RemovePeer(peer, "peer not available")
47 | assert.Equal(size+1, book.Size())
48 |
49 | outboundPeer := createRandomPeer(true)
50 |
51 | r.AddPeer(outboundPeer)
52 | assert.Equal(size+1, book.Size(), "outbound peers should not be added to the address book")
53 |
54 | r.RemovePeer(outboundPeer, "peer not available")
55 | assert.Equal(size+1, book.Size())
56 | }
57 |
58 | func TestPEXReactorRunning(t *testing.T) {
59 | require := require.New(t)
60 |
61 | N := 3
62 | switches := make([]*Switch, N)
63 |
64 | dir, err := ioutil.TempDir("", "pex_reactor")
65 | require.Nil(err)
66 | defer os.RemoveAll(dir)
67 | book := NewAddrBook(dir+"addrbook.json", false)
68 |
69 | // create switches
70 | for i := 0; i < N; i++ {
71 | switches[i] = makeSwitch(i, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch {
72 | r := NewPEXReactor(book)
73 | r.SetEnsurePeersPeriod(250 * time.Millisecond)
74 | sw.AddReactor("pex", r)
75 | return sw
76 | })
77 | }
78 |
79 | // fill the address book and add listeners
80 | for _, s := range switches {
81 | addr, _ := NewNetAddressString(s.NodeInfo().ListenAddr)
82 | book.AddAddress(addr, addr)
83 | s.AddListener(NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true))
84 | }
85 |
86 | // start switches
87 | for _, s := range switches {
88 | _, err := s.Start() // start switch and reactors
89 | require.Nil(err)
90 | }
91 |
92 | time.Sleep(1 * time.Second)
93 |
94 | // check peers are connected after some time
95 | for _, s := range switches {
96 | outbound, inbound, _ := s.NumPeers()
97 | if outbound+inbound == 0 {
98 | t.Errorf("%v expected to be connected to at least one peer", s.NodeInfo().ListenAddr)
99 | }
100 | }
101 |
102 | // stop them
103 | for _, s := range switches {
104 | s.Stop()
105 | }
106 | }
107 |
108 | func TestPEXReactorReceive(t *testing.T) {
109 | assert, require := assert.New(t), require.New(t)
110 |
111 | dir, err := ioutil.TempDir("", "pex_reactor")
112 | require.Nil(err)
113 | defer os.RemoveAll(dir)
114 | book := NewAddrBook(dir+"addrbook.json", true)
115 |
116 | r := NewPEXReactor(book)
117 |
118 | peer := createRandomPeer(false)
119 |
120 | size := book.Size()
121 | netAddr, _ := NewNetAddressString(peer.ListenAddr)
122 | addrs := []*NetAddress{netAddr}
123 | msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
124 | r.Receive(PexChannel, peer, msg)
125 | assert.Equal(size+1, book.Size())
126 |
127 | msg = wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}})
128 | r.Receive(PexChannel, peer, msg)
129 | }
130 |
131 | func TestPEXReactorAbuseFromPeer(t *testing.T) {
132 | assert, require := assert.New(t), require.New(t)
133 |
134 | dir, err := ioutil.TempDir("", "pex_reactor")
135 | require.Nil(err)
136 | defer os.RemoveAll(dir)
137 | book := NewAddrBook(dir+"addrbook.json", true)
138 |
139 | r := NewPEXReactor(book)
140 | r.SetMaxMsgCountByPeer(5)
141 |
142 | peer := createRandomPeer(false)
143 |
144 | msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}})
145 | for i := 0; i < 10; i++ {
146 | r.Receive(PexChannel, peer, msg)
147 | }
148 |
149 | assert.True(r.ReachedMaxMsgCountForPeer(peer.ListenAddr))
150 | }
151 |
152 | func createRandomPeer(outbound bool) *Peer {
153 | addr := cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256)
154 | netAddr, _ := NewNetAddressString(addr)
155 | return &Peer{
156 | Key: cmn.RandStr(12),
157 | NodeInfo: &NodeInfo{
158 | ListenAddr: addr,
159 | },
160 | outbound: outbound,
161 | mconn: &MConnection{RemoteAddress: netAddr},
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/secret_connection.go:
--------------------------------------------------------------------------------
1 | // Uses nacl's secret_box to encrypt a net.Conn.
2 | // It is (meant to be) an implementation of the STS protocol.
3 | // Note we do not (yet) assume that a remote peer's pubkey
4 | // is known ahead of time, and thus we are technically
5 | // still vulnerable to MITM. (TODO!)
6 | // See docs/sts-final.pdf for more info
7 | package p2p
8 |
9 | import (
10 | "bytes"
11 | crand "crypto/rand"
12 | "crypto/sha256"
13 | "encoding/binary"
14 | "errors"
15 | "io"
16 | "net"
17 | "time"
18 |
19 | "golang.org/x/crypto/nacl/box"
20 | "golang.org/x/crypto/nacl/secretbox"
21 | "golang.org/x/crypto/ripemd160"
22 |
23 | . "github.com/tendermint/go-common"
24 | "github.com/tendermint/go-crypto"
25 | "github.com/tendermint/go-wire"
26 | )
27 |
28 | // 2 + 1024 == 1026 total frame size
29 | const dataLenSize = 2 // uint16 to describe the length, is <= dataMaxSize
30 | const dataMaxSize = 1024
31 | const totalFrameSize = dataMaxSize + dataLenSize
32 | const sealedFrameSize = totalFrameSize + secretbox.Overhead
33 | const authSigMsgSize = (32 + 1) + (64 + 1) // fixed size (length prefixed) byte arrays
34 |
35 | // Implements net.Conn
36 | type SecretConnection struct {
37 | conn io.ReadWriteCloser
38 | recvBuffer []byte
39 | recvNonce *[24]byte
40 | sendNonce *[24]byte
41 | remPubKey crypto.PubKeyEd25519
42 | shrSecret *[32]byte // shared secret
43 | }
44 |
45 | // Performs handshake and returns a new authenticated SecretConnection.
46 | // Returns nil if error in handshake.
47 | // Caller should call conn.Close()
48 | // See docs/sts-final.pdf for more information.
49 | func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25519) (*SecretConnection, error) {
50 |
51 | locPubKey := locPrivKey.PubKey().(crypto.PubKeyEd25519)
52 |
53 | // Generate ephemeral keys for perfect forward secrecy.
54 | locEphPub, locEphPriv := genEphKeys()
55 |
56 | // Write local ephemeral pubkey and receive one too.
57 | // NOTE: every 32-byte string is accepted as a Curve25519 public key
58 | // (see DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
59 | remEphPub, err := shareEphPubKey(conn, locEphPub)
60 | if err != nil {
61 | return nil, err
62 | }
63 |
64 | // Compute common shared secret.
65 | shrSecret := computeSharedSecret(remEphPub, locEphPriv)
66 |
67 | // Sort by lexical order.
68 | loEphPub, hiEphPub := sort32(locEphPub, remEphPub)
69 |
70 | // Generate nonces to use for secretbox.
71 | recvNonce, sendNonce := genNonces(loEphPub, hiEphPub, locEphPub == loEphPub)
72 |
73 | // Generate common challenge to sign.
74 | challenge := genChallenge(loEphPub, hiEphPub)
75 |
76 | // Construct SecretConnection.
77 | sc := &SecretConnection{
78 | conn: conn,
79 | recvBuffer: nil,
80 | recvNonce: recvNonce,
81 | sendNonce: sendNonce,
82 | shrSecret: shrSecret,
83 | }
84 |
85 | // Sign the challenge bytes for authentication.
86 | locSignature := signChallenge(challenge, locPrivKey)
87 |
88 | // Share (in secret) each other's pubkey & challenge signature
89 | authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
90 | if err != nil {
91 | return nil, err
92 | }
93 | remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig
94 | if !remPubKey.VerifyBytes(challenge[:], remSignature) {
95 | return nil, errors.New("Challenge verification failed")
96 | }
97 |
98 | // We've authorized.
99 | sc.remPubKey = remPubKey.(crypto.PubKeyEd25519)
100 | return sc, nil
101 | }
102 |
103 | // Returns authenticated remote pubkey
104 | func (sc *SecretConnection) RemotePubKey() crypto.PubKeyEd25519 {
105 | return sc.remPubKey
106 | }
107 |
108 | // Writes encrypted frames of `sealedFrameSize`
109 | // CONTRACT: data smaller than dataMaxSize is read atomically.
110 | func (sc *SecretConnection) Write(data []byte) (n int, err error) {
111 | for 0 < len(data) {
112 | var frame []byte = make([]byte, totalFrameSize)
113 | var chunk []byte
114 | if dataMaxSize < len(data) {
115 | chunk = data[:dataMaxSize]
116 | data = data[dataMaxSize:]
117 | } else {
118 | chunk = data
119 | data = nil
120 | }
121 | chunkLength := len(chunk)
122 | binary.BigEndian.PutUint16(frame, uint16(chunkLength))
123 | copy(frame[dataLenSize:], chunk)
124 |
125 | // encrypt the frame
126 | var sealedFrame = make([]byte, sealedFrameSize)
127 | secretbox.Seal(sealedFrame[:0], frame, sc.sendNonce, sc.shrSecret)
128 | // fmt.Printf("secretbox.Seal(sealed:%X,sendNonce:%X,shrSecret:%X\n", sealedFrame, sc.sendNonce, sc.shrSecret)
129 | incr2Nonce(sc.sendNonce)
130 | // end encryption
131 |
132 | _, err := sc.conn.Write(sealedFrame)
133 | if err != nil {
134 | return n, err
135 | } else {
136 | n += len(chunk)
137 | }
138 | }
139 | return
140 | }
141 |
142 | // CONTRACT: data smaller than dataMaxSize is read atomically.
143 | func (sc *SecretConnection) Read(data []byte) (n int, err error) {
144 | if 0 < len(sc.recvBuffer) {
145 | n_ := copy(data, sc.recvBuffer)
146 | sc.recvBuffer = sc.recvBuffer[n_:]
147 | return
148 | }
149 |
150 | sealedFrame := make([]byte, sealedFrameSize)
151 | _, err = io.ReadFull(sc.conn, sealedFrame)
152 | if err != nil {
153 | return
154 | }
155 |
156 | // decrypt the frame
157 | var frame = make([]byte, totalFrameSize)
158 | // fmt.Printf("secretbox.Open(sealed:%X,recvNonce:%X,shrSecret:%X\n", sealedFrame, sc.recvNonce, sc.shrSecret)
159 | _, ok := secretbox.Open(frame[:0], sealedFrame, sc.recvNonce, sc.shrSecret)
160 | if !ok {
161 | return n, errors.New("Failed to decrypt SecretConnection")
162 | }
163 | incr2Nonce(sc.recvNonce)
164 | // end decryption
165 |
166 | var chunkLength = binary.BigEndian.Uint16(frame) // read the first two bytes
167 | if chunkLength > dataMaxSize {
168 | return 0, errors.New("chunkLength is greater than dataMaxSize")
169 | }
170 | var chunk = frame[dataLenSize : dataLenSize+chunkLength]
171 |
172 | n = copy(data, chunk)
173 | sc.recvBuffer = chunk[n:]
174 | return
175 | }
176 |
177 | // Implements net.Conn
178 | func (sc *SecretConnection) Close() error { return sc.conn.Close() }
179 | func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() }
180 | func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() }
181 | func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
182 | func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
183 | return sc.conn.(net.Conn).SetReadDeadline(t)
184 | }
185 | func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
186 | return sc.conn.(net.Conn).SetWriteDeadline(t)
187 | }
188 |
189 | func genEphKeys() (ephPub, ephPriv *[32]byte) {
190 | var err error
191 | ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
192 | if err != nil {
193 | PanicCrisis("Could not generate ephemeral keypairs")
194 | }
195 | return
196 | }
197 |
198 | func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
199 | var err1, err2 error
200 |
201 | Parallel(
202 | func() {
203 | _, err1 = conn.Write(locEphPub[:])
204 | },
205 | func() {
206 | remEphPub = new([32]byte)
207 | _, err2 = io.ReadFull(conn, remEphPub[:])
208 | },
209 | )
210 |
211 | if err1 != nil {
212 | return nil, err1
213 | }
214 | if err2 != nil {
215 | return nil, err2
216 | }
217 |
218 | return remEphPub, nil
219 | }
220 |
221 | func computeSharedSecret(remPubKey, locPrivKey *[32]byte) (shrSecret *[32]byte) {
222 | shrSecret = new([32]byte)
223 | box.Precompute(shrSecret, remPubKey, locPrivKey)
224 | return
225 | }
226 |
227 | func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
228 | if bytes.Compare(foo[:], bar[:]) < 0 {
229 | lo = foo
230 | hi = bar
231 | } else {
232 | lo = bar
233 | hi = foo
234 | }
235 | return
236 | }
237 |
238 | func genNonces(loPubKey, hiPubKey *[32]byte, locIsLo bool) (recvNonce, sendNonce *[24]byte) {
239 | nonce1 := hash24(append(loPubKey[:], hiPubKey[:]...))
240 | nonce2 := new([24]byte)
241 | copy(nonce2[:], nonce1[:])
242 | nonce2[len(nonce2)-1] ^= 0x01
243 | if locIsLo {
244 | recvNonce = nonce1
245 | sendNonce = nonce2
246 | } else {
247 | recvNonce = nonce2
248 | sendNonce = nonce1
249 | }
250 | return
251 | }
252 |
253 | func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) {
254 | return hash32(append(loPubKey[:], hiPubKey[:]...))
255 | }
256 |
257 | func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKeyEd25519) (signature crypto.SignatureEd25519) {
258 | signature = locPrivKey.Sign(challenge[:]).(crypto.SignatureEd25519)
259 | return
260 | }
261 |
262 | type authSigMessage struct {
263 | Key crypto.PubKey
264 | Sig crypto.Signature
265 | }
266 |
267 | func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKeyEd25519, signature crypto.SignatureEd25519) (*authSigMessage, error) {
268 | var recvMsg authSigMessage
269 | var err1, err2 error
270 |
271 | Parallel(
272 | func() {
273 | msgBytes := wire.BinaryBytes(authSigMessage{pubKey, signature})
274 | _, err1 = sc.Write(msgBytes)
275 | },
276 | func() {
277 | readBuffer := make([]byte, authSigMsgSize)
278 | _, err2 = io.ReadFull(sc, readBuffer)
279 | if err2 != nil {
280 | return
281 | }
282 | n := int(0) // not used.
283 | recvMsg = wire.ReadBinary(authSigMessage{}, bytes.NewBuffer(readBuffer), authSigMsgSize, &n, &err2).(authSigMessage)
284 | })
285 |
286 | if err1 != nil {
287 | return nil, err1
288 | }
289 | if err2 != nil {
290 | return nil, err2
291 | }
292 |
293 | return &recvMsg, nil
294 | }
295 |
296 | func verifyChallengeSignature(challenge *[32]byte, remPubKey crypto.PubKeyEd25519, remSignature crypto.SignatureEd25519) bool {
297 | return remPubKey.VerifyBytes(challenge[:], remSignature)
298 | }
299 |
300 | //--------------------------------------------------------------------------------
301 |
302 | // sha256
303 | func hash32(input []byte) (res *[32]byte) {
304 | hasher := sha256.New()
305 | hasher.Write(input) // does not error
306 | resSlice := hasher.Sum(nil)
307 | res = new([32]byte)
308 | copy(res[:], resSlice)
309 | return
310 | }
311 |
312 | // We only fill in the first 20 bytes with ripemd160
313 | func hash24(input []byte) (res *[24]byte) {
314 | hasher := ripemd160.New()
315 | hasher.Write(input) // does not error
316 | resSlice := hasher.Sum(nil)
317 | res = new([24]byte)
318 | copy(res[:], resSlice)
319 | return
320 | }
321 |
322 | // ripemd160
323 | func hash20(input []byte) (res *[20]byte) {
324 | hasher := ripemd160.New()
325 | hasher.Write(input) // does not error
326 | resSlice := hasher.Sum(nil)
327 | res = new([20]byte)
328 | copy(res[:], resSlice)
329 | return
330 | }
331 |
332 | // increment nonce big-endian by 2 with wraparound.
333 | func incr2Nonce(nonce *[24]byte) {
334 | incrNonce(nonce)
335 | incrNonce(nonce)
336 | }
337 |
338 | // increment nonce big-endian by 1 with wraparound.
339 | func incrNonce(nonce *[24]byte) {
340 | for i := 23; 0 <= i; i-- {
341 | nonce[i] += 1
342 | if nonce[i] != 0 {
343 | return
344 | }
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/secret_connection_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "testing"
7 |
8 | . "github.com/tendermint/go-common"
9 | "github.com/tendermint/go-crypto"
10 | )
11 |
12 | type dummyConn struct {
13 | *io.PipeReader
14 | *io.PipeWriter
15 | }
16 |
17 | func (drw dummyConn) Close() (err error) {
18 | err2 := drw.PipeWriter.CloseWithError(io.EOF)
19 | err1 := drw.PipeReader.Close()
20 | if err2 != nil {
21 | return err
22 | }
23 | return err1
24 | }
25 |
26 | // Each returned ReadWriteCloser is akin to a net.Connection
27 | func makeDummyConnPair() (fooConn, barConn dummyConn) {
28 | barReader, fooWriter := io.Pipe()
29 | fooReader, barWriter := io.Pipe()
30 | return dummyConn{fooReader, fooWriter}, dummyConn{barReader, barWriter}
31 | }
32 |
33 | func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
34 | fooConn, barConn := makeDummyConnPair()
35 | fooPrvKey := crypto.GenPrivKeyEd25519()
36 | fooPubKey := fooPrvKey.PubKey().(crypto.PubKeyEd25519)
37 | barPrvKey := crypto.GenPrivKeyEd25519()
38 | barPubKey := barPrvKey.PubKey().(crypto.PubKeyEd25519)
39 |
40 | Parallel(
41 | func() {
42 | var err error
43 | fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
44 | if err != nil {
45 | tb.Errorf("Failed to establish SecretConnection for foo: %v", err)
46 | return
47 | }
48 | remotePubBytes := fooSecConn.RemotePubKey()
49 | if !bytes.Equal(remotePubBytes[:], barPubKey[:]) {
50 | tb.Errorf("Unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
51 | barPubKey, fooSecConn.RemotePubKey())
52 | }
53 | },
54 | func() {
55 | var err error
56 | barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
57 | if barSecConn == nil {
58 | tb.Errorf("Failed to establish SecretConnection for bar: %v", err)
59 | return
60 | }
61 | remotePubBytes := barSecConn.RemotePubKey()
62 | if !bytes.Equal(remotePubBytes[:], fooPubKey[:]) {
63 | tb.Errorf("Unexpected barSecConn.RemotePubKey. Expected %v, got %v",
64 | fooPubKey, barSecConn.RemotePubKey())
65 | }
66 | })
67 |
68 | return
69 | }
70 |
71 | func TestSecretConnectionHandshake(t *testing.T) {
72 | fooSecConn, barSecConn := makeSecretConnPair(t)
73 | fooSecConn.Close()
74 | barSecConn.Close()
75 | }
76 |
77 | func TestSecretConnectionReadWrite(t *testing.T) {
78 | fooConn, barConn := makeDummyConnPair()
79 | fooWrites, barWrites := []string{}, []string{}
80 | fooReads, barReads := []string{}, []string{}
81 |
82 | // Pre-generate the things to write (for foo & bar)
83 | for i := 0; i < 100; i++ {
84 | fooWrites = append(fooWrites, RandStr((RandInt()%(dataMaxSize*5))+1))
85 | barWrites = append(barWrites, RandStr((RandInt()%(dataMaxSize*5))+1))
86 | }
87 |
88 | // A helper that will run with (fooConn, fooWrites, fooReads) and vice versa
89 | genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() {
90 | return func() {
91 | // Node handskae
92 | nodePrvKey := crypto.GenPrivKeyEd25519()
93 | nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey)
94 | if err != nil {
95 | t.Errorf("Failed to establish SecretConnection for node: %v", err)
96 | return
97 | }
98 | // In parallel, handle reads and writes
99 | Parallel(
100 | func() {
101 | // Node writes
102 | for _, nodeWrite := range nodeWrites {
103 | n, err := nodeSecretConn.Write([]byte(nodeWrite))
104 | if err != nil {
105 | t.Errorf("Failed to write to nodeSecretConn: %v", err)
106 | return
107 | }
108 | if n != len(nodeWrite) {
109 | t.Errorf("Failed to write all bytes. Expected %v, wrote %v", len(nodeWrite), n)
110 | return
111 | }
112 | }
113 | nodeConn.PipeWriter.Close()
114 | },
115 | func() {
116 | // Node reads
117 | readBuffer := make([]byte, dataMaxSize)
118 | for {
119 | n, err := nodeSecretConn.Read(readBuffer)
120 | if err == io.EOF {
121 | return
122 | } else if err != nil {
123 | t.Errorf("Failed to read from nodeSecretConn: %v", err)
124 | return
125 | }
126 | *nodeReads = append(*nodeReads, string(readBuffer[:n]))
127 | }
128 | nodeConn.PipeReader.Close()
129 | })
130 | }
131 | }
132 |
133 | // Run foo & bar in parallel
134 | Parallel(
135 | genNodeRunner(fooConn, fooWrites, &fooReads),
136 | genNodeRunner(barConn, barWrites, &barReads),
137 | )
138 |
139 | // A helper to ensure that the writes and reads match.
140 | // Additionally, small writes (<= dataMaxSize) must be atomically read.
141 | compareWritesReads := func(writes []string, reads []string) {
142 | for {
143 | // Pop next write & corresponding reads
144 | var read, write string = "", writes[0]
145 | var readCount = 0
146 | for _, readChunk := range reads {
147 | read += readChunk
148 | readCount += 1
149 | if len(write) <= len(read) {
150 | break
151 | }
152 | if len(write) <= dataMaxSize {
153 | break // atomicity of small writes
154 | }
155 | }
156 | // Compare
157 | if write != read {
158 | t.Errorf("Expected to read %X, got %X", write, read)
159 | }
160 | // Iterate
161 | writes = writes[1:]
162 | reads = reads[readCount:]
163 | if len(writes) == 0 {
164 | break
165 | }
166 | }
167 | }
168 |
169 | compareWritesReads(fooWrites, barReads)
170 | compareWritesReads(barWrites, fooReads)
171 |
172 | }
173 |
174 | func BenchmarkSecretConnection(b *testing.B) {
175 | b.StopTimer()
176 | fooSecConn, barSecConn := makeSecretConnPair(b)
177 | fooWriteText := RandStr(dataMaxSize)
178 | // Consume reads from bar's reader
179 | go func() {
180 | readBuffer := make([]byte, dataMaxSize)
181 | for {
182 | _, err := barSecConn.Read(readBuffer)
183 | if err == io.EOF {
184 | return
185 | } else if err != nil {
186 | b.Fatalf("Failed to read from barSecConn: %v", err)
187 | }
188 | }
189 | }()
190 |
191 | b.StartTimer()
192 | for i := 0; i < b.N; i++ {
193 | _, err := fooSecConn.Write([]byte(fooWriteText))
194 | if err != nil {
195 | b.Fatalf("Failed to write to fooSecConn: %v", err)
196 | }
197 | }
198 | b.StopTimer()
199 |
200 | fooSecConn.Close()
201 | //barSecConn.Close() race condition
202 | }
203 |
--------------------------------------------------------------------------------
/switch.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math/rand"
7 | "net"
8 | "time"
9 |
10 | . "github.com/tendermint/go-common"
11 | cfg "github.com/tendermint/go-config"
12 | crypto "github.com/tendermint/go-crypto"
13 | "github.com/tendermint/log15"
14 | )
15 |
16 | const (
17 | reconnectAttempts = 30
18 | reconnectInterval = 3 * time.Second
19 | )
20 |
21 | type Reactor interface {
22 | Service // Start, Stop
23 |
24 | SetSwitch(*Switch)
25 | GetChannels() []*ChannelDescriptor
26 | AddPeer(peer *Peer)
27 | RemovePeer(peer *Peer, reason interface{})
28 | Receive(chID byte, peer *Peer, msgBytes []byte)
29 | }
30 |
31 | //--------------------------------------
32 |
33 | type BaseReactor struct {
34 | BaseService // Provides Start, Stop, .Quit
35 | Switch *Switch
36 | }
37 |
38 | func NewBaseReactor(log log15.Logger, name string, impl Reactor) *BaseReactor {
39 | return &BaseReactor{
40 | BaseService: *NewBaseService(log, name, impl),
41 | Switch: nil,
42 | }
43 | }
44 |
45 | func (br *BaseReactor) SetSwitch(sw *Switch) {
46 | br.Switch = sw
47 | }
48 | func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil }
49 | func (_ *BaseReactor) AddPeer(peer *Peer) {}
50 | func (_ *BaseReactor) RemovePeer(peer *Peer, reason interface{}) {}
51 | func (_ *BaseReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {}
52 |
53 | //-----------------------------------------------------------------------------
54 |
55 | /*
56 | The `Switch` handles peer connections and exposes an API to receive incoming messages
57 | on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one
58 | or more `Channels`. So while sending outgoing messages is typically performed on the peer,
59 | incoming messages are received on the reactor.
60 | */
61 | type Switch struct {
62 | BaseService
63 |
64 | config cfg.Config
65 | listeners []Listener
66 | reactors map[string]Reactor
67 | chDescs []*ChannelDescriptor
68 | reactorsByCh map[byte]Reactor
69 | peers *PeerSet
70 | dialing *CMap
71 | nodeInfo *NodeInfo // our node info
72 | nodePrivKey crypto.PrivKeyEd25519 // our node privkey
73 |
74 | filterConnByAddr func(net.Addr) error
75 | filterConnByPubKey func(crypto.PubKeyEd25519) error
76 | }
77 |
78 | var (
79 | ErrSwitchDuplicatePeer = errors.New("Duplicate peer")
80 | ErrSwitchMaxPeersPerIPRange = errors.New("IP range has too many peers")
81 | )
82 |
83 | func NewSwitch(config cfg.Config) *Switch {
84 | setConfigDefaults(config)
85 |
86 | sw := &Switch{
87 | config: config,
88 | reactors: make(map[string]Reactor),
89 | chDescs: make([]*ChannelDescriptor, 0),
90 | reactorsByCh: make(map[byte]Reactor),
91 | peers: NewPeerSet(),
92 | dialing: NewCMap(),
93 | nodeInfo: nil,
94 | }
95 | sw.BaseService = *NewBaseService(log, "P2P Switch", sw)
96 | return sw
97 | }
98 |
99 | // Not goroutine safe.
100 | func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor {
101 | // Validate the reactor.
102 | // No two reactors can share the same channel.
103 | reactorChannels := reactor.GetChannels()
104 | for _, chDesc := range reactorChannels {
105 | chID := chDesc.ID
106 | if sw.reactorsByCh[chID] != nil {
107 | PanicSanity(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor))
108 | }
109 | sw.chDescs = append(sw.chDescs, chDesc)
110 | sw.reactorsByCh[chID] = reactor
111 | }
112 | sw.reactors[name] = reactor
113 | reactor.SetSwitch(sw)
114 | return reactor
115 | }
116 |
117 | // Not goroutine safe.
118 | func (sw *Switch) Reactors() map[string]Reactor {
119 | return sw.reactors
120 | }
121 |
122 | // Not goroutine safe.
123 | func (sw *Switch) Reactor(name string) Reactor {
124 | return sw.reactors[name]
125 | }
126 |
127 | // Not goroutine safe.
128 | func (sw *Switch) AddListener(l Listener) {
129 | sw.listeners = append(sw.listeners, l)
130 | }
131 |
132 | // Not goroutine safe.
133 | func (sw *Switch) Listeners() []Listener {
134 | return sw.listeners
135 | }
136 |
137 | // Not goroutine safe.
138 | func (sw *Switch) IsListening() bool {
139 | return len(sw.listeners) > 0
140 | }
141 |
142 | // Not goroutine safe.
143 | func (sw *Switch) SetNodeInfo(nodeInfo *NodeInfo) {
144 | sw.nodeInfo = nodeInfo
145 | }
146 |
147 | // Not goroutine safe.
148 | func (sw *Switch) NodeInfo() *NodeInfo {
149 | return sw.nodeInfo
150 | }
151 |
152 | // Not goroutine safe.
153 | // NOTE: Overwrites sw.nodeInfo.PubKey
154 | func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKeyEd25519) {
155 | sw.nodePrivKey = nodePrivKey
156 | if sw.nodeInfo != nil {
157 | sw.nodeInfo.PubKey = nodePrivKey.PubKey().(crypto.PubKeyEd25519)
158 | }
159 | }
160 |
161 | // Switch.Start() starts all the reactors, peers, and listeners.
162 | func (sw *Switch) OnStart() error {
163 | sw.BaseService.OnStart()
164 | // Start reactors
165 | for _, reactor := range sw.reactors {
166 | _, err := reactor.Start()
167 | if err != nil {
168 | return err
169 | }
170 | }
171 | // Start peers
172 | for _, peer := range sw.peers.List() {
173 | sw.startInitPeer(peer)
174 | }
175 | // Start listeners
176 | for _, listener := range sw.listeners {
177 | go sw.listenerRoutine(listener)
178 | }
179 | return nil
180 | }
181 |
182 | func (sw *Switch) OnStop() {
183 | sw.BaseService.OnStop()
184 | // Stop listeners
185 | for _, listener := range sw.listeners {
186 | listener.Stop()
187 | }
188 | sw.listeners = nil
189 | // Stop peers
190 | for _, peer := range sw.peers.List() {
191 | peer.Stop()
192 | sw.peers.Remove(peer)
193 | }
194 | // Stop reactors
195 | for _, reactor := range sw.reactors {
196 | reactor.Stop()
197 | }
198 | }
199 |
200 | // NOTE: This performs a blocking handshake before the peer is added.
201 | // CONTRACT: If error is returned, peer is nil, and conn is immediately closed.
202 | func (sw *Switch) AddPeer(peer *Peer) error {
203 | if err := sw.FilterConnByAddr(peer.Addr()); err != nil {
204 | return err
205 | }
206 |
207 | if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil {
208 | return err
209 | }
210 |
211 | if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.config.GetInt(configKeyHandshakeTimeoutSeconds))*time.Second); err != nil {
212 | return err
213 | }
214 |
215 | // Avoid self
216 | if sw.nodeInfo.PubKey.Equals(peer.PubKey()) {
217 | return errors.New("Ignoring connection from self")
218 | }
219 |
220 | // Check version, chain id
221 | if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo); err != nil {
222 | return err
223 | }
224 |
225 | // Add the peer to .peers
226 | // ignore if duplicate or if we already have too many for that IP range
227 | if err := sw.peers.Add(peer); err != nil {
228 | log.Notice("Ignoring peer", "error", err, "peer", peer)
229 | peer.Stop()
230 | return err
231 | }
232 |
233 | // Start peer
234 | if sw.IsRunning() {
235 | sw.startInitPeer(peer)
236 | }
237 |
238 | log.Notice("Added peer", "peer", peer)
239 | return nil
240 | }
241 |
242 | func (sw *Switch) FilterConnByAddr(addr net.Addr) error {
243 | if sw.filterConnByAddr != nil {
244 | return sw.filterConnByAddr(addr)
245 | }
246 | return nil
247 | }
248 |
249 | func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKeyEd25519) error {
250 | if sw.filterConnByPubKey != nil {
251 | return sw.filterConnByPubKey(pubkey)
252 | }
253 | return nil
254 |
255 | }
256 |
257 | func (sw *Switch) SetAddrFilter(f func(net.Addr) error) {
258 | sw.filterConnByAddr = f
259 | }
260 |
261 | func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKeyEd25519) error) {
262 | sw.filterConnByPubKey = f
263 | }
264 |
265 | func (sw *Switch) startInitPeer(peer *Peer) {
266 | peer.Start() // spawn send/recv routines
267 | for _, reactor := range sw.reactors {
268 | reactor.AddPeer(peer)
269 | }
270 | }
271 |
272 | // Dial a list of seeds asynchronously in random order
273 | func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error {
274 |
275 | netAddrs, err := NewNetAddressStrings(seeds)
276 | if err != nil {
277 | return err
278 | }
279 |
280 | if addrBook != nil {
281 | // add seeds to `addrBook`
282 | ourAddrS := sw.nodeInfo.ListenAddr
283 | ourAddr, _ := NewNetAddressString(ourAddrS)
284 | for _, netAddr := range netAddrs {
285 | // do not add ourselves
286 | if netAddr.Equals(ourAddr) {
287 | continue
288 | }
289 | addrBook.AddAddress(netAddr, ourAddr)
290 | }
291 | addrBook.Save()
292 | }
293 |
294 | // permute the list, dial them in random order.
295 | perm := rand.Perm(len(netAddrs))
296 | for i := 0; i < len(perm); i++ {
297 | go func(i int) {
298 | time.Sleep(time.Duration(rand.Int63n(3000)) * time.Millisecond)
299 | j := perm[i]
300 | sw.dialSeed(netAddrs[j])
301 | }(i)
302 | }
303 | return nil
304 | }
305 |
306 | func (sw *Switch) dialSeed(addr *NetAddress) {
307 | peer, err := sw.DialPeerWithAddress(addr, true)
308 | if err != nil {
309 | log.Error("Error dialing seed", "error", err)
310 | return
311 | } else {
312 | log.Notice("Connected to seed", "peer", peer)
313 | }
314 | }
315 |
316 | func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, error) {
317 | sw.dialing.Set(addr.IP.String(), addr)
318 | defer sw.dialing.Delete(addr.IP.String())
319 |
320 | peer, err := newOutboundPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, peerConfigFromGoConfig(sw.config))
321 | if err != nil {
322 | log.Info("Failed dialing peer", "address", addr, "error", err)
323 | return nil, err
324 | }
325 | if persistent {
326 | peer.makePersistent()
327 | }
328 | err = sw.AddPeer(peer)
329 | if err != nil {
330 | log.Info("Failed adding peer", "address", addr, "error", err)
331 | peer.CloseConn()
332 | return nil, err
333 | }
334 | log.Notice("Dialed and added peer", "address", addr, "peer", peer)
335 | return peer, nil
336 | }
337 |
338 | func (sw *Switch) IsDialing(addr *NetAddress) bool {
339 | return sw.dialing.Has(addr.IP.String())
340 | }
341 |
342 | // Broadcast runs a go routine for each attempted send, which will block
343 | // trying to send for defaultSendTimeoutSeconds. Returns a channel
344 | // which receives success values for each attempted send (false if times out)
345 | // NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved.
346 | func (sw *Switch) Broadcast(chID byte, msg interface{}) chan bool {
347 | successChan := make(chan bool, len(sw.peers.List()))
348 | log.Debug("Broadcast", "channel", chID, "msg", msg)
349 | for _, peer := range sw.peers.List() {
350 | go func(peer *Peer) {
351 | success := peer.Send(chID, msg)
352 | successChan <- success
353 | }(peer)
354 | }
355 | return successChan
356 | }
357 |
358 | // Returns the count of outbound/inbound and outbound-dialing peers.
359 | func (sw *Switch) NumPeers() (outbound, inbound, dialing int) {
360 | peers := sw.peers.List()
361 | for _, peer := range peers {
362 | if peer.outbound {
363 | outbound++
364 | } else {
365 | inbound++
366 | }
367 | }
368 | dialing = sw.dialing.Size()
369 | return
370 | }
371 |
372 | func (sw *Switch) Peers() IPeerSet {
373 | return sw.peers
374 | }
375 |
376 | // Disconnect from a peer due to external error, retry if it is a persistent peer.
377 | // TODO: make record depending on reason.
378 | func (sw *Switch) StopPeerForError(peer *Peer, reason interface{}) {
379 | addr := NewNetAddress(peer.Addr())
380 | log.Notice("Stopping peer for error", "peer", peer, "error", reason)
381 | sw.stopAndRemovePeer(peer, reason)
382 |
383 | if peer.IsPersistent() {
384 | go func() {
385 | log.Notice("Reconnecting to peer", "peer", peer)
386 | for i := 1; i < reconnectAttempts; i++ {
387 | if !sw.IsRunning() {
388 | return
389 | }
390 |
391 | peer, err := sw.DialPeerWithAddress(addr, true)
392 | if err != nil {
393 | if i == reconnectAttempts {
394 | log.Notice("Error reconnecting to peer. Giving up", "tries", i, "error", err)
395 | return
396 | }
397 | log.Notice("Error reconnecting to peer. Trying again", "tries", i, "error", err)
398 | time.Sleep(reconnectInterval)
399 | continue
400 | }
401 |
402 | log.Notice("Reconnected to peer", "peer", peer)
403 | return
404 | }
405 | }()
406 | }
407 | }
408 |
409 | // Disconnect from a peer gracefully.
410 | // TODO: handle graceful disconnects.
411 | func (sw *Switch) StopPeerGracefully(peer *Peer) {
412 | log.Notice("Stopping peer gracefully")
413 | sw.stopAndRemovePeer(peer, nil)
414 | }
415 |
416 | func (sw *Switch) stopAndRemovePeer(peer *Peer, reason interface{}) {
417 | sw.peers.Remove(peer)
418 | peer.Stop()
419 | for _, reactor := range sw.reactors {
420 | reactor.RemovePeer(peer, reason)
421 | }
422 | }
423 |
424 | func (sw *Switch) listenerRoutine(l Listener) {
425 | for {
426 | inConn, ok := <-l.Connections()
427 | if !ok {
428 | break
429 | }
430 |
431 | // ignore connection if we already have enough
432 | maxPeers := sw.config.GetInt(configKeyMaxNumPeers)
433 | if maxPeers <= sw.peers.Size() {
434 | log.Info("Ignoring inbound connection: already have enough peers", "address", inConn.RemoteAddr().String(), "numPeers", sw.peers.Size(), "max", maxPeers)
435 | continue
436 | }
437 |
438 | // New inbound connection!
439 | err := sw.addPeerWithConnectionAndConfig(inConn, peerConfigFromGoConfig(sw.config))
440 | if err != nil {
441 | log.Notice("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "error", err)
442 | continue
443 | }
444 |
445 | // NOTE: We don't yet have the listening port of the
446 | // remote (if they have a listener at all).
447 | // The peerHandshake will handle that
448 | }
449 |
450 | // cleanup
451 | }
452 |
453 | //-----------------------------------------------------------------------------
454 |
455 | type SwitchEventNewPeer struct {
456 | Peer *Peer
457 | }
458 |
459 | type SwitchEventDonePeer struct {
460 | Peer *Peer
461 | Error interface{}
462 | }
463 |
464 | //------------------------------------------------------------------
465 | // Switches connected via arbitrary net.Conn; useful for testing
466 |
467 | // Returns n switches, connected according to the connect func.
468 | // If connect==Connect2Switches, the switches will be fully connected.
469 | // initSwitch defines how the ith switch should be initialized (ie. with what reactors).
470 | // NOTE: panics if any switch fails to start.
471 | func MakeConnectedSwitches(n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch {
472 | switches := make([]*Switch, n)
473 | for i := 0; i < n; i++ {
474 | switches[i] = makeSwitch(i, "testing", "123.123.123", initSwitch)
475 | }
476 |
477 | if err := StartSwitches(switches); err != nil {
478 | panic(err)
479 | }
480 |
481 | for i := 0; i < n; i++ {
482 | for j := i; j < n; j++ {
483 | connect(switches, i, j)
484 | }
485 | }
486 |
487 | return switches
488 | }
489 |
490 | var PanicOnAddPeerErr = false
491 |
492 | // Will connect switches i and j via net.Pipe()
493 | // Blocks until a conection is established.
494 | // NOTE: caller ensures i and j are within bounds
495 | func Connect2Switches(switches []*Switch, i, j int) {
496 | switchI := switches[i]
497 | switchJ := switches[j]
498 | c1, c2 := net.Pipe()
499 | doneCh := make(chan struct{})
500 | go func() {
501 | err := switchI.addPeerWithConnection(c1)
502 | if PanicOnAddPeerErr && err != nil {
503 | panic(err)
504 | }
505 | doneCh <- struct{}{}
506 | }()
507 | go func() {
508 | err := switchJ.addPeerWithConnection(c2)
509 | if PanicOnAddPeerErr && err != nil {
510 | panic(err)
511 | }
512 | doneCh <- struct{}{}
513 | }()
514 | <-doneCh
515 | <-doneCh
516 | }
517 |
518 | func StartSwitches(switches []*Switch) error {
519 | for _, s := range switches {
520 | _, err := s.Start() // start switch and reactors
521 | if err != nil {
522 | return err
523 | }
524 | }
525 | return nil
526 | }
527 |
528 | func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
529 | privKey := crypto.GenPrivKeyEd25519()
530 | // new switch, add reactors
531 | // TODO: let the config be passed in?
532 | s := initSwitch(i, NewSwitch(cfg.NewMapConfig(nil)))
533 | s.SetNodeInfo(&NodeInfo{
534 | PubKey: privKey.PubKey().(crypto.PubKeyEd25519),
535 | Moniker: Fmt("switch%d", i),
536 | Network: network,
537 | Version: version,
538 | RemoteAddr: Fmt("%v:%v", network, rand.Intn(64512)+1023),
539 | ListenAddr: Fmt("%v:%v", network, rand.Intn(64512)+1023),
540 | })
541 | s.SetNodePrivKey(privKey)
542 | return s
543 | }
544 |
545 | func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
546 | peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey)
547 | if err != nil {
548 | conn.Close()
549 | return err
550 | }
551 |
552 | if err = sw.AddPeer(peer); err != nil {
553 | conn.Close()
554 | return err
555 | }
556 |
557 | return nil
558 | }
559 |
560 | func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error {
561 | peer, err := newInboundPeerWithConfig(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config)
562 | if err != nil {
563 | conn.Close()
564 | return err
565 | }
566 |
567 | if err = sw.AddPeer(peer); err != nil {
568 | conn.Close()
569 | return err
570 | }
571 |
572 | return nil
573 | }
574 |
575 | func peerConfigFromGoConfig(config cfg.Config) *PeerConfig {
576 | return &PeerConfig{
577 | AuthEnc: config.GetBool(configKeyAuthEnc),
578 | Fuzz: config.GetBool(configFuzzEnable),
579 | HandshakeTimeout: time.Duration(config.GetInt(configKeyHandshakeTimeoutSeconds)) * time.Second,
580 | DialTimeout: time.Duration(config.GetInt(configKeyDialTimeoutSeconds)) * time.Second,
581 | MConfig: &MConnConfig{
582 | SendRate: int64(config.GetInt(configKeySendRate)),
583 | RecvRate: int64(config.GetInt(configKeyRecvRate)),
584 | },
585 | FuzzConfig: &FuzzConnConfig{
586 | Mode: config.GetInt(configFuzzMode),
587 | MaxDelay: time.Duration(config.GetInt(configFuzzMaxDelayMilliseconds)) * time.Millisecond,
588 | ProbDropRW: config.GetFloat64(configFuzzProbDropRW),
589 | ProbDropConn: config.GetFloat64(configFuzzProbDropConn),
590 | ProbSleep: config.GetFloat64(configFuzzProbSleep),
591 | },
592 | }
593 | }
594 |
--------------------------------------------------------------------------------
/switch_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net"
7 | "sync"
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | . "github.com/tendermint/go-common"
14 | cfg "github.com/tendermint/go-config"
15 | crypto "github.com/tendermint/go-crypto"
16 | wire "github.com/tendermint/go-wire"
17 | )
18 |
19 | var (
20 | config cfg.Config
21 | )
22 |
23 | func init() {
24 | config = cfg.NewMapConfig(nil)
25 | setConfigDefaults(config)
26 | }
27 |
28 | type PeerMessage struct {
29 | PeerKey string
30 | Bytes []byte
31 | Counter int
32 | }
33 |
34 | type TestReactor struct {
35 | BaseReactor
36 |
37 | mtx sync.Mutex
38 | channels []*ChannelDescriptor
39 | peersAdded []*Peer
40 | peersRemoved []*Peer
41 | logMessages bool
42 | msgsCounter int
43 | msgsReceived map[byte][]PeerMessage
44 | }
45 |
46 | func NewTestReactor(channels []*ChannelDescriptor, logMessages bool) *TestReactor {
47 | tr := &TestReactor{
48 | channels: channels,
49 | logMessages: logMessages,
50 | msgsReceived: make(map[byte][]PeerMessage),
51 | }
52 | tr.BaseReactor = *NewBaseReactor(log, "TestReactor", tr)
53 | return tr
54 | }
55 |
56 | func (tr *TestReactor) GetChannels() []*ChannelDescriptor {
57 | return tr.channels
58 | }
59 |
60 | func (tr *TestReactor) AddPeer(peer *Peer) {
61 | tr.mtx.Lock()
62 | defer tr.mtx.Unlock()
63 | tr.peersAdded = append(tr.peersAdded, peer)
64 | }
65 |
66 | func (tr *TestReactor) RemovePeer(peer *Peer, reason interface{}) {
67 | tr.mtx.Lock()
68 | defer tr.mtx.Unlock()
69 | tr.peersRemoved = append(tr.peersRemoved, peer)
70 | }
71 |
72 | func (tr *TestReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
73 | if tr.logMessages {
74 | tr.mtx.Lock()
75 | defer tr.mtx.Unlock()
76 | //fmt.Printf("Received: %X, %X\n", chID, msgBytes)
77 | tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.Key, msgBytes, tr.msgsCounter})
78 | tr.msgsCounter++
79 | }
80 | }
81 |
82 | func (tr *TestReactor) getMsgs(chID byte) []PeerMessage {
83 | tr.mtx.Lock()
84 | defer tr.mtx.Unlock()
85 | return tr.msgsReceived[chID]
86 | }
87 |
88 | //-----------------------------------------------------------------------------
89 |
90 | // convenience method for creating two switches connected to each other.
91 | // XXX: note this uses net.Pipe and not a proper TCP conn
92 | func makeSwitchPair(t testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) {
93 | // Create two switches that will be interconnected.
94 | switches := MakeConnectedSwitches(2, initSwitch, Connect2Switches)
95 | return switches[0], switches[1]
96 | }
97 |
98 | func initSwitchFunc(i int, sw *Switch) *Switch {
99 | // Make two reactors of two channels each
100 | sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
101 | &ChannelDescriptor{ID: byte(0x00), Priority: 10},
102 | &ChannelDescriptor{ID: byte(0x01), Priority: 10},
103 | }, true))
104 | sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
105 | &ChannelDescriptor{ID: byte(0x02), Priority: 10},
106 | &ChannelDescriptor{ID: byte(0x03), Priority: 10},
107 | }, true))
108 | return sw
109 | }
110 |
111 | func TestSwitches(t *testing.T) {
112 | s1, s2 := makeSwitchPair(t, initSwitchFunc)
113 | defer s1.Stop()
114 | defer s2.Stop()
115 |
116 | if s1.Peers().Size() != 1 {
117 | t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size())
118 | }
119 | if s2.Peers().Size() != 1 {
120 | t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size())
121 | }
122 |
123 | // Lets send some messages
124 | ch0Msg := "channel zero"
125 | ch1Msg := "channel foo"
126 | ch2Msg := "channel bar"
127 |
128 | s1.Broadcast(byte(0x00), ch0Msg)
129 | s1.Broadcast(byte(0x01), ch1Msg)
130 | s1.Broadcast(byte(0x02), ch2Msg)
131 |
132 | // Wait for things to settle...
133 | time.Sleep(5000 * time.Millisecond)
134 |
135 | // Check message on ch0
136 | ch0Msgs := s2.Reactor("foo").(*TestReactor).getMsgs(byte(0x00))
137 | if len(ch0Msgs) != 1 {
138 | t.Errorf("Expected to have received 1 message in ch0")
139 | }
140 | if !bytes.Equal(ch0Msgs[0].Bytes, wire.BinaryBytes(ch0Msg)) {
141 | t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch0Msg), ch0Msgs[0].Bytes)
142 | }
143 |
144 | // Check message on ch1
145 | ch1Msgs := s2.Reactor("foo").(*TestReactor).getMsgs(byte(0x01))
146 | if len(ch1Msgs) != 1 {
147 | t.Errorf("Expected to have received 1 message in ch1")
148 | }
149 | if !bytes.Equal(ch1Msgs[0].Bytes, wire.BinaryBytes(ch1Msg)) {
150 | t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch1Msg), ch1Msgs[0].Bytes)
151 | }
152 |
153 | // Check message on ch2
154 | ch2Msgs := s2.Reactor("bar").(*TestReactor).getMsgs(byte(0x02))
155 | if len(ch2Msgs) != 1 {
156 | t.Errorf("Expected to have received 1 message in ch2")
157 | }
158 | if !bytes.Equal(ch2Msgs[0].Bytes, wire.BinaryBytes(ch2Msg)) {
159 | t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch2Msg), ch2Msgs[0].Bytes)
160 | }
161 |
162 | }
163 |
164 | func TestConnAddrFilter(t *testing.T) {
165 | s1 := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
166 | s2 := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
167 |
168 | c1, c2 := net.Pipe()
169 |
170 | s1.SetAddrFilter(func(addr net.Addr) error {
171 | if addr.String() == c1.RemoteAddr().String() {
172 | return fmt.Errorf("Error: pipe is blacklisted")
173 | }
174 | return nil
175 | })
176 |
177 | // connect to good peer
178 | go func() {
179 | s1.addPeerWithConnection(c1)
180 | }()
181 | go func() {
182 | s2.addPeerWithConnection(c2)
183 | }()
184 |
185 | // Wait for things to happen, peers to get added...
186 | time.Sleep(100 * time.Millisecond * time.Duration(4))
187 |
188 | defer s1.Stop()
189 | defer s2.Stop()
190 | if s1.Peers().Size() != 0 {
191 | t.Errorf("Expected s1 not to connect to peers, got %d", s1.Peers().Size())
192 | }
193 | if s2.Peers().Size() != 0 {
194 | t.Errorf("Expected s2 not to connect to peers, got %d", s2.Peers().Size())
195 | }
196 | }
197 |
198 | func TestConnPubKeyFilter(t *testing.T) {
199 | s1 := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
200 | s2 := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
201 |
202 | c1, c2 := net.Pipe()
203 |
204 | // set pubkey filter
205 | s1.SetPubKeyFilter(func(pubkey crypto.PubKeyEd25519) error {
206 | if bytes.Equal(pubkey.Bytes(), s2.nodeInfo.PubKey.Bytes()) {
207 | return fmt.Errorf("Error: pipe is blacklisted")
208 | }
209 | return nil
210 | })
211 |
212 | // connect to good peer
213 | go func() {
214 | s1.addPeerWithConnection(c1)
215 | }()
216 | go func() {
217 | s2.addPeerWithConnection(c2)
218 | }()
219 |
220 | // Wait for things to happen, peers to get added...
221 | time.Sleep(100 * time.Millisecond * time.Duration(4))
222 |
223 | defer s1.Stop()
224 | defer s2.Stop()
225 | if s1.Peers().Size() != 0 {
226 | t.Errorf("Expected s1 not to connect to peers, got %d", s1.Peers().Size())
227 | }
228 | if s2.Peers().Size() != 0 {
229 | t.Errorf("Expected s2 not to connect to peers, got %d", s2.Peers().Size())
230 | }
231 | }
232 |
233 | func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
234 | assert, require := assert.New(t), require.New(t)
235 |
236 | sw := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
237 | sw.Start()
238 | defer sw.Stop()
239 |
240 | // simulate remote peer
241 | rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()}
242 | rp.Start()
243 | defer rp.Stop()
244 |
245 | peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey)
246 | require.Nil(err)
247 | err = sw.AddPeer(peer)
248 | require.Nil(err)
249 |
250 | // simulate failure by closing connection
251 | peer.CloseConn()
252 |
253 | time.Sleep(100 * time.Millisecond)
254 |
255 | assert.Zero(sw.Peers().Size())
256 | assert.False(peer.IsRunning())
257 | }
258 |
259 | func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
260 | assert, require := assert.New(t), require.New(t)
261 |
262 | sw := makeSwitch(1, "testing", "123.123.123", initSwitchFunc)
263 | sw.Start()
264 | defer sw.Stop()
265 |
266 | // simulate remote peer
267 | rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()}
268 | rp.Start()
269 | defer rp.Stop()
270 |
271 | peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey)
272 | peer.makePersistent()
273 | require.Nil(err)
274 | err = sw.AddPeer(peer)
275 | require.Nil(err)
276 |
277 | // simulate failure by closing connection
278 | peer.CloseConn()
279 |
280 | time.Sleep(100 * time.Millisecond)
281 |
282 | assert.NotZero(sw.Peers().Size())
283 | assert.False(peer.IsRunning())
284 | }
285 |
286 | func BenchmarkSwitches(b *testing.B) {
287 |
288 | b.StopTimer()
289 |
290 | s1, s2 := makeSwitchPair(b, func(i int, sw *Switch) *Switch {
291 | // Make bar reactors of bar channels each
292 | sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
293 | &ChannelDescriptor{ID: byte(0x00), Priority: 10},
294 | &ChannelDescriptor{ID: byte(0x01), Priority: 10},
295 | }, false))
296 | sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
297 | &ChannelDescriptor{ID: byte(0x02), Priority: 10},
298 | &ChannelDescriptor{ID: byte(0x03), Priority: 10},
299 | }, false))
300 | return sw
301 | })
302 | defer s1.Stop()
303 | defer s2.Stop()
304 |
305 | // Allow time for goroutines to boot up
306 | time.Sleep(1000 * time.Millisecond)
307 | b.StartTimer()
308 |
309 | numSuccess, numFailure := 0, 0
310 |
311 | // Send random message from foo channel to another
312 | for i := 0; i < b.N; i++ {
313 | chID := byte(i % 4)
314 | successChan := s1.Broadcast(chID, "test data")
315 | for s := range successChan {
316 | if s {
317 | numSuccess++
318 | } else {
319 | numFailure++
320 | }
321 | }
322 | }
323 |
324 | log.Warn(Fmt("success: %v, failure: %v", numSuccess, numFailure))
325 |
326 | // Allow everything to flush before stopping switches & closing connections.
327 | b.StopTimer()
328 | time.Sleep(1000 * time.Millisecond)
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/types.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/tendermint/go-crypto"
10 | )
11 |
12 | const maxNodeInfoSize = 10240 // 10Kb
13 |
14 | type NodeInfo struct {
15 | PubKey crypto.PubKeyEd25519 `json:"pub_key"`
16 | Moniker string `json:"moniker"`
17 | Network string `json:"network"`
18 | RemoteAddr string `json:"remote_addr"`
19 | ListenAddr string `json:"listen_addr"`
20 | Version string `json:"version"` // major.minor.revision
21 | Other []string `json:"other"` // other application specific data
22 | }
23 |
24 | // CONTRACT: two nodes are compatible if the major/minor versions match and network match
25 | func (info *NodeInfo) CompatibleWith(other *NodeInfo) error {
26 | iMajor, iMinor, _, iErr := splitVersion(info.Version)
27 | oMajor, oMinor, _, oErr := splitVersion(other.Version)
28 |
29 | // if our own version number is not formatted right, we messed up
30 | if iErr != nil {
31 | return iErr
32 | }
33 |
34 | // version number must be formatted correctly ("x.x.x")
35 | if oErr != nil {
36 | return oErr
37 | }
38 |
39 | // major version must match
40 | if iMajor != oMajor {
41 | return fmt.Errorf("Peer is on a different major version. Got %v, expected %v", oMajor, iMajor)
42 | }
43 |
44 | // minor version must match
45 | if iMinor != oMinor {
46 | return fmt.Errorf("Peer is on a different minor version. Got %v, expected %v", oMinor, iMinor)
47 | }
48 |
49 | // nodes must be on the same network
50 | if info.Network != other.Network {
51 | return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network)
52 | }
53 |
54 | return nil
55 | }
56 |
57 | func (info *NodeInfo) ListenHost() string {
58 | host, _, _ := net.SplitHostPort(info.ListenAddr)
59 | return host
60 | }
61 |
62 | func (info *NodeInfo) ListenPort() int {
63 | _, port, _ := net.SplitHostPort(info.ListenAddr)
64 | port_i, err := strconv.Atoi(port)
65 | if err != nil {
66 | return -1
67 | }
68 | return port_i
69 | }
70 |
71 | func splitVersion(version string) (string, string, string, error) {
72 | spl := strings.Split(version, ".")
73 | if len(spl) != 3 {
74 | return "", "", "", fmt.Errorf("Invalid version format %v", version)
75 | }
76 | return spl[0], spl[1], spl[2], nil
77 | }
78 |
--------------------------------------------------------------------------------
/upnp/README.md:
--------------------------------------------------------------------------------
1 | # `tendermint/p2p/upnp`
2 |
3 | ## Resources
4 |
5 | * http://www.upnp-hacks.org/upnp.html
6 |
--------------------------------------------------------------------------------
/upnp/log.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "github.com/tendermint/go-logger"
5 | )
6 |
7 | var log = logger.New("module", "upnp")
8 |
--------------------------------------------------------------------------------
/upnp/probe.go:
--------------------------------------------------------------------------------
1 | package upnp
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net"
7 | "time"
8 |
9 | . "github.com/tendermint/go-common"
10 | )
11 |
12 | type UPNPCapabilities struct {
13 | PortMapping bool
14 | Hairpin bool
15 | }
16 |
17 | func makeUPNPListener(intPort int, extPort int) (NAT, net.Listener, net.IP, error) {
18 | nat, err := Discover()
19 | if err != nil {
20 | return nil, nil, nil, errors.New(fmt.Sprintf("NAT upnp could not be discovered: %v", err))
21 | }
22 | log.Info(Fmt("ourIP: %v", nat.(*upnpNAT).ourIP))
23 |
24 | ext, err := nat.GetExternalAddress()
25 | if err != nil {
26 | return nat, nil, nil, errors.New(fmt.Sprintf("External address error: %v", err))
27 | }
28 | log.Info(Fmt("External address: %v", ext))
29 |
30 | port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0)
31 | if err != nil {
32 | return nat, nil, ext, errors.New(fmt.Sprintf("Port mapping error: %v", err))
33 | }
34 | log.Info(Fmt("Port mapping mapped: %v", port))
35 |
36 | // also run the listener, open for all remote addresses.
37 | listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort))
38 | if err != nil {
39 | return nat, nil, ext, errors.New(fmt.Sprintf("Error establishing listener: %v", err))
40 | }
41 | return nat, listener, ext, nil
42 | }
43 |
44 | func testHairpin(listener net.Listener, extAddr string) (supportsHairpin bool) {
45 | // Listener
46 | go func() {
47 | inConn, err := listener.Accept()
48 | if err != nil {
49 | log.Notice(Fmt("Listener.Accept() error: %v", err))
50 | return
51 | }
52 | log.Info(Fmt("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr()))
53 | buf := make([]byte, 1024)
54 | n, err := inConn.Read(buf)
55 | if err != nil {
56 | log.Notice(Fmt("Incoming connection read error: %v", err))
57 | return
58 | }
59 | log.Info(Fmt("Incoming connection read %v bytes: %X", n, buf))
60 | if string(buf) == "test data" {
61 | supportsHairpin = true
62 | return
63 | }
64 | }()
65 |
66 | // Establish outgoing
67 | outConn, err := net.Dial("tcp", extAddr)
68 | if err != nil {
69 | log.Notice(Fmt("Outgoing connection dial error: %v", err))
70 | return
71 | }
72 |
73 | n, err := outConn.Write([]byte("test data"))
74 | if err != nil {
75 | log.Notice(Fmt("Outgoing connection write error: %v", err))
76 | return
77 | }
78 | log.Info(Fmt("Outgoing connection wrote %v bytes", n))
79 |
80 | // Wait for data receipt
81 | time.Sleep(1 * time.Second)
82 | return
83 | }
84 |
85 | func Probe() (caps UPNPCapabilities, err error) {
86 | log.Info("Probing for UPnP!")
87 |
88 | intPort, extPort := 8001, 8001
89 |
90 | nat, listener, ext, err := makeUPNPListener(intPort, extPort)
91 | if err != nil {
92 | return
93 | }
94 | caps.PortMapping = true
95 |
96 | // Deferred cleanup
97 | defer func() {
98 | err = nat.DeletePortMapping("tcp", intPort, extPort)
99 | if err != nil {
100 | log.Warn(Fmt("Port mapping delete error: %v", err))
101 | }
102 | listener.Close()
103 | }()
104 |
105 | supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort))
106 | if supportsHairpin {
107 | caps.Hairpin = true
108 | }
109 |
110 | return
111 | }
112 |
--------------------------------------------------------------------------------
/upnp/upnp.go:
--------------------------------------------------------------------------------
1 | /*
2 | Taken from taipei-torrent
3 |
4 | Just enough UPnP to be able to forward ports
5 | */
6 | package upnp
7 |
8 | // BUG(jae): TODO: use syscalls to get actual ourIP. http://pastebin.com/9exZG4rh
9 |
10 | import (
11 | "bytes"
12 | "encoding/xml"
13 | "errors"
14 | "io/ioutil"
15 | "net"
16 | "net/http"
17 | "strconv"
18 | "strings"
19 | "time"
20 | )
21 |
22 | type upnpNAT struct {
23 | serviceURL string
24 | ourIP string
25 | urnDomain string
26 | }
27 |
28 | // protocol is either "udp" or "tcp"
29 | type NAT interface {
30 | GetExternalAddress() (addr net.IP, err error)
31 | AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
32 | DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
33 | }
34 |
35 | func Discover() (nat NAT, err error) {
36 | ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
37 | if err != nil {
38 | return
39 | }
40 | conn, err := net.ListenPacket("udp4", ":0")
41 | if err != nil {
42 | return
43 | }
44 | socket := conn.(*net.UDPConn)
45 | defer socket.Close()
46 |
47 | err = socket.SetDeadline(time.Now().Add(3 * time.Second))
48 | if err != nil {
49 | return
50 | }
51 |
52 | st := "InternetGatewayDevice:1"
53 |
54 | buf := bytes.NewBufferString(
55 | "M-SEARCH * HTTP/1.1\r\n" +
56 | "HOST: 239.255.255.250:1900\r\n" +
57 | "ST: ssdp:all\r\n" +
58 | "MAN: \"ssdp:discover\"\r\n" +
59 | "MX: 2\r\n\r\n")
60 | message := buf.Bytes()
61 | answerBytes := make([]byte, 1024)
62 | for i := 0; i < 3; i++ {
63 | _, err = socket.WriteToUDP(message, ssdp)
64 | if err != nil {
65 | return
66 | }
67 | var n int
68 | n, _, err = socket.ReadFromUDP(answerBytes)
69 | for {
70 | n, _, err = socket.ReadFromUDP(answerBytes)
71 | if err != nil {
72 | break
73 | }
74 | answer := string(answerBytes[0:n])
75 | if strings.Index(answer, st) < 0 {
76 | continue
77 | }
78 | // HTTP header field names are case-insensitive.
79 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
80 | locString := "\r\nlocation:"
81 | answer = strings.ToLower(answer)
82 | locIndex := strings.Index(answer, locString)
83 | if locIndex < 0 {
84 | continue
85 | }
86 | loc := answer[locIndex+len(locString):]
87 | endIndex := strings.Index(loc, "\r\n")
88 | if endIndex < 0 {
89 | continue
90 | }
91 | locURL := strings.TrimSpace(loc[0:endIndex])
92 | var serviceURL, urnDomain string
93 | serviceURL, urnDomain, err = getServiceURL(locURL)
94 | if err != nil {
95 | return
96 | }
97 | var ourIP net.IP
98 | ourIP, err = localIPv4()
99 | if err != nil {
100 | return
101 | }
102 | nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}
103 | return
104 | }
105 | }
106 | err = errors.New("UPnP port discovery failed.")
107 | return
108 | }
109 |
110 | type Envelope struct {
111 | XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
112 | Soap *SoapBody
113 | }
114 | type SoapBody struct {
115 | XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
116 | ExternalIP *ExternalIPAddressResponse
117 | }
118 |
119 | type ExternalIPAddressResponse struct {
120 | XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
121 | IPAddress string `xml:"NewExternalIPAddress"`
122 | }
123 |
124 | type ExternalIPAddress struct {
125 | XMLName xml.Name `xml:"NewExternalIPAddress"`
126 | IP string
127 | }
128 |
129 | type UPNPService struct {
130 | ServiceType string `xml:"serviceType"`
131 | ControlURL string `xml:"controlURL"`
132 | }
133 |
134 | type DeviceList struct {
135 | Device []Device `xml:"device"`
136 | }
137 |
138 | type ServiceList struct {
139 | Service []UPNPService `xml:"service"`
140 | }
141 |
142 | type Device struct {
143 | XMLName xml.Name `xml:"device"`
144 | DeviceType string `xml:"deviceType"`
145 | DeviceList DeviceList `xml:"deviceList"`
146 | ServiceList ServiceList `xml:"serviceList"`
147 | }
148 |
149 | type Root struct {
150 | Device Device
151 | }
152 |
153 | func getChildDevice(d *Device, deviceType string) *Device {
154 | dl := d.DeviceList.Device
155 | for i := 0; i < len(dl); i++ {
156 | if strings.Index(dl[i].DeviceType, deviceType) >= 0 {
157 | return &dl[i]
158 | }
159 | }
160 | return nil
161 | }
162 |
163 | func getChildService(d *Device, serviceType string) *UPNPService {
164 | sl := d.ServiceList.Service
165 | for i := 0; i < len(sl); i++ {
166 | if strings.Index(sl[i].ServiceType, serviceType) >= 0 {
167 | return &sl[i]
168 | }
169 | }
170 | return nil
171 | }
172 |
173 | func localIPv4() (net.IP, error) {
174 | tt, err := net.Interfaces()
175 | if err != nil {
176 | return nil, err
177 | }
178 | for _, t := range tt {
179 | aa, err := t.Addrs()
180 | if err != nil {
181 | return nil, err
182 | }
183 | for _, a := range aa {
184 | ipnet, ok := a.(*net.IPNet)
185 | if !ok {
186 | continue
187 | }
188 | v4 := ipnet.IP.To4()
189 | if v4 == nil || v4[0] == 127 { // loopback address
190 | continue
191 | }
192 | return v4, nil
193 | }
194 | }
195 | return nil, errors.New("cannot find local IP address")
196 | }
197 |
198 | func getServiceURL(rootURL string) (url, urnDomain string, err error) {
199 | r, err := http.Get(rootURL)
200 | if err != nil {
201 | return
202 | }
203 | defer r.Body.Close()
204 | if r.StatusCode >= 400 {
205 | err = errors.New(string(r.StatusCode))
206 | return
207 | }
208 | var root Root
209 | err = xml.NewDecoder(r.Body).Decode(&root)
210 | if err != nil {
211 | return
212 | }
213 | a := &root.Device
214 | if strings.Index(a.DeviceType, "InternetGatewayDevice:1") < 0 {
215 | err = errors.New("No InternetGatewayDevice")
216 | return
217 | }
218 | b := getChildDevice(a, "WANDevice:1")
219 | if b == nil {
220 | err = errors.New("No WANDevice")
221 | return
222 | }
223 | c := getChildDevice(b, "WANConnectionDevice:1")
224 | if c == nil {
225 | err = errors.New("No WANConnectionDevice")
226 | return
227 | }
228 | d := getChildService(c, "WANIPConnection:1")
229 | if d == nil {
230 | // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
231 | // instead of under WanConnectionDevice
232 | d = getChildService(b, "WANIPConnection:1")
233 |
234 | if d == nil {
235 | err = errors.New("No WANIPConnection")
236 | return
237 | }
238 | }
239 | // Extract the domain name, which isn't always 'schemas-upnp-org'
240 | urnDomain = strings.Split(d.ServiceType, ":")[1]
241 | url = combineURL(rootURL, d.ControlURL)
242 | return
243 | }
244 |
245 | func combineURL(rootURL, subURL string) string {
246 | protocolEnd := "://"
247 | protoEndIndex := strings.Index(rootURL, protocolEnd)
248 | a := rootURL[protoEndIndex+len(protocolEnd):]
249 | rootIndex := strings.Index(a, "/")
250 | return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
251 | }
252 |
253 | func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
254 | fullMessage := "" +
255 | "\r\n" +
256 | "" + message + ""
257 |
258 | req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
259 | if err != nil {
260 | return nil, err
261 | }
262 | req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
263 | req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
264 | //req.Header.Set("Transfer-Encoding", "chunked")
265 | req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
266 | req.Header.Set("Connection", "Close")
267 | req.Header.Set("Cache-Control", "no-cache")
268 | req.Header.Set("Pragma", "no-cache")
269 |
270 | // log.Stderr("soapRequest ", req)
271 |
272 | r, err = http.DefaultClient.Do(req)
273 | if err != nil {
274 | return nil, err
275 | }
276 | /*if r.Body != nil {
277 | defer r.Body.Close()
278 | }*/
279 |
280 | if r.StatusCode >= 400 {
281 | // log.Stderr(function, r.StatusCode)
282 | err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
283 | r = nil
284 | return
285 | }
286 | return
287 | }
288 |
289 | type statusInfo struct {
290 | externalIpAddress string
291 | }
292 |
293 | func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
294 |
295 | message := "\r\n" +
296 | ""
297 |
298 | var response *http.Response
299 | response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
300 | if response != nil {
301 | defer response.Body.Close()
302 | }
303 | if err != nil {
304 | return
305 | }
306 | var envelope Envelope
307 | data, err := ioutil.ReadAll(response.Body)
308 | reader := bytes.NewReader(data)
309 | xml.NewDecoder(reader).Decode(&envelope)
310 |
311 | info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
312 |
313 | if err != nil {
314 | return
315 | }
316 |
317 | return
318 | }
319 |
320 | func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
321 | info, err := n.getExternalIPAddress()
322 | if err != nil {
323 | return
324 | }
325 | addr = net.ParseIP(info.externalIpAddress)
326 | return
327 | }
328 |
329 | func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
330 | // A single concatenation would break ARM compilation.
331 | message := "\r\n" +
332 | "" + strconv.Itoa(externalPort)
333 | message += "" + protocol + ""
334 | message += "" + strconv.Itoa(internalPort) + "" +
335 | "" + n.ourIP + "" +
336 | "1"
337 | message += description +
338 | "" + strconv.Itoa(timeout) +
339 | ""
340 |
341 | var response *http.Response
342 | response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
343 | if response != nil {
344 | defer response.Body.Close()
345 | }
346 | if err != nil {
347 | return
348 | }
349 |
350 | // TODO: check response to see if the port was forwarded
351 | // log.Println(message, response)
352 | // JAE:
353 | // body, err := ioutil.ReadAll(response.Body)
354 | // fmt.Println(string(body), err)
355 | mappedExternalPort = externalPort
356 | _ = response
357 | return
358 | }
359 |
360 | func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
361 |
362 | message := "\r\n" +
363 | "" + strconv.Itoa(externalPort) +
364 | "" + protocol + "" +
365 | ""
366 |
367 | var response *http.Response
368 | response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
369 | if response != nil {
370 | defer response.Body.Close()
371 | }
372 | if err != nil {
373 | return
374 | }
375 |
376 | // TODO: check response to see if the port was deleted
377 | // log.Println(message, response)
378 | _ = response
379 | return
380 | }
381 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "crypto/sha256"
5 | )
6 |
7 | // doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes.
8 | func doubleSha256(b []byte) []byte {
9 | hasher := sha256.New()
10 | hasher.Write(b)
11 | sum := hasher.Sum(nil)
12 | hasher.Reset()
13 | hasher.Write(sum)
14 | return hasher.Sum(nil)
15 | }
16 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | const Version = "0.5.0"
4 |
--------------------------------------------------------------------------------