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