github.com/pslzym/go-ethereum@v1.8.17-0.20180926104442-4b6824e07b1b/p2p/discover/udp.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser 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 // The go-ethereum library 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 Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 "bytes" 21 "container/list" 22 "crypto/ecdsa" 23 "errors" 24 "fmt" 25 "net" 26 "time" 27 28 "github.com/ethereum/go-ethereum/crypto" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "github.com/ethereum/go-ethereum/p2p/nat" 32 "github.com/ethereum/go-ethereum/p2p/netutil" 33 "github.com/ethereum/go-ethereum/rlp" 34 ) 35 36 // Errors 37 var ( 38 errPacketTooSmall = errors.New("too small") 39 errBadHash = errors.New("bad hash") 40 errExpired = errors.New("expired") 41 errUnsolicitedReply = errors.New("unsolicited reply") 42 errUnknownNode = errors.New("unknown node") 43 errTimeout = errors.New("RPC timeout") 44 errClockWarp = errors.New("reply deadline too far in the future") 45 errClosed = errors.New("socket closed") 46 ) 47 48 // Timeouts 49 const ( 50 respTimeout = 500 * time.Millisecond 51 expiration = 20 * time.Second 52 bondExpiration = 24 * time.Hour 53 54 ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP 55 ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning 56 driftThreshold = 10 * time.Second // Allowed clock drift before warning user 57 ) 58 59 // RPC packet types 60 const ( 61 pingPacket = iota + 1 // zero is 'reserved' 62 pongPacket 63 findnodePacket 64 neighborsPacket 65 ) 66 67 // RPC request structures 68 type ( 69 ping struct { 70 Version uint 71 From, To rpcEndpoint 72 Expiration uint64 73 // Ignore additional fields (for forward compatibility). 74 Rest []rlp.RawValue `rlp:"tail"` 75 } 76 77 // pong is the reply to ping. 78 pong struct { 79 // This field should mirror the UDP envelope address 80 // of the ping packet, which provides a way to discover the 81 // the external address (after NAT). 82 To rpcEndpoint 83 84 ReplyTok []byte // This contains the hash of the ping packet. 85 Expiration uint64 // Absolute timestamp at which the packet becomes invalid. 86 // Ignore additional fields (for forward compatibility). 87 Rest []rlp.RawValue `rlp:"tail"` 88 } 89 90 // findnode is a query for nodes close to the given target. 91 findnode struct { 92 Target encPubkey 93 Expiration uint64 94 // Ignore additional fields (for forward compatibility). 95 Rest []rlp.RawValue `rlp:"tail"` 96 } 97 98 // reply to findnode 99 neighbors struct { 100 Nodes []rpcNode 101 Expiration uint64 102 // Ignore additional fields (for forward compatibility). 103 Rest []rlp.RawValue `rlp:"tail"` 104 } 105 106 rpcNode struct { 107 IP net.IP // len 4 for IPv4 or 16 for IPv6 108 UDP uint16 // for discovery protocol 109 TCP uint16 // for RLPx protocol 110 ID encPubkey 111 } 112 113 rpcEndpoint struct { 114 IP net.IP // len 4 for IPv4 or 16 for IPv6 115 UDP uint16 // for discovery protocol 116 TCP uint16 // for RLPx protocol 117 } 118 ) 119 120 func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { 121 ip := addr.IP.To4() 122 if ip == nil { 123 ip = addr.IP.To16() 124 } 125 return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} 126 } 127 128 func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { 129 if rn.UDP <= 1024 { 130 return nil, errors.New("low port") 131 } 132 if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { 133 return nil, err 134 } 135 if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { 136 return nil, errors.New("not contained in netrestrict whitelist") 137 } 138 key, err := decodePubkey(rn.ID) 139 if err != nil { 140 return nil, err 141 } 142 n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) 143 err = n.ValidateComplete() 144 return n, err 145 } 146 147 func nodeToRPC(n *node) rpcNode { 148 var key ecdsa.PublicKey 149 var ekey encPubkey 150 if err := n.Load((*enode.Secp256k1)(&key)); err == nil { 151 ekey = encodePubkey(&key) 152 } 153 return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} 154 } 155 156 type packet interface { 157 handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error 158 name() string 159 } 160 161 type conn interface { 162 ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) 163 WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) 164 Close() error 165 LocalAddr() net.Addr 166 } 167 168 // udp implements the RPC protocol. 169 type udp struct { 170 conn conn 171 netrestrict *netutil.Netlist 172 priv *ecdsa.PrivateKey 173 ourEndpoint rpcEndpoint 174 175 addpending chan *pending 176 gotreply chan reply 177 178 closing chan struct{} 179 nat nat.Interface 180 181 *Table 182 } 183 184 // pending represents a pending reply. 185 // 186 // some implementations of the protocol wish to send more than one 187 // reply packet to findnode. in general, any neighbors packet cannot 188 // be matched up with a specific findnode packet. 189 // 190 // our implementation handles this by storing a callback function for 191 // each pending reply. incoming packets from a node are dispatched 192 // to all the callback functions for that node. 193 type pending struct { 194 // these fields must match in the reply. 195 from enode.ID 196 ptype byte 197 198 // time when the request must complete 199 deadline time.Time 200 201 // callback is called when a matching reply arrives. if it returns 202 // true, the callback is removed from the pending reply queue. 203 // if it returns false, the reply is considered incomplete and 204 // the callback will be invoked again for the next matching reply. 205 callback func(resp interface{}) (done bool) 206 207 // errc receives nil when the callback indicates completion or an 208 // error if no further reply is received within the timeout. 209 errc chan<- error 210 } 211 212 type reply struct { 213 from enode.ID 214 ptype byte 215 data interface{} 216 // loop indicates whether there was 217 // a matching request by sending on this channel. 218 matched chan<- bool 219 } 220 221 // ReadPacket is sent to the unhandled channel when it could not be processed 222 type ReadPacket struct { 223 Data []byte 224 Addr *net.UDPAddr 225 } 226 227 // Config holds Table-related settings. 228 type Config struct { 229 // These settings are required and configure the UDP listener: 230 PrivateKey *ecdsa.PrivateKey 231 232 // These settings are optional: 233 AnnounceAddr *net.UDPAddr // local address announced in the DHT 234 NodeDBPath string // if set, the node database is stored at this filesystem location 235 NetRestrict *netutil.Netlist // network whitelist 236 Bootnodes []*enode.Node // list of bootstrap nodes 237 Unhandled chan<- ReadPacket // unhandled packets are sent on this channel 238 } 239 240 // ListenUDP returns a new table that listens for UDP packets on laddr. 241 func ListenUDP(c conn, cfg Config) (*Table, error) { 242 tab, _, err := newUDP(c, cfg) 243 if err != nil { 244 return nil, err 245 } 246 log.Info("UDP listener up", "self", tab.self) 247 return tab, nil 248 } 249 250 func newUDP(c conn, cfg Config) (*Table, *udp, error) { 251 realaddr := c.LocalAddr().(*net.UDPAddr) 252 if cfg.AnnounceAddr != nil { 253 realaddr = cfg.AnnounceAddr 254 } 255 self := enode.NewV4(&cfg.PrivateKey.PublicKey, realaddr.IP, realaddr.Port, realaddr.Port) 256 db, err := enode.OpenDB(cfg.NodeDBPath) 257 if err != nil { 258 return nil, nil, err 259 } 260 261 udp := &udp{ 262 conn: c, 263 priv: cfg.PrivateKey, 264 netrestrict: cfg.NetRestrict, 265 closing: make(chan struct{}), 266 gotreply: make(chan reply), 267 addpending: make(chan *pending), 268 } 269 // TODO: separate TCP port 270 udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port)) 271 tab, err := newTable(udp, self, db, cfg.Bootnodes) 272 if err != nil { 273 return nil, nil, err 274 } 275 udp.Table = tab 276 277 go udp.loop() 278 go udp.readLoop(cfg.Unhandled) 279 return udp.Table, udp, nil 280 } 281 282 func (t *udp) close() { 283 close(t.closing) 284 t.conn.Close() 285 t.db.Close() 286 // TODO: wait for the loops to end. 287 } 288 289 // ping sends a ping message to the given node and waits for a reply. 290 func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error { 291 return <-t.sendPing(toid, toaddr, nil) 292 } 293 294 // sendPing sends a ping message to the given node and invokes the callback 295 // when the reply arrives. 296 func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error { 297 req := &ping{ 298 Version: 4, 299 From: t.ourEndpoint, 300 To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB 301 Expiration: uint64(time.Now().Add(expiration).Unix()), 302 } 303 packet, hash, err := encodePacket(t.priv, pingPacket, req) 304 if err != nil { 305 errc := make(chan error, 1) 306 errc <- err 307 return errc 308 } 309 errc := t.pending(toid, pongPacket, func(p interface{}) bool { 310 ok := bytes.Equal(p.(*pong).ReplyTok, hash) 311 if ok && callback != nil { 312 callback() 313 } 314 return ok 315 }) 316 t.write(toaddr, req.name(), packet) 317 return errc 318 } 319 320 func (t *udp) waitping(from enode.ID) error { 321 return <-t.pending(from, pingPacket, func(interface{}) bool { return true }) 322 } 323 324 // findnode sends a findnode request to the given node and waits until 325 // the node has sent up to k neighbors. 326 func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { 327 // If we haven't seen a ping from the destination node for a while, it won't remember 328 // our endpoint proof and reject findnode. Solicit a ping first. 329 if time.Since(t.db.LastPingReceived(toid)) > bondExpiration { 330 t.ping(toid, toaddr) 331 t.waitping(toid) 332 } 333 334 nodes := make([]*node, 0, bucketSize) 335 nreceived := 0 336 errc := t.pending(toid, neighborsPacket, func(r interface{}) bool { 337 reply := r.(*neighbors) 338 for _, rn := range reply.Nodes { 339 nreceived++ 340 n, err := t.nodeFromRPC(toaddr, rn) 341 if err != nil { 342 log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err) 343 continue 344 } 345 nodes = append(nodes, n) 346 } 347 return nreceived >= bucketSize 348 }) 349 t.send(toaddr, findnodePacket, &findnode{ 350 Target: target, 351 Expiration: uint64(time.Now().Add(expiration).Unix()), 352 }) 353 return nodes, <-errc 354 } 355 356 // pending adds a reply callback to the pending reply queue. 357 // see the documentation of type pending for a detailed explanation. 358 func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool) <-chan error { 359 ch := make(chan error, 1) 360 p := &pending{from: id, ptype: ptype, callback: callback, errc: ch} 361 select { 362 case t.addpending <- p: 363 // loop will handle it 364 case <-t.closing: 365 ch <- errClosed 366 } 367 return ch 368 } 369 370 func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool { 371 matched := make(chan bool, 1) 372 select { 373 case t.gotreply <- reply{from, ptype, req, matched}: 374 // loop will handle it 375 return <-matched 376 case <-t.closing: 377 return false 378 } 379 } 380 381 // loop runs in its own goroutine. it keeps track of 382 // the refresh timer and the pending reply queue. 383 func (t *udp) loop() { 384 var ( 385 plist = list.New() 386 timeout = time.NewTimer(0) 387 nextTimeout *pending // head of plist when timeout was last reset 388 contTimeouts = 0 // number of continuous timeouts to do NTP checks 389 ntpWarnTime = time.Unix(0, 0) 390 ) 391 <-timeout.C // ignore first timeout 392 defer timeout.Stop() 393 394 resetTimeout := func() { 395 if plist.Front() == nil || nextTimeout == plist.Front().Value { 396 return 397 } 398 // Start the timer so it fires when the next pending reply has expired. 399 now := time.Now() 400 for el := plist.Front(); el != nil; el = el.Next() { 401 nextTimeout = el.Value.(*pending) 402 if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout { 403 timeout.Reset(dist) 404 return 405 } 406 // Remove pending replies whose deadline is too far in the 407 // future. These can occur if the system clock jumped 408 // backwards after the deadline was assigned. 409 nextTimeout.errc <- errClockWarp 410 plist.Remove(el) 411 } 412 nextTimeout = nil 413 timeout.Stop() 414 } 415 416 for { 417 resetTimeout() 418 419 select { 420 case <-t.closing: 421 for el := plist.Front(); el != nil; el = el.Next() { 422 el.Value.(*pending).errc <- errClosed 423 } 424 return 425 426 case p := <-t.addpending: 427 p.deadline = time.Now().Add(respTimeout) 428 plist.PushBack(p) 429 430 case r := <-t.gotreply: 431 var matched bool 432 for el := plist.Front(); el != nil; el = el.Next() { 433 p := el.Value.(*pending) 434 if p.from == r.from && p.ptype == r.ptype { 435 matched = true 436 // Remove the matcher if its callback indicates 437 // that all replies have been received. This is 438 // required for packet types that expect multiple 439 // reply packets. 440 if p.callback(r.data) { 441 p.errc <- nil 442 plist.Remove(el) 443 } 444 // Reset the continuous timeout counter (time drift detection) 445 contTimeouts = 0 446 } 447 } 448 r.matched <- matched 449 450 case now := <-timeout.C: 451 nextTimeout = nil 452 453 // Notify and remove callbacks whose deadline is in the past. 454 for el := plist.Front(); el != nil; el = el.Next() { 455 p := el.Value.(*pending) 456 if now.After(p.deadline) || now.Equal(p.deadline) { 457 p.errc <- errTimeout 458 plist.Remove(el) 459 contTimeouts++ 460 } 461 } 462 // If we've accumulated too many timeouts, do an NTP time sync check 463 if contTimeouts > ntpFailureThreshold { 464 if time.Since(ntpWarnTime) >= ntpWarningCooldown { 465 ntpWarnTime = time.Now() 466 go checkClockDrift() 467 } 468 contTimeouts = 0 469 } 470 } 471 } 472 } 473 474 const ( 475 macSize = 256 / 8 476 sigSize = 520 / 8 477 headSize = macSize + sigSize // space of packet frame data 478 ) 479 480 var ( 481 headSpace = make([]byte, headSize) 482 483 // Neighbors replies are sent across multiple packets to 484 // stay below the 1280 byte limit. We compute the maximum number 485 // of entries by stuffing a packet until it grows too large. 486 maxNeighbors int 487 ) 488 489 func init() { 490 p := neighbors{Expiration: ^uint64(0)} 491 maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} 492 for n := 0; ; n++ { 493 p.Nodes = append(p.Nodes, maxSizeNode) 494 size, _, err := rlp.EncodeToReader(p) 495 if err != nil { 496 // If this ever happens, it will be caught by the unit tests. 497 panic("cannot encode: " + err.Error()) 498 } 499 if headSize+size+1 >= 1280 { 500 maxNeighbors = n 501 break 502 } 503 } 504 } 505 506 func (t *udp) send(toaddr *net.UDPAddr, ptype byte, req packet) ([]byte, error) { 507 packet, hash, err := encodePacket(t.priv, ptype, req) 508 if err != nil { 509 return hash, err 510 } 511 return hash, t.write(toaddr, req.name(), packet) 512 } 513 514 func (t *udp) write(toaddr *net.UDPAddr, what string, packet []byte) error { 515 _, err := t.conn.WriteToUDP(packet, toaddr) 516 log.Trace(">> "+what, "addr", toaddr, "err", err) 517 return err 518 } 519 520 func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error) { 521 b := new(bytes.Buffer) 522 b.Write(headSpace) 523 b.WriteByte(ptype) 524 if err := rlp.Encode(b, req); err != nil { 525 log.Error("Can't encode discv4 packet", "err", err) 526 return nil, nil, err 527 } 528 packet = b.Bytes() 529 sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) 530 if err != nil { 531 log.Error("Can't sign discv4 packet", "err", err) 532 return nil, nil, err 533 } 534 copy(packet[macSize:], sig) 535 // add the hash to the front. Note: this doesn't protect the 536 // packet in any way. Our public key will be part of this hash in 537 // The future. 538 hash = crypto.Keccak256(packet[macSize:]) 539 copy(packet, hash) 540 return packet, hash, nil 541 } 542 543 // readLoop runs in its own goroutine. it handles incoming UDP packets. 544 func (t *udp) readLoop(unhandled chan<- ReadPacket) { 545 defer t.conn.Close() 546 if unhandled != nil { 547 defer close(unhandled) 548 } 549 // Discovery packets are defined to be no larger than 1280 bytes. 550 // Packets larger than this size will be cut at the end and treated 551 // as invalid because their hash won't match. 552 buf := make([]byte, 1280) 553 for { 554 nbytes, from, err := t.conn.ReadFromUDP(buf) 555 if netutil.IsTemporaryError(err) { 556 // Ignore temporary read errors. 557 log.Debug("Temporary UDP read error", "err", err) 558 continue 559 } else if err != nil { 560 // Shut down the loop for permament errors. 561 log.Debug("UDP read error", "err", err) 562 return 563 } 564 if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil { 565 select { 566 case unhandled <- ReadPacket{buf[:nbytes], from}: 567 default: 568 } 569 } 570 } 571 } 572 573 func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { 574 packet, fromID, hash, err := decodePacket(buf) 575 if err != nil { 576 log.Debug("Bad discv4 packet", "addr", from, "err", err) 577 return err 578 } 579 err = packet.handle(t, from, fromID, hash) 580 log.Trace("<< "+packet.name(), "addr", from, "err", err) 581 return err 582 } 583 584 func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { 585 if len(buf) < headSize+1 { 586 return nil, encPubkey{}, nil, errPacketTooSmall 587 } 588 hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] 589 shouldhash := crypto.Keccak256(buf[macSize:]) 590 if !bytes.Equal(hash, shouldhash) { 591 return nil, encPubkey{}, nil, errBadHash 592 } 593 fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig) 594 if err != nil { 595 return nil, fromKey, hash, err 596 } 597 598 var req packet 599 switch ptype := sigdata[0]; ptype { 600 case pingPacket: 601 req = new(ping) 602 case pongPacket: 603 req = new(pong) 604 case findnodePacket: 605 req = new(findnode) 606 case neighborsPacket: 607 req = new(neighbors) 608 default: 609 return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) 610 } 611 s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) 612 err = s.Decode(req) 613 return req, fromKey, hash, err 614 } 615 616 func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { 617 if expired(req.Expiration) { 618 return errExpired 619 } 620 key, err := decodePubkey(fromKey) 621 if err != nil { 622 return fmt.Errorf("invalid public key: %v", err) 623 } 624 t.send(from, pongPacket, &pong{ 625 To: makeEndpoint(from, req.From.TCP), 626 ReplyTok: mac, 627 Expiration: uint64(time.Now().Add(expiration).Unix()), 628 }) 629 n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port)) 630 t.handleReply(n.ID(), pingPacket, req) 631 if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration { 632 t.sendPing(n.ID(), from, func() { t.addThroughPing(n) }) 633 } else { 634 t.addThroughPing(n) 635 } 636 t.db.UpdateLastPingReceived(n.ID(), time.Now()) 637 return nil 638 } 639 640 func (req *ping) name() string { return "PING/v4" } 641 642 func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { 643 if expired(req.Expiration) { 644 return errExpired 645 } 646 fromID := fromKey.id() 647 if !t.handleReply(fromID, pongPacket, req) { 648 return errUnsolicitedReply 649 } 650 t.db.UpdateLastPongReceived(fromID, time.Now()) 651 return nil 652 } 653 654 func (req *pong) name() string { return "PONG/v4" } 655 656 func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { 657 if expired(req.Expiration) { 658 return errExpired 659 } 660 fromID := fromKey.id() 661 if time.Since(t.db.LastPongReceived(fromID)) > bondExpiration { 662 // No endpoint proof pong exists, we don't process the packet. This prevents an 663 // attack vector where the discovery protocol could be used to amplify traffic in a 664 // DDOS attack. A malicious actor would send a findnode request with the IP address 665 // and UDP port of the target as the source address. The recipient of the findnode 666 // packet would then send a neighbors packet (which is a much bigger packet than 667 // findnode) to the victim. 668 return errUnknownNode 669 } 670 target := enode.ID(crypto.Keccak256Hash(req.Target[:])) 671 t.mutex.Lock() 672 closest := t.closest(target, bucketSize).entries 673 t.mutex.Unlock() 674 675 p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} 676 var sent bool 677 // Send neighbors in chunks with at most maxNeighbors per packet 678 // to stay below the 1280 byte limit. 679 for _, n := range closest { 680 if netutil.CheckRelayIP(from.IP, n.IP()) == nil { 681 p.Nodes = append(p.Nodes, nodeToRPC(n)) 682 } 683 if len(p.Nodes) == maxNeighbors { 684 t.send(from, neighborsPacket, &p) 685 p.Nodes = p.Nodes[:0] 686 sent = true 687 } 688 } 689 if len(p.Nodes) > 0 || !sent { 690 t.send(from, neighborsPacket, &p) 691 } 692 return nil 693 } 694 695 func (req *findnode) name() string { return "FINDNODE/v4" } 696 697 func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { 698 if expired(req.Expiration) { 699 return errExpired 700 } 701 if !t.handleReply(fromKey.id(), neighborsPacket, req) { 702 return errUnsolicitedReply 703 } 704 return nil 705 } 706 707 func (req *neighbors) name() string { return "NEIGHBORS/v4" } 708 709 func expired(ts uint64) bool { 710 return time.Unix(int64(ts), 0).Before(time.Now()) 711 }