/** Notify all the handshake futures about the failure during the handshake. */ private void setHandshakeFailure(Throwable cause) { // Release all resources such as internal buffers that SSLEngine // is managing. engine.closeOutbound(); try { engine.closeInbound(); } catch (SSLException e) { if (logger.isDebugEnabled()) { logger.debug( "SSLEngine.closeInbound() raised an exception after " + "a handshake failure.", e); } } if (cause == null) { cause = new ClosedChannelException(); } for (; ; ) { ChannelFuture f = handshakeFutures.poll(); if (f == null) { break; } f.setFailure(cause); } flush0(ctx, 0, cause); }
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { logger.debug( String.format( "Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); if (frame instanceof CloseWebSocketFrame) { this.handshaker.performClosingHandshake(ctx, (CloseWebSocketFrame) frame); } else if (frame instanceof PingWebSocketFrame) { ctx.getChannel() .write( new PongWebSocketFrame( frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); } else if (frame instanceof TextWebSocketFrame) { // String text = ((TextWebSocketFrame) frame).getText(); ctx.getChannel() .write( new TextWebSocketFrame( frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); } else if (frame instanceof BinaryWebSocketFrame) { ctx.getChannel() .write( new BinaryWebSocketFrame( frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); } else if (frame instanceof ContinuationWebSocketFrame) { ctx.getChannel() .write( new ContinuationWebSocketFrame( frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); } else if (frame instanceof PongWebSocketFrame) { // Ignore } else { throw new UnsupportedOperationException( String.format("%s frame types not supported", frame.getClass().getName())); } }
/** * Handle the web socket handshake for the web socket specification <a href= * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>. * Versions 13-17 share the same wire protocol. * * <p>Browser request to the server: * * <pre> * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 13 * </pre> * * <p>Server response: * * <pre> * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= * Sec-WebSocket-Protocol: chat * </pre> * * @param channel Channel * @param req HTTP request */ @Override public ChannelFuture handshake(Channel channel, HttpRequest req) { if (logger.isDebugEnabled()) { logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId())); } HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS); String key = req.getHeader(Names.SEC_WEBSOCKET_KEY); if (key == null) { throw new WebSocketHandshakeException("not a WebSocket request: missing key"); } String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID; byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); String accept = WebSocketUtil.base64(sha1); if (logger.isDebugEnabled()) { logger.debug( String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept)); } res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS); res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); if (protocol != null) { res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); } ChannelFuture future = channel.write(res); // Upgrade the connection and send the handshake response. ChannelPipeline p = channel.getPipeline(); if (p.get(HttpChunkAggregator.class) != null) { p.remove(HttpChunkAggregator.class); } p.replace( HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, allowExtensions, this.getMaxFramePayloadLength())); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); return future; }
@Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // Make sure the handshake future is notified when a connection has // been closed during handshake. setHandshakeFailure(null); try { inboundBufferUpdated(ctx); } finally { engine.closeOutbound(); try { engine.closeInbound(); } catch (SSLException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to clean up SSLEngine.", ex); } } ctx.fireChannelInactive(); } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (ignoreException(cause)) { // It is safe to ignore the 'connection reset by peer' or // 'broken pipe' error after sending closure_notify. if (logger.isDebugEnabled()) { logger.debug( "Swallowing a 'connection reset by peer / " + "broken pipe' error occurred while writing " + "'closure_notify'", cause); } // Close the connection explicitly just in case the transport // did not close the connection automatically. if (ctx.channel().isActive()) { ctx.close(); } return; } super.exceptionCaught(ctx, cause); }
@Override public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { // Discard all data received if closing handshake was received before. if (receivedClosingHandshake) { in.skipBytes(actualReadableBytes()); return null; } switch (state()) { case FRAME_START: framePayloadBytesRead = 0; framePayloadLength = -1; framePayload = null; // FIN, RSV, OPCODE byte b = in.readByte(); frameFinalFlag = (b & 0x80) != 0; frameRsv = (b & 0x70) >> 4; frameOpcode = b & 0x0F; if (logger.isDebugEnabled()) { logger.debug("Decoding WebSocket Frame opCode={}", frameOpcode); } // MASK, PAYLOAD LEN 1 b = in.readByte(); boolean frameMasked = (b & 0x80) != 0; int framePayloadLen1 = b & 0x7F; if (frameRsv != 0 && !allowExtensions) { protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv); return null; } if (maskedPayload && !frameMasked) { protocolViolation(ctx, "unmasked client to server frame"); return null; } if (frameOpcode > 7) { // control frame (have MSB in opcode set) // control frames MUST NOT be fragmented if (!frameFinalFlag) { protocolViolation(ctx, "fragmented control frame"); return null; } // control frames MUST have payload 125 octets or less if (framePayloadLen1 > 125) { protocolViolation(ctx, "control frame with payload length > 125 octets"); return null; } // check for reserved control frame opcodes if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) { protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode); return null; } // close frame : if there is a body, the first two bytes of the // body MUST be a 2-byte unsigned integer (in network byte // order) representing a getStatus code if (frameOpcode == 8 && framePayloadLen1 == 1) { protocolViolation(ctx, "received close control frame with payload len 1"); return null; } } else { // data frame // check for reserved data frame opcodes if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) { protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode); return null; } // check opcode vs message fragmentation state 1/2 if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) { protocolViolation(ctx, "received continuation data frame outside fragmented message"); return null; } // check opcode vs message fragmentation state 2/2 if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) { protocolViolation( ctx, "received non-continuation data frame while inside fragmented message"); return null; } } // Read frame payload length if (framePayloadLen1 == 126) { framePayloadLength = in.readUnsignedShort(); if (framePayloadLength < 126) { protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)"); return null; } } else if (framePayloadLen1 == 127) { framePayloadLength = in.readLong(); // TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe // just check if it's negative? if (framePayloadLength < 65536) { protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)"); return null; } } else { framePayloadLength = framePayloadLen1; } if (framePayloadLength > maxFramePayloadLength) { protocolViolation( ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded."); return null; } if (logger.isDebugEnabled()) { logger.debug("Decoding WebSocket Frame length={}", framePayloadLength); } checkpoint(State.MASKING_KEY); case MASKING_KEY: if (maskedPayload) { maskingKey = in.readBytes(4); } checkpoint(State.PAYLOAD); case PAYLOAD: // Sometimes, the payload may not be delivered in 1 nice packet // We need to accumulate the data until we have it all int rbytes = actualReadableBytes(); ByteBuf payloadBuffer = null; long willHaveReadByteCount = framePayloadBytesRead + rbytes; // logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount=" // + willHaveReadByteCount + " framePayloadLength=" + // framePayloadLength); if (willHaveReadByteCount == framePayloadLength) { // We have all our content so proceed to process payloadBuffer = ctx.alloc().buffer(rbytes); payloadBuffer.writeBytes(in, rbytes); } else if (willHaveReadByteCount < framePayloadLength) { // We don't have all our content so accumulate payload. // Returning null means we will get called back if (framePayload == null) { framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength)); } framePayload.writeBytes(in, rbytes); framePayloadBytesRead += rbytes; // Return null to wait for more bytes to arrive return null; } else if (willHaveReadByteCount > framePayloadLength) { // We have more than what we need so read up to the end of frame // Leave the remainder in the buffer for next frame if (framePayload == null) { framePayload = ctx.alloc().buffer(toFrameLength(framePayloadLength)); } framePayload.writeBytes(in, toFrameLength(framePayloadLength - framePayloadBytesRead)); } // Now we have all the data, the next checkpoint must be the next // frame checkpoint(State.FRAME_START); // Take the data that we have in this packet if (framePayload == null) { framePayload = payloadBuffer; } else if (payloadBuffer != null) { framePayload.writeBytes(payloadBuffer); } // Unmask data if needed if (maskedPayload) { unmask(framePayload); } // Processing ping/pong/close frames because they cannot be // fragmented if (frameOpcode == OPCODE_PING) { return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload); } if (frameOpcode == OPCODE_PONG) { return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload); } if (frameOpcode == OPCODE_CLOSE) { checkCloseFrameBody(ctx, framePayload); receivedClosingHandshake = true; return new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload); } // Processing for possible fragmented messages for text and binary // frames String aggregatedText = null; if (frameFinalFlag) { // Final frame of the sequence. Apparently ping frames are // allowed in the middle of a fragmented message if (frameOpcode != OPCODE_PING) { fragmentedFramesCount = 0; // Check text for UTF8 correctness if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) { // Check UTF-8 correctness for this payload checkUTF8String(ctx, framePayload); // This does a second check to make sure UTF-8 // correctness for entire text message aggregatedText = fragmentedFramesText.toString(); fragmentedFramesText = null; } } } else { // Not final frame so we can expect more frames in the // fragmented sequence if (fragmentedFramesCount == 0) { // First text or binary frame for a fragmented set fragmentedFramesText = null; if (frameOpcode == OPCODE_TEXT) { checkUTF8String(ctx, framePayload); } } else { // Subsequent frames - only check if init frame is text if (fragmentedFramesText != null) { checkUTF8String(ctx, framePayload); } } // Increment counter fragmentedFramesCount++; } // Return the frame if (frameOpcode == OPCODE_TEXT) { return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload); } else if (frameOpcode == OPCODE_BINARY) { return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload); } else if (frameOpcode == OPCODE_CONT) { return new ContinuationWebSocketFrame( frameFinalFlag, frameRsv, framePayload, aggregatedText); } else { throw new UnsupportedOperationException( "Cannot decode web socket frame with opcode: " + frameOpcode); } case CORRUPT: // If we don't keep reading Netty will throw an exception saying // we can't return null if no bytes read and state not changed. in.readByte(); return null; default: throw new Error("Shouldn't reach here."); } }
/** * Handle the web socket handshake for the web socket specification <a href= * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and * lower. This standard is really a rehash of <a * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and <a * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>. * * <p>Browser request to the server: * * <pre> * GET /demo HTTP/1.1 * Upgrade: WebSocket * Connection: Upgrade * Host: example.com * Origin: http://example.com * Sec-WebSocket-Protocol: chat, sample * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 * * ^n:ds[4U * </pre> * * <p>Server response: * * <pre> * HTTP/1.1 101 WebSocket Protocol Handshake * Upgrade: WebSocket * Connection: Upgrade * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Location: ws://example.com/demo * Sec-WebSocket-Protocol: sample * * 8jKS'y:G*Co,Wxa- * </pre> * * @param channel Channel * @param req HTTP request */ @Override public ChannelFuture handshake(Channel channel, FullHttpRequest req, ChannelPromise promise) { if (logger.isDebugEnabled()) { logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.id())); } // Serve the WebSocket handshake request. if (!Values.UPGRADE.equalsIgnoreCase(req.headers().get(CONNECTION)) || !WEBSOCKET.equalsIgnoreCase(req.headers().get(Names.UPGRADE))) { throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade"); } // Hixie 75 does not contain these headers while Hixie 76 does boolean isHixie76 = req.headers().contains(SEC_WEBSOCKET_KEY1) && req.headers().contains(SEC_WEBSOCKET_KEY2); // Create the WebSocket handshake response. FullHttpResponse res = new DefaultFullHttpResponse( HTTP_1_1, new HttpResponseStatus( 101, isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake")); res.headers().add(Names.UPGRADE, WEBSOCKET); res.headers().add(CONNECTION, Values.UPGRADE); // Fill in the headers and contents depending on handshake getMethod. if (isHixie76) { // New handshake getMethod with a challenge: res.headers().add(SEC_WEBSOCKET_ORIGIN, req.headers().get(ORIGIN)); res.headers().add(SEC_WEBSOCKET_LOCATION, uri()); String subprotocols = req.headers().get(SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { throw new WebSocketHandshakeException( "Requested subprotocol(s) not supported: " + subprotocols); } else { res.headers().add(SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); setSelectedSubprotocol(selectedSubprotocol); } } // Calculate the answer of the challenge. String key1 = req.headers().get(SEC_WEBSOCKET_KEY1); String key2 = req.headers().get(SEC_WEBSOCKET_KEY2); int a = (int) (Long.parseLong(BEGINNING_DIGIT.matcher(key1).replaceAll("")) / BEGINNING_SPACE.matcher(key1).replaceAll("").length()); int b = (int) (Long.parseLong(BEGINNING_DIGIT.matcher(key2).replaceAll("")) / BEGINNING_SPACE.matcher(key2).replaceAll("").length()); long c = req.data().readLong(); ByteBuf input = Unpooled.buffer(16); input.writeInt(a); input.writeInt(b); input.writeLong(c); res.data().writeBytes(WebSocketUtil.md5(input.array())); } else { // Old Hixie 75 handshake getMethod with no challenge: res.headers().add(WEBSOCKET_ORIGIN, req.headers().get(ORIGIN)); res.headers().add(WEBSOCKET_LOCATION, uri()); String protocol = req.headers().get(WEBSOCKET_PROTOCOL); if (protocol != null) { res.headers().add(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); } } // Upgrade the connection and send the handshake response. channel.write(res, promise); promise.addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { ChannelPipeline p = future.channel().pipeline(); if (p.get(HttpObjectAggregator.class) != null) { p.remove(HttpObjectAggregator.class); } p.replaceAndForward( HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder(maxFramePayloadLength())); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder()); } }); return promise; }