bpf/include/protocol_analyzer.h (139 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#define CONNECTION_PROTOCOL_UNKNOWN 0
#define CONNECTION_PROTOCOL_HTTP1 1
#define CONNECTION_PROTOCOL_HTTP2 2
#define CONNECTION_MESSAGE_TYPE_UNKNOWN 0
#define CONNECTION_MESSAGE_TYPE_REQUEST 1
#define CONNECTION_MESSAGE_TYPE_RESPONSE 2
// HTTP 1.x
// request frame format: https://www.rfc-editor.org/rfc/rfc2068.html#section-5
// response frame format: https://www.rfc-editor.org/rfc/rfc2068.html#section-6
static __inline __u32 infer_http1_message(const char* buf, size_t count) {
if (count < 16) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
// response
if (buf[0] == 'H' && buf[1] == 'T' && buf[2] == 'T' && buf[3] == 'P') {
return CONNECTION_MESSAGE_TYPE_RESPONSE;
}
// request
if (buf[0] == 'G' && buf[1] == 'E' && buf[2] == 'T') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'P' && buf[1] == 'O' && buf[2] == 'S' && buf[3] == 'T') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'O' && buf[1] == 'P' && buf[2] == 'T' && buf[3] == 'I' && buf[4] == 'O'
&& buf[5] == 'N' && buf[6] == 'S')
{
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'H' && buf[1] == 'E' && buf[2] == 'A' && buf[3] == 'D') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'P' && buf[1] == 'U' && buf[2] == 'T') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'D' && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'E' && buf[4] == 'T' && buf[5] == 'E') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'C' && buf[1] == 'O' && buf[2] == 'N' && buf[3] == 'N' && buf[4] == 'E'
&& buf[5] == 'C' && buf[6] == 'T')
{
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'T' && buf[1] == 'R' && buf[2] == 'A' && buf[3] == 'C' && buf[4] == 'E') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
if (buf[0] == 'P' && buf[1] == 'A' && buf[2] == 'T' && buf[3] == 'C' && buf[4] == 'H') {
return CONNECTION_MESSAGE_TYPE_REQUEST;
}
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
static bool is_http2_magic(const char *buf_src, size_t count)
{
static const char magic[] = "PRI * HTTP/2";
char buffer[sizeof(magic)] = { 0 };
bpf_probe_read(buffer, sizeof(buffer) - 1, buf_src);
for (int idx = 0; idx < sizeof(magic); ++idx) {
if (magic[idx] == buffer[idx])
continue;
return false;
}
return true;
}
// HTTP 2.x
// frame format: https://www.rfc-editor.org/rfc/rfc7540.html#section-4.1
static __inline __u32 infer_http2_message(const char* buf, size_t count) {
static const __u8 kFrameBasicSize = 0x9; // including Length, Type, Flags, Reserved, Stream Identity
static const __u8 kFrameTypeHeader = 0x1; // the type of the frame: https://www.rfc-editor.org/rfc/rfc7540.html#section-6.2
static const __u8 kFrameLoopCount = 5;
static const __u8 kStaticTableMaxSize = 61;// https://www.rfc-editor.org/rfc/rfc7541#appendix-A
static const __u8 kStaticTableAuth = 1;
static const __u8 kStaticTableGet = 2;
static const __u8 kStaticTablePost = 3;
static const __u8 kStaticTablePath1 = 4;
static const __u8 kStaticTablePath2 = 5;
// the buffer size must bigger than basic frame size
if (count < kFrameBasicSize) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
// frame info
__u8 frame[21] = { 0 };
__u32 frameOffset = 0;
// header info
__u8 staticInx, headerBlockFragmentOffset;
if (is_http2_magic(buf, count)) {
frameOffset = 24;
}
// each all frame
#pragma unroll
for (__u8 i = 0; i < kFrameLoopCount; i++) {
if (frameOffset >= count) {
break;
}
// read frame
bpf_probe_read(frame, sizeof(frame), buf + frameOffset);
frameOffset += (bpf_ntohl(*(__u32 *) frame) >> 8) + kFrameBasicSize;
// frametype only accept 0x00 - 0x09
if (frame[3] > 0x09) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
// is header frame
if (frame[3] != kFrameTypeHeader) {
continue;
}
// validate the header(unset): not HTTP2 protocol
// this frame must is a send request
if ((frame[4] & 0xd2) || frame[5] & 0x01) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
// stream ID cannot be 0
__u32 streamID = ((frame[5] & 0x7F) << 24) | (frame[6] << 16) | (frame[7] << 8) | frame[8];
if (streamID == 0) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
// locate the header block fragment offset
headerBlockFragmentOffset = kFrameBasicSize;
if (frame[4] & 0x08) { // PADDED flag is set
headerBlockFragmentOffset += 1;
}
if (frame[4] & 0x20) { // PRIORITY flag is set
headerBlockFragmentOffset += 5;
}
#pragma unroll
for (__u8 j = 0; j <= kStaticTablePath2; j++) {
if (headerBlockFragmentOffset > count) {
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
staticInx = frame[headerBlockFragmentOffset] & 0x7f;
if (staticInx <= kStaticTableMaxSize && staticInx > 0) {
if (staticInx == kStaticTableAuth ||
staticInx == kStaticTableGet ||
staticInx == kStaticTablePost ||
staticInx == kStaticTablePath1 ||
staticInx == kStaticTablePath2) {
return CONNECTION_MESSAGE_TYPE_REQUEST;
} else {
return CONNECTION_MESSAGE_TYPE_RESPONSE;
}
}
headerBlockFragmentOffset++;
}
}
return CONNECTION_MESSAGE_TYPE_UNKNOWN;
}
static __inline __u32 analyze_protocol(char *buf, __u32 count, __u8 *protocol_ref) {
__u32 protocol = CONNECTION_PROTOCOL_UNKNOWN, type = CONNECTION_MESSAGE_TYPE_UNKNOWN;
// support http 1.x and 2.x
if ((type = infer_http1_message(buf, count)) != CONNECTION_PROTOCOL_UNKNOWN) {
protocol = CONNECTION_PROTOCOL_HTTP1;
} else if ((type = infer_http2_message(buf, count)) != CONNECTION_PROTOCOL_UNKNOWN) {
protocol = CONNECTION_PROTOCOL_HTTP2;
}
if (protocol != CONNECTION_PROTOCOL_UNKNOWN) {
*protocol_ref = protocol;
}
return type;
}