-
Notifications
You must be signed in to change notification settings - Fork 41
/
Copy pathcrawler.go
176 lines (146 loc) · 5.14 KB
/
crawler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package main
import (
"errors"
"log"
"net"
"strconv"
"time"
"github.com/btcsuite/btcd/wire"
)
type crawlError struct {
errLoc string
Err error
}
// Error returns a formatted error about a crawl
func (e *crawlError) Error() string {
return "err: " + e.errLoc + ": " + e.Err.Error()
}
// crawlNode runs in a goroutine, crawls the remote ip and updates the master
// list of currently active addresses
func crawlNode(rc chan *result, s *dnsseeder, nd *node) {
res := &result{
node: net.JoinHostPort(nd.na.IP.String(), strconv.Itoa(int(nd.na.Port))),
}
// connect to the remote ip and ask them for their addr list
res.nas, res.msg = crawlIP(s, res)
// all done so push the result back to the seeder.
//This will block until the seeder reads the result
rc <- res
// goroutine will end and be cleaned up
}
// crawlIP retrievs a slice of ip addresses from a client
func crawlIP(s *dnsseeder, r *result) ([]*wire.NetAddress, *crawlError) {
conn, err := net.DialTimeout("tcp", r.node, time.Second*10)
if err != nil {
if config.debug {
log.Printf("%s - debug - Could not connect to %s - %v\n", s.name, r.node, err)
}
return nil, &crawlError{"", err}
}
defer conn.Close()
if config.debug {
log.Printf("%s - debug - Connected to remote address: %s\n", s.name, r.node)
}
// set a deadline for all comms to be done by. After this all i/o will error
conn.SetDeadline(time.Now().Add(time.Second * maxTo))
meAddr, youAddr := conn.LocalAddr(), conn.RemoteAddr()
me := wire.NewNetAddress(meAddr.(*net.TCPAddr), wire.SFNodeNetwork)
you := wire.NewNetAddress(youAddr.(*net.TCPAddr), wire.SFNodeNetwork)
msgver := wire.NewMsgVersion(me, you, nounce, 0)
err = wire.WriteMessage(conn, msgver, s.pver, s.id)
if err != nil {
// Log and handle the error
return nil, &crawlError{"Write Version Message", err}
}
// first message received should be version
msg, _, err := wire.ReadMessage(conn, s.pver, s.id)
if err != nil {
// Log and handle the error
return nil, &crawlError{"Read message after sending Version", err}
}
switch msg := msg.(type) {
case *wire.MsgVersion:
// The message is a pointer to a MsgVersion struct.
if config.debug {
log.Printf("%s - debug - %s - Remote version: %v\n", s.name, r.node, msg.ProtocolVersion)
}
// fill the node struct with the remote details
r.version = msg.ProtocolVersion
r.services = msg.Services
r.lastBlock = msg.LastBlock
r.strVersion = msg.UserAgent
default:
return nil, &crawlError{"Did not receive expected Version message from remote client", errors.New("")}
}
// send verack command
msgverack := wire.NewMsgVerAck()
err = wire.WriteMessage(conn, msgverack, s.pver, s.id)
if err != nil {
return nil, &crawlError{"writing message VerAck", err}
}
// second message received should be verack
msg, _, err = wire.ReadMessage(conn, s.pver, s.id)
if err != nil {
return nil, &crawlError{"reading expected Ver Ack from remote client", err}
}
switch msg.(type) {
case *wire.MsgVerAck:
if config.debug {
log.Printf("%s - debug - %s - received Version Ack\n", s.name, r.node)
}
default:
return nil, &crawlError{"Did not receive expected Ver Ack message from remote client", errors.New("")}
}
// if we get this far and if the seeder is full then don't ask for addresses. This will reduce bandwith usage while still
// confirming that we can connect to the remote node
if len(s.theList) > s.maxSize {
return nil, nil
}
// send getaddr command
msgGetAddr := wire.NewMsgGetAddr()
err = wire.WriteMessage(conn, msgGetAddr, s.pver, s.id)
if err != nil {
return nil, &crawlError{"writing Addr message to remote client", err}
}
c := 0
dowhile := true
peers := []*wire.NetAddress{}
for dowhile == true {
// Using the Bitcoin lib for the some networks means it does not understand some
// of the commands and will error. We can ignore these as we are only
// interested in the addr message and its content.
msgaddr, _, _ := wire.ReadMessage(conn, s.pver, s.id)
if msgaddr != nil {
switch msg := msgaddr.(type) {
case *wire.MsgAddr:
// received the addr message so return the result
if config.debug {
log.Printf("%s - debug - %s - received valid addr message\n", s.name, r.node)
}
peers = append(peers, msg.AddrList...)
// Bitcoin nodes typically return two Addr messages: one with
// only one peer, and another with many peers. This is
// probably because ancient protocol versions (pver < 209) only
// allowed one peer per Addr mesage, so returning a one-peer
// Addr message first improves backward-compatibility. Anyway,
// this means we need to wait for the second Addr message.
if len(peers) > 1 {
dowhile = false
return msg.AddrList, nil
}
default:
if config.debug {
log.Printf("%s - debug - %s - ignoring message - %v\n", s.name, r.node, msg.Command())
}
}
}
// if we get more than 25 messages before the addr we asked for then give up on this client
if c++; c >= 25 {
dowhile = false
}
}
// received too many messages before requested Addr
return nil, &crawlError{"message loop - did not receive remote addresses in first 25 messages from remote client", errors.New("")}
}
/*
*/