discv4tests.go 13 KB

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