gitee.com/liu-zhao234568/cntest@v1.0.0/cmd/devp2p/internal/v4test/discv4tests.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package v4test 18 19 import ( 20 "bytes" 21 "crypto/rand" 22 "fmt" 23 "net" 24 "time" 25 26 "gitee.com/liu-zhao234568/cntest/crypto" 27 "gitee.com/liu-zhao234568/cntest/internal/utesting" 28 "gitee.com/liu-zhao234568/cntest/p2p/discover/v4wire" 29 ) 30 31 const ( 32 expiration = 20 * time.Second 33 wrongPacket = 66 34 macSize = 256 / 8 35 ) 36 37 var ( 38 // Remote node under test 39 Remote string 40 // IP where the first tester is listening, port will be assigned 41 Listen1 string = "127.0.0.1" 42 // IP where the second tester is listening, port will be assigned 43 // Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least) 44 Listen2 string = "127.0.0.2" 45 ) 46 47 type pingWithJunk struct { 48 Version uint 49 From, To v4wire.Endpoint 50 Expiration uint64 51 JunkData1 uint 52 JunkData2 []byte 53 } 54 55 func (req *pingWithJunk) Name() string { return "PING/v4" } 56 func (req *pingWithJunk) Kind() byte { return v4wire.PingPacket } 57 58 type pingWrongType struct { 59 Version uint 60 From, To v4wire.Endpoint 61 Expiration uint64 62 } 63 64 func (req *pingWrongType) Name() string { return "WRONG/v4" } 65 func (req *pingWrongType) Kind() byte { return wrongPacket } 66 67 func futureExpiration() uint64 { 68 return uint64(time.Now().Add(expiration).Unix()) 69 } 70 71 // This test just sends a PING packet and expects a response. 72 func BasicPing(t *utesting.T) { 73 te := newTestEnv(Remote, Listen1, Listen2) 74 defer te.close() 75 76 pingHash := te.send(te.l1, &v4wire.Ping{ 77 Version: 4, 78 From: te.localEndpoint(te.l1), 79 To: te.remoteEndpoint(), 80 Expiration: futureExpiration(), 81 }) 82 83 reply, _, _ := te.read(te.l1) 84 if err := te.checkPong(reply, pingHash); err != nil { 85 t.Fatal(err) 86 } 87 } 88 89 // checkPong verifies that reply is a valid PONG matching the given ping hash. 90 func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { 91 if reply == nil { 92 return fmt.Errorf("expected PONG reply, got nil") 93 } 94 if reply.Kind() != v4wire.PongPacket { 95 return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply) 96 } 97 pong := reply.(*v4wire.Pong) 98 if !bytes.Equal(pong.ReplyTok, pingHash) { 99 return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) 100 } 101 if want := te.localEndpoint(te.l1); !want.IP.Equal(pong.To.IP) || want.UDP != pong.To.UDP { 102 return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, want) 103 } 104 if v4wire.Expired(pong.Expiration) { 105 return fmt.Errorf("PONG is expired (%v)", pong.Expiration) 106 } 107 return nil 108 } 109 110 // This test sends a PING packet with wrong 'to' field and expects a PONG response. 111 func PingWrongTo(t *utesting.T) { 112 te := newTestEnv(Remote, Listen1, Listen2) 113 defer te.close() 114 115 wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} 116 pingHash := te.send(te.l1, &v4wire.Ping{ 117 Version: 4, 118 From: te.localEndpoint(te.l1), 119 To: wrongEndpoint, 120 Expiration: futureExpiration(), 121 }) 122 123 reply, _, _ := te.read(te.l1) 124 if err := te.checkPong(reply, pingHash); err != nil { 125 t.Fatal(err) 126 } 127 } 128 129 // This test sends a PING packet with wrong 'from' field and expects a PONG response. 130 func PingWrongFrom(t *utesting.T) { 131 te := newTestEnv(Remote, Listen1, Listen2) 132 defer te.close() 133 134 wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} 135 pingHash := te.send(te.l1, &v4wire.Ping{ 136 Version: 4, 137 From: wrongEndpoint, 138 To: te.remoteEndpoint(), 139 Expiration: futureExpiration(), 140 }) 141 142 reply, _, _ := te.read(te.l1) 143 if err := te.checkPong(reply, pingHash); err != nil { 144 t.Fatal(err) 145 } 146 } 147 148 // This test sends a PING packet with additional data at the end and expects a PONG 149 // response. The remote node should respond because EIP-8 mandates ignoring additional 150 // trailing data. 151 func PingExtraData(t *utesting.T) { 152 te := newTestEnv(Remote, Listen1, Listen2) 153 defer te.close() 154 155 pingHash := te.send(te.l1, &pingWithJunk{ 156 Version: 4, 157 From: te.localEndpoint(te.l1), 158 To: te.remoteEndpoint(), 159 Expiration: futureExpiration(), 160 JunkData1: 42, 161 JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, 162 }) 163 164 reply, _, _ := te.read(te.l1) 165 if err := te.checkPong(reply, pingHash); err != nil { 166 t.Fatal(err) 167 } 168 } 169 170 // This test sends a PING packet with additional data and wrong 'from' field 171 // and expects a PONG response. 172 func PingExtraDataWrongFrom(t *utesting.T) { 173 te := newTestEnv(Remote, Listen1, Listen2) 174 defer te.close() 175 176 wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} 177 req := pingWithJunk{ 178 Version: 4, 179 From: wrongEndpoint, 180 To: te.remoteEndpoint(), 181 Expiration: futureExpiration(), 182 JunkData1: 42, 183 JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, 184 } 185 pingHash := te.send(te.l1, &req) 186 reply, _, _ := te.read(te.l1) 187 if err := te.checkPong(reply, pingHash); err != nil { 188 t.Fatal(err) 189 } 190 } 191 192 // This test sends a PING packet with an expiration in the past. 193 // The remote node should not respond. 194 func PingPastExpiration(t *utesting.T) { 195 te := newTestEnv(Remote, Listen1, Listen2) 196 defer te.close() 197 198 te.send(te.l1, &v4wire.Ping{ 199 Version: 4, 200 From: te.localEndpoint(te.l1), 201 To: te.remoteEndpoint(), 202 Expiration: -futureExpiration(), 203 }) 204 205 reply, _, _ := te.read(te.l1) 206 if reply != nil { 207 t.Fatal("Expected no reply, got", reply) 208 } 209 } 210 211 // This test sends an invalid packet. The remote node should not respond. 212 func WrongPacketType(t *utesting.T) { 213 te := newTestEnv(Remote, Listen1, Listen2) 214 defer te.close() 215 216 te.send(te.l1, &pingWrongType{ 217 Version: 4, 218 From: te.localEndpoint(te.l1), 219 To: te.remoteEndpoint(), 220 Expiration: futureExpiration(), 221 }) 222 223 reply, _, _ := te.read(te.l1) 224 if reply != nil { 225 t.Fatal("Expected no reply, got", reply) 226 } 227 } 228 229 // This test verifies that the default behaviour of ignoring 'from' fields is unaffected by 230 // the bonding process. After bonding, it pings the target with a different from endpoint. 231 func BondThenPingWithWrongFrom(t *utesting.T) { 232 te := newTestEnv(Remote, Listen1, Listen2) 233 defer te.close() 234 bond(t, te) 235 236 wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} 237 pingHash := te.send(te.l1, &v4wire.Ping{ 238 Version: 4, 239 From: wrongEndpoint, 240 To: te.remoteEndpoint(), 241 Expiration: futureExpiration(), 242 }) 243 244 reply, _, _ := te.read(te.l1) 245 if err := te.checkPong(reply, pingHash); err != nil { 246 t.Fatal(err) 247 } 248 } 249 250 // This test just sends FINDNODE. The remote node should not reply 251 // because the endpoint proof has not completed. 252 func FindnodeWithoutEndpointProof(t *utesting.T) { 253 te := newTestEnv(Remote, Listen1, Listen2) 254 defer te.close() 255 256 req := v4wire.Findnode{Expiration: futureExpiration()} 257 rand.Read(req.Target[:]) 258 te.send(te.l1, &req) 259 260 reply, _, _ := te.read(te.l1) 261 if reply != nil { 262 t.Fatal("Expected no response, got", reply) 263 } 264 } 265 266 // BasicFindnode sends a FINDNODE request after performing the endpoint 267 // proof. The remote node should respond. 268 func BasicFindnode(t *utesting.T) { 269 te := newTestEnv(Remote, Listen1, Listen2) 270 defer te.close() 271 bond(t, te) 272 273 findnode := v4wire.Findnode{Expiration: futureExpiration()} 274 rand.Read(findnode.Target[:]) 275 te.send(te.l1, &findnode) 276 277 reply, _, err := te.read(te.l1) 278 if err != nil { 279 t.Fatal("read find nodes", err) 280 } 281 if reply.Kind() != v4wire.NeighborsPacket { 282 t.Fatal("Expected neighbors, got", reply.Name()) 283 } 284 } 285 286 // This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends 287 // FINDNODE to read the remote table. The remote node should not return the node contained 288 // in the unsolicited NEIGHBORS packet. 289 func UnsolicitedNeighbors(t *utesting.T) { 290 te := newTestEnv(Remote, Listen1, Listen2) 291 defer te.close() 292 bond(t, te) 293 294 // Send unsolicited NEIGHBORS response. 295 fakeKey, _ := crypto.GenerateKey() 296 encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey) 297 neighbors := v4wire.Neighbors{ 298 Expiration: futureExpiration(), 299 Nodes: []v4wire.Node{{ 300 ID: encFakeKey, 301 IP: net.IP{1, 2, 3, 4}, 302 UDP: 30303, 303 TCP: 30303, 304 }}, 305 } 306 te.send(te.l1, &neighbors) 307 308 // Check if the remote node included the fake node. 309 te.send(te.l1, &v4wire.Findnode{ 310 Expiration: futureExpiration(), 311 Target: encFakeKey, 312 }) 313 314 reply, _, err := te.read(te.l1) 315 if err != nil { 316 t.Fatal("read find nodes", err) 317 } 318 if reply.Kind() != v4wire.NeighborsPacket { 319 t.Fatal("Expected neighbors, got", reply.Name()) 320 } 321 nodes := reply.(*v4wire.Neighbors).Nodes 322 if contains(nodes, encFakeKey) { 323 t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") 324 } 325 } 326 327 // This test sends FINDNODE with an expiration timestamp in the past. 328 // The remote node should not respond. 329 func FindnodePastExpiration(t *utesting.T) { 330 te := newTestEnv(Remote, Listen1, Listen2) 331 defer te.close() 332 bond(t, te) 333 334 findnode := v4wire.Findnode{Expiration: -futureExpiration()} 335 rand.Read(findnode.Target[:]) 336 te.send(te.l1, &findnode) 337 338 for { 339 reply, _, _ := te.read(te.l1) 340 if reply == nil { 341 return 342 } else if reply.Kind() == v4wire.NeighborsPacket { 343 t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request") 344 } 345 } 346 } 347 348 // bond performs the endpoint proof with the remote node. 349 func bond(t *utesting.T, te *testenv) { 350 te.send(te.l1, &v4wire.Ping{ 351 Version: 4, 352 From: te.localEndpoint(te.l1), 353 To: te.remoteEndpoint(), 354 Expiration: futureExpiration(), 355 }) 356 357 var gotPing, gotPong bool 358 for !gotPing || !gotPong { 359 req, hash, err := te.read(te.l1) 360 if err != nil { 361 t.Fatal(err) 362 } 363 switch req.(type) { 364 case *v4wire.Ping: 365 te.send(te.l1, &v4wire.Pong{ 366 To: te.remoteEndpoint(), 367 ReplyTok: hash, 368 Expiration: futureExpiration(), 369 }) 370 gotPing = true 371 case *v4wire.Pong: 372 // TODO: maybe verify pong data here 373 gotPong = true 374 } 375 } 376 } 377 378 // This test attempts to perform a traffic amplification attack against a 379 // 'victim' endpoint using FINDNODE. In this attack scenario, the attacker 380 // attempts to complete the endpoint proof non-interactively by sending a PONG 381 // with mismatching reply token from the 'victim' endpoint. The attack works if 382 // the remote node does not verify the PONG reply token field correctly. The 383 // attacker could then perform traffic amplification by sending many FINDNODE 384 // requests to the discovery node, which would reply to the 'victim' address. 385 func FindnodeAmplificationInvalidPongHash(t *utesting.T) { 386 te := newTestEnv(Remote, Listen1, Listen2) 387 defer te.close() 388 389 // Send PING to start endpoint verification. 390 te.send(te.l1, &v4wire.Ping{ 391 Version: 4, 392 From: te.localEndpoint(te.l1), 393 To: te.remoteEndpoint(), 394 Expiration: futureExpiration(), 395 }) 396 397 var gotPing, gotPong bool 398 for !gotPing || !gotPong { 399 req, _, err := te.read(te.l1) 400 if err != nil { 401 t.Fatal(err) 402 } 403 switch req.(type) { 404 case *v4wire.Ping: 405 // Send PONG from this node ID, but with invalid ReplyTok. 406 te.send(te.l1, &v4wire.Pong{ 407 To: te.remoteEndpoint(), 408 ReplyTok: make([]byte, macSize), 409 Expiration: futureExpiration(), 410 }) 411 gotPing = true 412 case *v4wire.Pong: 413 gotPong = true 414 } 415 } 416 417 // Now send FINDNODE. The remote node should not respond because our 418 // PONG did not reference the PING hash. 419 findnode := v4wire.Findnode{Expiration: futureExpiration()} 420 rand.Read(findnode.Target[:]) 421 te.send(te.l1, &findnode) 422 423 // If we receive a NEIGHBORS response, the attack worked and the test fails. 424 reply, _, _ := te.read(te.l1) 425 if reply != nil && reply.Kind() == v4wire.NeighborsPacket { 426 t.Error("Got neighbors") 427 } 428 } 429 430 // This test attempts to perform a traffic amplification attack using FINDNODE. 431 // The attack works if the remote node does not verify the IP address of FINDNODE 432 // against the endpoint verification proof done by PING/PONG. 433 func FindnodeAmplificationWrongIP(t *utesting.T) { 434 te := newTestEnv(Remote, Listen1, Listen2) 435 defer te.close() 436 437 // Do the endpoint proof from the l1 IP. 438 bond(t, te) 439 440 // Now send FINDNODE from the same node ID, but different IP address. 441 // The remote node should not respond. 442 findnode := v4wire.Findnode{Expiration: futureExpiration()} 443 rand.Read(findnode.Target[:]) 444 te.send(te.l2, &findnode) 445 446 // If we receive a NEIGHBORS response, the attack worked and the test fails. 447 reply, _, _ := te.read(te.l2) 448 if reply != nil { 449 t.Error("Got NEIGHORS response for FINDNODE from wrong IP") 450 } 451 } 452 453 var AllTests = []utesting.Test{ 454 {Name: "Ping/Basic", Fn: BasicPing}, 455 {Name: "Ping/WrongTo", Fn: PingWrongTo}, 456 {Name: "Ping/WrongFrom", Fn: PingWrongFrom}, 457 {Name: "Ping/ExtraData", Fn: PingExtraData}, 458 {Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom}, 459 {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, 460 {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, 461 {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, 462 {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, 463 {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, 464 {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, 465 {Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration}, 466 {Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash}, 467 {Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP}, 468 }