Exemple #1
0
  /** 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);
  }
  /**
   * 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;
  }
Exemple #3
0
  @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();
    }
  }
Exemple #4
0
  @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;
  }