1
1
package dnsd
2
2
3
3
import (
4
- "context"
5
- "fmt"
6
- "io/ioutil"
7
4
"math/rand"
8
5
"net"
9
- "strconv"
10
- "strings"
11
6
"time"
12
7
13
8
"github.com/HouzuoGuo/laitos/lalog"
14
9
"github.com/HouzuoGuo/laitos/toolbox/filter"
15
10
16
11
"github.com/HouzuoGuo/laitos/daemon/common"
17
12
"github.com/HouzuoGuo/laitos/misc"
18
- "github.com/HouzuoGuo/laitos/testingstub"
19
13
)
20
14
21
- /*
22
- StartAndBlockTCP starts DNS daemon to listen on TCP port only, until daemon is told to stop.
23
- Daemon must have already been initialised prior to this call.
24
- */
25
- func (daemon * Daemon ) StartAndBlockTCP () error {
26
- listenAddr := net .JoinHostPort (daemon .Address , strconv .Itoa (daemon .TCPPort ))
27
- listener , err := net .Listen ("tcp" , listenAddr )
28
- if err != nil {
29
- return err
30
- }
31
- defer func () {
32
- daemon .logger .MaybeMinorError (listener .Close ())
33
- }()
34
- daemon .tcpListener = listener
35
- // Process incoming TCP DNS queries
36
- daemon .logger .Info ("StartAndBlockTCP" , listenAddr , nil , "going to listen for queries" )
37
- for {
38
- if misc .EmergencyLockDown {
39
- daemon .logger .Warning ("StartAndBlockTCP" , "" , misc .ErrEmergencyLockDown , "" )
40
- return misc .ErrEmergencyLockDown
41
- }
42
- clientConn , err := listener .Accept ()
43
- if err != nil {
44
- if strings .Contains (err .Error (), "closed" ) {
45
- return nil
46
- }
47
- return fmt .Errorf ("DNSD.StartAndBlockTCP: failed to accept new connection - %v" , err )
48
- }
49
- // Check address against rate limit and allowed IP prefixes
50
- clientIP := clientConn .RemoteAddr ().(* net.TCPAddr ).IP .String ()
51
- if ! daemon .rateLimit .Add (clientIP , true ) {
52
- daemon .logger .MaybeMinorError (clientConn .Close ())
53
- continue
54
- }
55
- go daemon .handleTCPQuery (clientConn )
56
- }
15
+ // GetTCPStatsCollector returns stats collector for the TCP server of this daemon.
16
+ func (daemon * Daemon ) GetTCPStatsCollector () * misc.Stats {
17
+ return common .DNSDStatsTCP
57
18
}
58
19
59
- func (daemon * Daemon ) handleTCPQuery (clientConn net.Conn ) {
60
- // Put query duration (including IO time) into statistics
61
- beginTimeNano := time .Now ().UnixNano ()
62
- defer func () {
63
- daemon .logger .MaybeMinorError (clientConn .Close ())
64
- common .DNSDStatsTCP .Trigger (float64 (time .Now ().UnixNano () - beginTimeNano ))
65
- }()
66
- clientIP := clientConn .RemoteAddr ().(* net.TCPAddr ).IP .String ()
20
+ // HandleConnection converses with a TCP DNS client.
21
+ func (daemon * Daemon ) HandleTCPConnection (logger lalog.Logger , ip string , conn * net.TCPConn ) {
67
22
// Read query length
68
- daemon . logger .MaybeMinorError (clientConn .SetDeadline (time .Now ().Add (ClientTimeoutSec * time .Second )))
23
+ logger .MaybeMinorError (conn .SetDeadline (time .Now ().Add (ClientTimeoutSec * time .Second )))
69
24
queryLen := make ([]byte , 2 )
70
- _ , err := clientConn .Read (queryLen )
25
+ _ , err := conn .Read (queryLen )
71
26
if err != nil {
72
- daemon . logger .Warning ("handleTCPQuery" , clientIP , err , "failed to read query length from client" )
27
+ logger .Warning ("handleTCPQuery" , ip , err , "failed to read query length from client" )
73
28
return
74
29
}
75
30
queryLenInteger := int (queryLen [0 ])* 256 + int (queryLen [1 ])
76
31
// Read query packet
77
32
if queryLenInteger > MaxPacketSize || queryLenInteger < MinNameQuerySize {
78
- daemon . logger .Warning ("handleTCPQuery" , clientIP , nil , "invalid query length from client" )
33
+ logger .Warning ("handleTCPQuery" , ip , nil , "invalid query length from client" )
79
34
return
80
35
}
81
36
queryBody := make ([]byte , queryLenInteger )
82
- _ , err = clientConn .Read (queryBody )
37
+ _ , err = conn .Read (queryBody )
83
38
if err != nil {
84
- daemon . logger .Warning ("handleTCPQuery" , clientIP , err , "failed to read query from client" )
39
+ logger .Warning ("handleTCPQuery" , ip , err , "failed to read query from client" )
85
40
return
86
41
}
87
42
// Formulate a response
88
43
var respBody , respLen []byte
89
44
if isTextQuery (queryBody ) {
90
45
// Handle toolbox command that arrives as a text query
91
- respLen , respBody = daemon .handleTCPTextQuery (clientIP , queryLen , queryBody )
46
+ respLen , respBody = daemon .handleTCPTextQuery (ip , queryLen , queryBody )
92
47
} else {
93
48
// Handle other query types such as name query
94
- respLen , respBody = daemon .handleTCPNameOrOtherQuery (clientIP , queryLen , queryBody )
49
+ respLen , respBody = daemon .handleTCPNameOrOtherQuery (ip , queryLen , queryBody )
95
50
}
96
51
// Close client connection in case there is no appropriate response
97
52
if respBody == nil || len (respBody ) < 2 {
@@ -100,11 +55,11 @@ func (daemon *Daemon) handleTCPQuery(clientConn net.Conn) {
100
55
// Send response to the client, match transaction ID of original query, the deadline is shared with the read deadline above.
101
56
respBody [0 ] = queryBody [0 ]
102
57
respBody [1 ] = queryBody [1 ]
103
- if _ , err := clientConn .Write (respLen ); err != nil {
104
- daemon . logger .Warning ("handleTCPQuery" , clientIP , err , "failed to answer length to client" )
58
+ if _ , err := conn .Write (respLen ); err != nil {
59
+ logger .Warning ("handleTCPQuery" , ip , err , "failed to answer length to client" )
105
60
return
106
- } else if _ , err := clientConn .Write (respBody ); err != nil {
107
- daemon . logger .Warning ("handleTCPQuery" , clientIP , err , "failed to answer to client" )
61
+ } else if _ , err := conn .Write (respBody ); err != nil {
62
+ logger .Warning ("handleTCPQuery" , ip , err , "failed to answer to client" )
108
63
return
109
64
}
110
65
}
@@ -217,72 +172,3 @@ func (daemon *Daemon) handleTCPRecursiveQuery(clientIP string, queryLen, queryBo
217
172
}
218
173
return
219
174
}
220
-
221
- // Run unit tests on DNS TCP daemon. See TestDNSD_StartAndBlockTCP for daemon setup.
222
- func TestTCPQueries (dnsd * Daemon , t testingstub.T ) {
223
- if misc .HostIsWindows () {
224
- // FIXME: fix this test case for Windows
225
- t .Log ("FIXME: enable TestTCPQueries for Windows" )
226
- return
227
- }
228
- // Prevent daemon from listening to UDP queries in this TCP test case
229
- udpListenPort := dnsd .UDPPort
230
- dnsd .UDPPort = 0
231
- defer func () {
232
- dnsd .UDPPort = udpListenPort
233
- }()
234
- // Server should start within two seconds
235
- var stoppedNormally bool
236
- go func () {
237
- if err := dnsd .StartAndBlock (); err != nil {
238
- t .Fatal (err )
239
- }
240
- stoppedNormally = true
241
- }()
242
- time .Sleep (2 * time .Second )
243
- // Use go DNS client to verify that the server returns satisfactory response
244
- resolver := & net.Resolver {
245
- PreferGo : true ,
246
- StrictErrors : true ,
247
- Dial : func (ctx context.Context , network , address string ) (conn net.Conn , e error ) {
248
- return net .Dial ("tcp" , fmt .Sprintf ("127.0.0.1:%d" , dnsd .TCPPort ))
249
- },
250
- }
251
- testResolveNameAndBlackList (t , dnsd , resolver )
252
- // Try to flood the server and reach rate limit
253
- success := 0
254
- dnsd .blackList = map [string ]struct {}{}
255
- for i := 0 ; i < 40 ; i ++ {
256
- go func () {
257
- clientConn , err := net .Dial ("tcp" , "127.0.0.1:" + strconv .Itoa (dnsd .TCPPort ))
258
- if err != nil {
259
- t .Fatal (err )
260
- }
261
- if err := clientConn .SetDeadline (time .Now ().Add (3 * time .Second )); err != nil {
262
- lalog .DefaultLogger .MaybeMinorError (clientConn .Close ())
263
- t .Fatal (err )
264
- }
265
- if _ , err := clientConn .Write (githubComTCPQuery ); err != nil {
266
- lalog .DefaultLogger .MaybeMinorError (clientConn .Close ())
267
- t .Fatal (err )
268
- }
269
- resp , err := ioutil .ReadAll (clientConn )
270
- lalog .DefaultLogger .MaybeMinorError (clientConn .Close ())
271
- if err == nil && len (resp ) > 50 {
272
- success ++
273
- }
274
- }()
275
- }
276
- // Wait for rate limit to reset and verify that regular name resolution resumes
277
- time .Sleep (RateLimitIntervalSec * time .Second )
278
- testResolveNameAndBlackList (t , dnsd , resolver )
279
- // Daemon must stop in a second
280
- dnsd .Stop ()
281
- time .Sleep (1 * time .Second )
282
- if ! stoppedNormally {
283
- t .Fatal ("did not stop" )
284
- }
285
- // Repeatedly stopping the daemon should have no negative consequence
286
- dnsd .Stop ()
287
- dnsd .Stop ()
288
- }
0 commit comments