Coverage Summary for Class: HandshakeHandler (org.ethereum.net.rlpx)
Class |
Class, %
|
Method, %
|
Line, %
|
HandshakeHandler |
0%
(0/1)
|
0%
(0/16)
|
0%
(0/172)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net.rlpx;
21
22 import co.rsk.net.NodeID;
23 import co.rsk.scoring.EventType;
24 import co.rsk.scoring.PeerScoringManager;
25 import com.google.common.annotations.VisibleForTesting;
26 import com.google.common.io.ByteStreams;
27 import io.netty.buffer.ByteBuf;
28 import io.netty.channel.ChannelHandlerContext;
29 import io.netty.handler.codec.ByteToMessageDecoder;
30 import org.bouncycastle.crypto.InvalidCipherTextException;
31 import org.bouncycastle.math.ec.ECPoint;
32 import org.bouncycastle.util.encoders.Hex;
33 import org.ethereum.config.SystemProperties;
34 import org.ethereum.crypto.ECIESCoder;
35 import org.ethereum.crypto.ECKey;
36 import org.ethereum.net.client.Capability;
37 import org.ethereum.net.client.ConfigCapabilities;
38 import org.ethereum.net.eth.EthVersion;
39 import org.ethereum.net.message.Message;
40 import org.ethereum.net.p2p.*;
41 import org.ethereum.net.server.Channel;
42 import org.ethereum.util.ByteUtil;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import java.io.IOException;
47 import java.net.InetAddress;
48 import java.net.InetSocketAddress;
49 import java.net.SocketAddress;
50 import java.util.List;
51
52 import static org.ethereum.net.eth.EthVersion.fromCode;
53 import static org.ethereum.net.rlpx.FrameCodec.Frame;
54 import static org.ethereum.util.ByteUtil.bigEndianToShort;
55
56 /**
57 * The Netty handler which manages initial negotiation with peer
58 * (when either we initiating connection or remote peer initiates)
59 *
60 * The initial handshake includes:
61 * - first AuthInitiate -> AuthResponse messages when peers exchange with secrets
62 * - second P2P Hello messages when P2P protocol and subprotocol capabilities are negotiated
63 *
64 * After the handshake is done this handler reports secrets and other data to the Channel
65 * which installs further handlers depending on the protocol parameters.
66 * This handler is finally removed from the pipeline.
67 */
68 public class HandshakeHandler extends ByteToMessageDecoder {
69
70 private final SystemProperties config;
71 private final PeerScoringManager peerScoringManager;
72 private final P2pHandler p2pHandler;
73 private final MessageCodec messageCodec;
74 private final ConfigCapabilities configCapabilities;
75
76 private static final Logger loggerWire = LoggerFactory.getLogger("wire");
77 private static final Logger loggerNet = LoggerFactory.getLogger("net");
78
79 private FrameCodec frameCodec;
80 private ECKey myKey;
81 private byte[] nodeId;
82 private byte[] remoteId;
83 private EncryptionHandshake handshake;
84 private byte[] initiatePacket;
85 private Channel channel;
86
87 public HandshakeHandler(
88 SystemProperties config,
89 PeerScoringManager peerScoringManager,
90 P2pHandler p2pHandler,
91 MessageCodec messageCodec,
92 ConfigCapabilities configCapabilities) {
93 this.config = config;
94 this.peerScoringManager = peerScoringManager;
95 this.p2pHandler = p2pHandler;
96 this.messageCodec = messageCodec;
97 this.configCapabilities = configCapabilities;
98 this.myKey = config.getMyKey();
99 }
100
101 @Override
102 public void channelActive(ChannelHandlerContext ctx) throws Exception {
103 channel.setInetSocketAddress((InetSocketAddress) ctx.channel().remoteAddress());
104 internalChannelActive(ctx);
105 }
106
107 @VisibleForTesting
108 public void internalChannelActive(ChannelHandlerContext ctx) throws Exception {
109 if (remoteId.length == 64) {
110 channel.setNode(remoteId);
111 initiate(ctx);
112 } else {
113 handshake = new EncryptionHandshake();
114 nodeId = myKey.getNodeId();
115 }
116 }
117
118 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
119 loggerWire.debug("Decoding handshake... ({} bytes available)", in.readableBytes());
120 decodeHandshake(ctx, in);
121 }
122
123 private void initiate(ChannelHandlerContext ctx) throws Exception {
124
125 loggerNet.trace("RLPX protocol activated");
126
127 nodeId = myKey.getNodeId();
128
129 byte[] remotePublicBytes = new byte[remoteId.length + 1];
130 System.arraycopy(remoteId, 0, remotePublicBytes, 1, remoteId.length);
131 remotePublicBytes[0] = 0x04; // uncompressed
132 ECPoint remotePublic = ECKey.fromPublicOnly(remotePublicBytes).getPubKeyPoint();
133 handshake = new EncryptionHandshake(remotePublic);
134
135 Object msg;
136 if (config.eip8()) {
137 AuthInitiateMessageV4 initiateMessage = handshake.createAuthInitiateV4(myKey);
138 initiatePacket = handshake.encryptAuthInitiateV4(initiateMessage);
139 msg = initiateMessage;
140 } else {
141 AuthInitiateMessage initiateMessage = handshake.createAuthInitiate(null, myKey);
142 initiatePacket = handshake.encryptAuthMessage(initiateMessage);
143 msg = initiateMessage;
144 }
145
146 final ByteBuf byteBufMsg = ctx.alloc().buffer(initiatePacket.length);
147 byteBufMsg.writeBytes(initiatePacket);
148 ctx.writeAndFlush(byteBufMsg).sync();
149
150 channel.getNodeStatistics().rlpxAuthMessagesSent.add();
151
152 loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), msg);
153 }
154
155 // consume handshake, producing no resulting message to upper layers
156 private void decodeHandshake(final ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
157
158 if (handshake.isInitiator()) {
159 if (frameCodec == null) {
160
161 byte[] responsePacket = new byte[AuthResponseMessage.getLength() + ECIESCoder.getOverhead()];
162 if (!buffer.isReadable(responsePacket.length)) {
163 return;
164 }
165 buffer.readBytes(responsePacket);
166
167 try {
168
169 // trying to decode as pre-EIP-8
170
171 AuthResponseMessage response = handshake.handleAuthResponse(myKey, initiatePacket, responsePacket);
172 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), response);
173
174 } catch (Throwable t) {
175
176 // it must be format defined by EIP-8 then
177
178 responsePacket = readEIP8Packet(buffer, responsePacket);
179
180 if (responsePacket == null) {
181 return;
182 }
183
184 AuthResponseMessageV4 response = handshake.handleAuthResponseV4(myKey, initiatePacket, responsePacket);
185 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), response);
186 }
187
188 EncryptionHandshake.Secrets secrets = this.handshake.getSecrets();
189 this.frameCodec = new FrameCodec(secrets);
190
191 loggerNet.trace("auth exchange done");
192 channel.sendHelloMessage(ctx, frameCodec, ByteUtil.toHexString(nodeId), null);
193 } else {
194 loggerWire.debug("MessageCodec: Buffer bytes: {}", buffer.readableBytes());
195 List<Frame> frames = frameCodec.readFrames(buffer);
196 if (frames == null || frames.isEmpty()) {
197 return;
198 }
199 Frame frame = frames.get(0);
200 byte[] payload = ByteStreams.toByteArray(frame.getStream());
201 if (frame.getType() == P2pMessageCodes.HELLO.asByte()) {
202 HelloMessage helloMessage = new HelloMessage(payload);
203 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), helloMessage);
204 processHelloMessage(ctx, helloMessage);
205 } else {
206 DisconnectMessage message = new DisconnectMessage(payload);
207 loggerNet.trace("From: \t{} \tRecv: \t{}", channel, message);
208 channel.getNodeStatistics().nodeDisconnectedRemote(message.getReason());
209 }
210 }
211 } else {
212 loggerWire.debug("Not initiator.");
213 if (frameCodec == null) {
214 loggerWire.debug("FrameCodec == null");
215 byte[] authInitPacket = new byte[AuthInitiateMessage.getLength() + ECIESCoder.getOverhead()];
216 if (!buffer.isReadable(authInitPacket.length)) {
217 return;
218 }
219 buffer.readBytes(authInitPacket);
220
221 this.handshake = new EncryptionHandshake();
222
223 byte[] responsePacket;
224
225 try {
226
227 // trying to decode as pre-EIP-8
228 AuthInitiateMessage initiateMessage = handshake.decryptAuthInitiate(authInitPacket, myKey);
229 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), initiateMessage);
230
231 AuthResponseMessage response = handshake.makeAuthInitiate(initiateMessage, myKey);
232 loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), response);
233 responsePacket = handshake.encryptAuthResponse(response);
234
235 } catch (Throwable t) {
236
237 // it must be format defined by EIP-8 then
238 try {
239
240 authInitPacket = readEIP8Packet(buffer, authInitPacket);
241
242 if (authInitPacket == null) {
243 return;
244 }
245
246 AuthInitiateMessageV4 initiateMessage = handshake.decryptAuthInitiateV4(authInitPacket, myKey);
247 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), initiateMessage);
248
249 AuthResponseMessageV4 response = handshake.makeAuthInitiateV4(initiateMessage, myKey);
250 loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), response);
251 responsePacket = handshake.encryptAuthResponseV4(response);
252
253 } catch (InvalidCipherTextException ce) {
254 loggerNet.warn(
255 "Can't decrypt AuthInitiateMessage from {}. Most likely the remote peer used wrong public key (NodeID) to encrypt message.",
256 ctx.channel().remoteAddress()
257 );
258 return;
259 }
260 }
261
262 handshake.agreeSecret(authInitPacket, responsePacket);
263
264 EncryptionHandshake.Secrets secrets = this.handshake.getSecrets();
265 this.frameCodec = new FrameCodec(secrets);
266
267 ECPoint remotePubKey = this.handshake.getRemotePublicKey();
268
269 byte[] compressed = remotePubKey.getEncoded(false);
270
271 this.remoteId = new byte[compressed.length - 1];
272 System.arraycopy(compressed, 1, this.remoteId, 0, this.remoteId.length);
273 channel.setNode(remoteId);
274
275 final ByteBuf byteBufMsg = ctx.alloc().buffer(responsePacket.length);
276 byteBufMsg.writeBytes(responsePacket);
277 ctx.writeAndFlush(byteBufMsg).sync();
278 } else {
279 List<Frame> frames = frameCodec.readFrames(buffer);
280 if (frames == null || frames.isEmpty()) {
281 return;
282 }
283 Frame frame = frames.get(0);
284
285 Message message = new P2pMessageFactory().create((byte) frame.getType(),
286 ByteStreams.toByteArray(frame.getStream()));
287 loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), message);
288
289 if (frame.getType() == P2pMessageCodes.DISCONNECT.asByte()) {
290 loggerNet.info("Active remote peer disconnected right after handshake.");
291 return;
292 }
293
294 if (frame.getType() != P2pMessageCodes.HELLO.asByte()) {
295 throw new RuntimeException("The message type is not HELLO or DISCONNECT: " + message);
296 }
297
298 HelloMessage inboundHelloMessage = (HelloMessage) message;
299
300 // Secret authentication finish here
301 channel.sendHelloMessage(ctx, frameCodec, ByteUtil.toHexString(nodeId), inboundHelloMessage);
302 processHelloMessage(ctx, inboundHelloMessage);
303 }
304 }
305 channel.getNodeStatistics().rlpxInHello.add();
306 }
307
308 private byte[] readEIP8Packet(ByteBuf buffer, byte[] plainPacket) {
309
310 int size = bigEndianToShort(plainPacket);
311 if (size < plainPacket.length) {
312 throw new IllegalArgumentException("AuthResponse packet size is too low");
313 }
314
315 int bytesLeft = size - plainPacket.length + 2;
316 byte[] restBytes = new byte[bytesLeft];
317
318 if (!buffer.isReadable(restBytes.length)) {
319 return null;
320 }
321
322 buffer.readBytes(restBytes);
323
324 byte[] fullResponse = new byte[size + 2];
325 System.arraycopy(plainPacket, 0, fullResponse, 0, plainPacket.length);
326 System.arraycopy(restBytes, 0, fullResponse, plainPacket.length, restBytes.length);
327
328 return fullResponse;
329 }
330
331 public void setRemoteId(String remoteId, Channel channel){
332 this.remoteId = Hex.decode(remoteId);
333 this.channel = channel;
334 }
335
336 /**
337 * Generate random Key (and thus NodeID) per channel for 'anonymous'
338 * connection (e.g. for peer discovery)
339 */
340 public void generateTempKey() {
341 myKey = new ECKey();
342 }
343
344 @Override
345 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
346 recordFailedHandshake(ctx);
347 if (cause instanceof IOException) {
348 loggerNet.info("Handshake failed: address {}", ctx.channel().remoteAddress(), cause);
349 } else {
350 loggerNet.warn("Handshake failed", cause);
351 }
352 ctx.close();
353 }
354
355 private void recordFailedHandshake(ChannelHandlerContext ctx) {
356 recordEvent(ctx, EventType.FAILED_HANDSHAKE);
357 }
358
359 private void recordSuccessfulHandshake(ChannelHandlerContext ctx) {
360 recordEvent(ctx, EventType.SUCCESSFUL_HANDSHAKE);
361 }
362
363 private void recordEvent(ChannelHandlerContext ctx, EventType event) {
364 SocketAddress socketAddress = ctx.channel().remoteAddress();
365
366 //TODO(mmarquez): what if it is not ??
367 if (socketAddress instanceof InetSocketAddress) {
368 NodeID nodeID = channel.getNodeId();
369
370 InetAddress address = ((InetSocketAddress)socketAddress).getAddress();
371
372 peerScoringManager.recordEvent(nodeID, address, event);
373 }
374 }
375
376 private void processHelloMessage(ChannelHandlerContext ctx, HelloMessage helloMessage) {
377 List<Capability> capInCommon = configCapabilities.getSupportedCapabilities(helloMessage);
378 channel.initMessageCodes(capInCommon);
379 for (Capability capability : capInCommon) {
380 // It seems that the only supported capability is RSK, and everything else is ignored.
381 if (Capability.RSK.equals(capability.getName())) {
382 publicRLPxHandshakeFinished(ctx, helloMessage, fromCode(capability.getVersion()));
383 return;
384 }
385 }
386
387 throw new RuntimeException("The remote peer didn't support the RSK capability");
388 }
389
390 private void publicRLPxHandshakeFinished(ChannelHandlerContext ctx, HelloMessage helloRemote, EthVersion ethVersion) {
391 if (!P2pHandler.isProtocolVersionSupported(helloRemote.getP2PVersion())) {
392 throw new RuntimeException(String.format(
393 "The remote peer protocol version %s isn't supported", helloRemote.getP2PVersion()
394 ));
395 }
396
397 loggerNet.debug("publicRLPxHandshakeFinished with {}", ctx.channel().remoteAddress());
398 if (helloRemote.getP2PVersion() < 5) {
399 messageCodec.setSupportChunkedFrames(false);
400 }
401
402 FrameCodecHandler frameCodecHandler = new FrameCodecHandler(frameCodec, channel);
403 ctx.pipeline().addLast("medianFrameCodec", frameCodecHandler);
404 ctx.pipeline().addLast("messageCodec", messageCodec);
405 ctx.pipeline().addLast(Capability.P2P, p2pHandler);
406
407 p2pHandler.setChannel(channel);
408 p2pHandler.setHandshake(helloRemote);
409 channel.activateEth(ctx, ethVersion);
410
411 channel.getNodeStatistics().rlpxHandshake.add();
412
413 recordSuccessfulHandshake(ctx);
414
415 loggerWire.debug("Handshake done, removing HandshakeHandler from pipeline.");
416 ctx.pipeline().remove(this);
417 }
418 }