diff options
Diffstat (limited to 'p2p')
-rw-r--r-- | p2p/discover/database.go | 2 | ||||
-rw-r--r-- | p2p/discover/node.go | 2 | ||||
-rw-r--r-- | p2p/discover/ntp.go | 127 | ||||
-rw-r--r-- | p2p/discover/table.go | 6 | ||||
-rw-r--r-- | p2p/discover/table_test.go | 4 | ||||
-rw-r--r-- | p2p/discover/udp.go | 36 | ||||
-rw-r--r-- | p2p/discover/udp_test.go | 2 | ||||
-rw-r--r-- | p2p/rlpx.go | 8 | ||||
-rw-r--r-- | p2p/rlpx_test.go | 4 |
9 files changed, 168 insertions, 23 deletions
diff --git a/p2p/discover/database.go b/p2p/discover/database.go index e8e3371ff..6d448515d 100644 --- a/p2p/discover/database.go +++ b/p2p/discover/database.go @@ -188,7 +188,7 @@ func (db *nodeDB) node(id NodeID) *Node { glog.V(logger.Warn).Infof("failed to decode node RLP: %v", err) return nil } - node.sha = crypto.Sha3Hash(node.ID[:]) + node.sha = crypto.Keccak256Hash(node.ID[:]) return node } diff --git a/p2p/discover/node.go b/p2p/discover/node.go index c4a3b5011..139a95d80 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -67,7 +67,7 @@ func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { UDP: udpPort, TCP: tcpPort, ID: id, - sha: crypto.Sha3Hash(id[:]), + sha: crypto.Keccak256Hash(id[:]), } } diff --git a/p2p/discover/ntp.go b/p2p/discover/ntp.go new file mode 100644 index 000000000..c1a4b3af1 --- /dev/null +++ b/p2p/discover/ntp.go @@ -0,0 +1,127 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +// Contains the NTP time drift detection via the SNTP protocol: +// https://tools.ietf.org/html/rfc4330 + +package discover + +import ( + "fmt" + "net" + "sort" + "strings" + "time" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +const ( + ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time + ntpChecks = 3 // Number of measurements to do against the NTP server +) + +// durationSlice attaches the methods of sort.Interface to []time.Duration, +// sorting in increasing order. +type durationSlice []time.Duration + +func (s durationSlice) Len() int { return len(s) } +func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] } +func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// checkClockDrift queries an NTP server for clock drifts and warns the user if +// one large enough is detected. +func checkClockDrift() { + drift, err := sntpDrift(ntpChecks) + if err != nil { + return + } + if drift < -driftThreshold || drift > driftThreshold { + warning := fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift) + howtofix := fmt.Sprintf("Please enable network time synchronisation in system settings") + separator := strings.Repeat("-", len(warning)) + + glog.V(logger.Warn).Info(separator) + glog.V(logger.Warn).Info(warning) + glog.V(logger.Warn).Info(howtofix) + glog.V(logger.Warn).Info(separator) + } else { + glog.V(logger.Debug).Infof("Sanity NTP check reported %v drift, all ok", drift) + } +} + +// sntpDrift does a naive time resolution against an NTP server and returns the +// measured drift. This method uses the simple version of NTP. It's not precise +// but should be fine for these purposes. +// +// Note, it executes two extra measurements compared to the number of requested +// ones to be able to discard the two extremes as outliers. +func sntpDrift(measurements int) (time.Duration, error) { + // Resolve the address of the NTP server + addr, err := net.ResolveUDPAddr("udp", ntpPool+":123") + if err != nil { + return 0, err + } + // Construct the time request (empty package with only 2 fields set): + // Bits 3-5: Protocol version, 3 + // Bits 6-8: Mode of operation, client, 3 + request := make([]byte, 48) + request[0] = 3<<3 | 3 + + // Execute each of the measurements + drifts := []time.Duration{} + for i := 0; i < measurements+2; i++ { + // Dial the NTP server and send the time retrieval request + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + return 0, err + } + defer conn.Close() + + sent := time.Now() + if _, err = conn.Write(request); err != nil { + return 0, err + } + // Retrieve the reply and calculate the elapsed time + conn.SetDeadline(time.Now().Add(5 * time.Second)) + + reply := make([]byte, 48) + if _, err = conn.Read(reply); err != nil { + return 0, err + } + elapsed := time.Since(sent) + + // Reconstruct the time from the reply data + sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24 + frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24 + + nanosec := sec*1e9 + (frac*1e9)>>32 + + t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local() + + // Calculate the drift based on an assumed answer time of RRT/2 + drifts = append(drifts, sent.Sub(t)+elapsed/2) + } + // Calculate average drif (drop two extremities to avoid outliers) + sort.Sort(durationSlice(drifts)) + + drift := time.Duration(0) + for i := 1; i < len(drifts)-1; i++ { + drift += drifts[i] + } + return drift / time.Duration(measurements), nil +} diff --git a/p2p/discover/table.go b/p2p/discover/table.go index abb7980f8..1de045f04 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -195,7 +195,7 @@ func (tab *Table) SetFallbackNodes(nodes []*Node) error { cpy := *n // Recompute cpy.sha because the node might not have been // created by NewNode or ParseNode. - cpy.sha = crypto.Sha3Hash(n.ID[:]) + cpy.sha = crypto.Keccak256Hash(n.ID[:]) tab.nursery = append(tab.nursery, &cpy) } tab.mutex.Unlock() @@ -208,7 +208,7 @@ func (tab *Table) SetFallbackNodes(nodes []*Node) error { func (tab *Table) Resolve(targetID NodeID) *Node { // If the node is present in the local table, no // network interaction is required. - hash := crypto.Sha3Hash(targetID[:]) + hash := crypto.Keccak256Hash(targetID[:]) tab.mutex.Lock() cl := tab.closest(hash, 1) tab.mutex.Unlock() @@ -236,7 +236,7 @@ func (tab *Table) Lookup(targetID NodeID) []*Node { func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { var ( - target = crypto.Sha3Hash(targetID[:]) + target = crypto.Keccak256Hash(targetID[:]) asked = make(map[NodeID]bool) seen = make(map[NodeID]bool) reply = make(chan []*Node, alpha) diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 30a418f44..1a2405740 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -530,12 +530,12 @@ func (*preminedTestnet) ping(toid NodeID, toaddr *net.UDPAddr) error { return ni // various distances to the given target. func (n *preminedTestnet) mine(target NodeID) { n.target = target - n.targetSha = crypto.Sha3Hash(n.target[:]) + n.targetSha = crypto.Keccak256Hash(n.target[:]) found := 0 for found < bucketSize*10 { k := newkey() id := PubkeyID(&k.PublicKey) - sha := crypto.Sha3Hash(id[:]) + sha := crypto.Keccak256Hash(id[:]) ld := logdist(n.targetSha, sha) if len(n.dists[ld]) < bucketSize { n.dists[ld] = append(n.dists[ld], id) diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 81674f552..74758b6fd 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -51,6 +51,10 @@ const ( respTimeout = 500 * time.Millisecond sendTimeout = 500 * time.Millisecond expiration = 20 * time.Second + + ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP + ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning + driftThreshold = 10 * time.Second // Allowed clock drift before warning user ) // RPC packet types @@ -316,13 +320,15 @@ func (t *udp) handleReply(from NodeID, ptype byte, req packet) bool { } } -// loop runs in its own goroutin. it keeps track of +// loop runs in its own goroutine. it keeps track of // the refresh timer and the pending reply queue. func (t *udp) loop() { var ( - plist = list.New() - timeout = time.NewTimer(0) - nextTimeout *pending // head of plist when timeout was last reset + plist = list.New() + timeout = time.NewTimer(0) + nextTimeout *pending // head of plist when timeout was last reset + contTimeouts = 0 // number of continuous timeouts to do NTP checks + ntpWarnTime = time.Unix(0, 0) ) <-timeout.C // ignore first timeout defer timeout.Stop() @@ -377,19 +383,31 @@ func (t *udp) loop() { p.errc <- nil plist.Remove(el) } + // Reset the continuous timeout counter (time drift detection) + contTimeouts = 0 } } r.matched <- matched case now := <-timeout.C: nextTimeout = nil + // Notify and remove callbacks whose deadline is in the past. for el := plist.Front(); el != nil; el = el.Next() { p := el.Value.(*pending) if now.After(p.deadline) || now.Equal(p.deadline) { p.errc <- errTimeout plist.Remove(el) + contTimeouts++ + } + } + // If we've accumulated too many timeouts, do an NTP time sync check + if contTimeouts > ntpFailureThreshold { + if time.Since(ntpWarnTime) >= ntpWarningCooldown { + ntpWarnTime = time.Now() + go checkClockDrift() } + contTimeouts = 0 } } } @@ -448,7 +466,7 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) ([]byte, return nil, err } packet := b.Bytes() - sig, err := crypto.Sign(crypto.Sha3(packet[headSize:]), priv) + sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) if err != nil { glog.V(logger.Error).Infoln("could not sign packet:", err) return nil, err @@ -457,7 +475,7 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) ([]byte, // add the hash to the front. Note: this doesn't protect the // packet in any way. Our public key will be part of this hash in // The future. - copy(packet, crypto.Sha3(packet[macSize:])) + copy(packet, crypto.Keccak256(packet[macSize:])) return packet, nil } @@ -509,11 +527,11 @@ func decodePacket(buf []byte) (packet, NodeID, []byte, error) { return nil, NodeID{}, nil, errPacketTooSmall } hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] - shouldhash := crypto.Sha3(buf[macSize:]) + shouldhash := crypto.Keccak256(buf[macSize:]) if !bytes.Equal(hash, shouldhash) { return nil, NodeID{}, nil, errBadHash } - fromID, err := recoverNodeID(crypto.Sha3(buf[headSize:]), sig) + fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig) if err != nil { return nil, NodeID{}, hash, err } @@ -575,7 +593,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte // (which is a much bigger packet than findnode) to the victim. return errUnknownNode } - target := crypto.Sha3Hash(req.Target[:]) + target := crypto.Keccak256Hash(req.Target[:]) t.mutex.Lock() closest := t.closest(target, bucketSize).entries t.mutex.Unlock() diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 66fc4cf2c..3939a69a7 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -286,7 +286,7 @@ func TestUDP_findnode(t *testing.T) { // put a few nodes into the table. their exact // distribution shouldn't matter much, altough we need to // take care not to overflow any bucket. - targetHash := crypto.Sha3Hash(testTarget[:]) + targetHash := crypto.Keccak256Hash(testTarget[:]) nodes := &nodesByDistance{target: targetHash} for i := 0; i < bucketSize; i++ { nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSize) diff --git a/p2p/rlpx.go b/p2p/rlpx.go index 9d6cba5b6..ddfafe9a4 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -232,12 +232,12 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { } // derive base secrets from ephemeral key agreement - sharedSecret := crypto.Sha3(ecdheSecret, crypto.Sha3(h.respNonce, h.initNonce)) - aesSecret := crypto.Sha3(ecdheSecret, sharedSecret) + sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) + aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) s := secrets{ RemoteID: h.remoteID, AES: aesSecret, - MAC: crypto.Sha3(ecdheSecret, aesSecret), + MAC: crypto.Keccak256(ecdheSecret, aesSecret), } // setup sha3 instances for the MACs @@ -426,7 +426,7 @@ func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) { buf := make([]byte, authMsgLen) n := copy(buf, msg.Signature[:]) - n += copy(buf[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey))) + n += copy(buf[n:], crypto.Keccak256(exportPubkey(&h.randomPrivKey.PublicKey))) n += copy(buf[n:], msg.InitiatorPubkey[:]) n += copy(buf[n:], msg.Nonce[:]) buf[n] = 0 // token-flag diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go index f9583e224..f4cefa650 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx_test.go @@ -267,8 +267,8 @@ func TestRLPXFrameFake(t *testing.T) { buf := new(bytes.Buffer) hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) rw := newRLPXFrameRW(buf, secrets{ - AES: crypto.Sha3(), - MAC: crypto.Sha3(), + AES: crypto.Keccak256(), + MAC: crypto.Keccak256(), IngressMAC: hash, EgressMAC: hash, }) |