Skip to content
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6dc1f66
Added some eBPF C code for parsing MSSQL
Orenico10 Feb 17, 2026
da3ca69
Added some of the code needed for supporting mssql
Orenico10 Feb 23, 2026
6d107d6
Fixed mssql detection
Orenico10 Feb 23, 2026
9324bbe
Minor changes
Orenico10 Mar 10, 2026
1d879d1
Added tests
Orenico10 Mar 11, 2026
8d491b9
Added feature doc
Orenico10 Mar 11, 2026
4a49bfe
Merge branch 'main' into feature/mssql-support
Orenico10 Mar 11, 2026
030bf97
Fixed typos
Orenico10 Mar 11, 2026
ff02e18
Adapted to the new largebuf + added some constants where needed
Orenico10 Mar 11, 2026
87e8be5
Reworked ucs2ToUTF8 to improve performance
Orenico10 Mar 12, 2026
dd817d6
Fixed TestUCS2ToUTF8 to work with []bytes instead of string
Orenico10 Mar 12, 2026
d7b7fec
Removed misleading comment from mssql_send_large_buffer
Orenico10 Mar 12, 2026
52f63f1
Updated config schema
Orenico10 Mar 12, 2026
636762d
Fixed linting on sqlparser_test.go
Orenico10 Mar 12, 2026
94e44c3
Refactored to use the new largebuf API + Fixed the tests + Clarified …
Orenico10 Mar 12, 2026
aa598a4
Fixed linting in sql_detect_mssql.go
Orenico10 Mar 12, 2026
4d537ef
Merge branch 'main' into feature/mssql-support
Orenico10 Mar 12, 2026
f519a85
added mssql integration tests
Orenico10 Mar 13, 2026
4ede158
Fixed error parsing for mssql
Orenico10 Mar 24, 2026
4daf722
Reworked the procotol_mssql and fixed the unittest
Orenico10 Mar 25, 2026
922572b
Merge branch 'main' into feature/mssql-support
Orenico10 Mar 25, 2026
afbfa36
Fixed the code after merging main
Orenico10 Mar 25, 2026
47a6231
Merge remote-tracking branch 'origin/main' into feature/mssql-support
Orenico10 Apr 4, 2026
dac879f
Update internal/test/integration/components/pythonsql/main_mssql.py
Orenico10 Apr 4, 2026
2ffaf13
Update pkg/config/ebpf_tracer.go
Orenico10 Apr 4, 2026
3c47303
Merge remote-tracking branch 'Orenico10/feature/mssql-support' into f…
Orenico10 Apr 4, 2026
692947b
Reworked the code to comply with the CR comments
Orenico10 Apr 4, 2026
de76d6f
Generated config schema
Orenico10 Apr 4, 2026
887da01
Update pkg/internal/sqlprune/mssql.go
Orenico10 Apr 4, 2026
3c6057f
Reworeded the comment to make it more clear
Orenico10 Apr 4, 2026
4121611
Reverted the formatting changes
Orenico10 Apr 4, 2026
0c880fe
Update bpf/generictracer/protocol_tcp.h
Orenico10 Apr 4, 2026
d283e7e
Undid clang-format changes
Orenico10 Apr 7, 2026
3327bc2
Fixed protocol_tcp.h
Orenico10 Apr 9, 2026
18a86bd
Merge remote-tracking branch 'origin/main' into feature/mssql-support
Orenico10 Apr 9, 2026
64f637e
Fixed tcp_detect_transform.go to work with the new changes
Orenico10 Apr 9, 2026
39cf71d
removed the overriding large_buf->action
Orenico10 Apr 13, 2026
3fd2aa8
Fixed protocol_tcp.h's sending
Orenico10 Apr 13, 2026
0f8e3c9
Simplified and added a test for mssql error detection
Orenico10 Apr 16, 2026
0a868e5
Merge remote-tracking branch 'origin/main' into feature/mssql-support
Orenico10 Apr 16, 2026
3add9cb
Modified protocol_handler.c to include mssql
Orenico10 Apr 16, 2026
a033a37
Fixed all of the CR comments
Orenico10 Apr 17, 2026
85d0306
Added more test cases and fixed the broken tests
Orenico10 Apr 18, 2026
266e2d6
Fixed CR comment
Orenico10 Apr 18, 2026
c9d4dda
Fixed Copilot's CR comments
Orenico10 Apr 18, 2026
375fab2
Fixed again
Orenico10 Apr 18, 2026
cb91f4e
Merge branch 'open-telemetry:main' into feature/mssql-support
Orenico10 Apr 19, 2026
d54fe0d
Added mssql to SUPPORT_MATRIX.md
Orenico10 Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bpf/common/connection_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum protocol_type : u8 {
k_protocol_type_http = 3,
k_protocol_type_kafka = 4,
k_protocol_type_mqtt = 5,
k_protocol_type_mssql = 6,
};

// Struct to keep information on the connections in flight
Expand Down
2 changes: 2 additions & 0 deletions bpf/common/large_buffers.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ volatile const u32 http_max_captured_bytes = 0;
volatile const u32 mysql_max_captured_bytes = 0;
volatile const u32 postgres_max_captured_bytes = 0;
volatile const u32 kafka_max_captured_bytes = 0;
volatile const u32 mssql_max_captured_bytes = 0;

enum {
// Maximum payload size per ring buffer chunk.
Expand All @@ -26,6 +27,7 @@ enum {
k_large_buf_max_mysql_captured_bytes = 1 << 16,
k_large_buf_max_postgres_captured_bytes = 1 << 16,
k_large_buf_max_kafka_captured_bytes = 1 << 16,
k_large_buf_max_mssql_captured_bytes = 1 << 16,
};

SCRATCH_MEM_SIZED(tcp_large_buffers, k_large_buf_max_size);
7 changes: 7 additions & 0 deletions bpf/generictracer/k_tracer.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <generictracer/protocol_http2.h>
#include <generictracer/protocol_mysql.h>
#include <generictracer/protocol_postgres.h>
#include <generictracer/protocol_mssql.h>
#include <generictracer/protocol_tcp.h>
#include <generictracer/ssl_defs.h>

Expand Down Expand Up @@ -1415,6 +1416,12 @@ int obi_handle_buf_with_args(void *ctx) {
&args->protocol_type)) {
bpf_dbg_printk("Found postgres connection");
bpf_tail_call(ctx, &jump_table, k_tail_protocol_tcp);
} else if (is_mssql(&args->pid_conn.conn,
(const unsigned char *)args->u_buf,
args->bytes_len,
&args->protocol_type)) {
bpf_dbg_printk("Found mssql connection");
bpf_tail_call(ctx, &jump_table, k_tail_protocol_tcp);
} else if (is_kafka(&args->pid_conn.conn,
(const unsigned char *)args->u_buf,
args->bytes_len,
Expand Down
176 changes: 176 additions & 0 deletions bpf/generictracer/protocol_mssql.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <logger/bpf_dbg.h>
#include <bpfcore/vmlinux.h>
#include <bpfcore/bpf_endian.h>
#include <bpfcore/bpf_helpers.h>
#include <bpfcore/utils.h>

#include <common/common.h>
#include <common/connection_info.h>
#include <common/large_buffers.h>
#include <common/ringbuf.h>

#include <generictracer/maps/protocol_cache.h>
#include <generictracer/protocol_common.h>

// TDS Packet Header
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/7af53667-1b72-4703-8258-7984e838f746
struct mssql_hdr {
u8 type;
u8 status;
u16 length; // big-endian
u16 spid; // big-endian
u8 packet_id;
u8 window;
};

enum {
// TDS header
k_mssql_hdr_size = 8,
Comment thread
rafaelroquetto marked this conversation as resolved.
k_mssql_messages_in_packet_max = 10,

// TDS message types
k_mssql_msg_sql_batch = 0x01,
k_mssql_msg_rpc = 0x03,
k_mssql_msg_response = 0x04,
k_mssql_msg_login7 = 0x10,
k_mssql_msg_prelogin = 0x12,
};

static __always_inline struct mssql_hdr mssql_parse_hdr(const unsigned char *data) {
struct mssql_hdr hdr = {};

bpf_probe_read(&hdr, sizeof(hdr), (const void *)data);
Comment thread
Orenico10 marked this conversation as resolved.

// Length and SPID are big-endian
hdr.length = bpf_ntohs(hdr.length);
hdr.spid = bpf_ntohs(hdr.spid);

return hdr;
Comment thread
Orenico10 marked this conversation as resolved.
}

static __always_inline u8 is_mssql(connection_info_t *conn_info,
const unsigned char *data,
u32 data_len,
enum protocol_type *protocol_type) {
if (*protocol_type != k_protocol_type_mssql && *protocol_type != k_protocol_type_unknown) {
// Already classified, not mssql.
return 0;
}

if (data_len < k_mssql_hdr_size) {
return 0;
}

size_t offset = 0;
bool includes_known_command = false;

for (u8 i = 0; i < k_mssql_messages_in_packet_max; i++) {
if (offset + k_mssql_hdr_size > data_len) {
break;
}

struct mssql_hdr hdr = mssql_parse_hdr(data + offset);

if (hdr.length < k_mssql_hdr_size || hdr.length > data_len - offset) {
return 0;
}

switch (hdr.type) {
case k_mssql_msg_sql_batch:
case k_mssql_msg_rpc:
case k_mssql_msg_response:
case k_mssql_msg_login7:
case k_mssql_msg_prelogin:
includes_known_command = true;
break;
default:
break;
}

offset += hdr.length;
}

if (offset != data_len || !includes_known_command) {
return 0;
}

*protocol_type = k_protocol_type_mssql;
bpf_map_update_elem(&protocol_cache, conn_info, protocol_type, BPF_ANY);

return 1;
}

// Emit a large buffer event for MSSQL protocol.
static __always_inline int mssql_send_large_buffer(tcp_req_t *req,
Comment thread
rafaelroquetto marked this conversation as resolved.
const void *u_buf,
u32 bytes_len,
u8 packet_type,
u8 direction,
enum large_buf_action action) {
u32 expected_len = 0;

if (packet_type == PACKET_TYPE_RESPONSE) {
// Response Phase
if (req->resp_len > 0) {
// The first part of the response (including the header) is already in req->rbuf
struct mssql_hdr hdr = mssql_parse_hdr(req->rbuf);
expected_len = hdr.length;
} else if (bytes_len >= k_mssql_hdr_size) {
struct mssql_hdr hdr = mssql_parse_hdr(u_buf);
expected_len = hdr.length;
bpf_dbg_printk("mssql response: expected_len=%d", hdr.length);

// Copy the first response packet, which contains the TDS header and error token for userspace parsing
bpf_probe_read(req->rbuf, k_tcp_res_len, u_buf);
Comment thread
Orenico10 marked this conversation as resolved.
Outdated
}
}
Comment thread
Orenico10 marked this conversation as resolved.

// these are the bytes already sent so far
u32 *bytes_sent_ptr =
(packet_type == PACKET_TYPE_REQUEST) ? &req->lb_req_bytes : &req->lb_res_bytes;
const u32 bytes_sent = *bytes_sent_ptr;

if (mssql_max_captured_bytes > 0 && bytes_sent < mssql_max_captured_bytes && bytes_len > 0) {
tcp_large_buffer_t *large_buf = (tcp_large_buffer_t *)tcp_large_buffers_mem();
if (!large_buf) {
if (packet_type == PACKET_TYPE_RESPONSE) {
bpf_dbg_printk(
"mssql_send_large_buffer: failed to reserve space for MSSQL large buffer");
}
} else {
large_buf->type = EVENT_TCP_LARGE_BUFFER;
large_buf->packet_type = packet_type;
large_buf->action = action;
large_buf->direction = direction;
large_buf->conn_info = req->conn_info;
large_buf->tp = req->tp;

u32 max_available_bytes = mssql_max_captured_bytes - bytes_sent;
bpf_clamp_umax(max_available_bytes, k_large_buf_max_mssql_captured_bytes);

u32 available_bytes = min(bytes_len, max_available_bytes);
const u32 consumed_bytes = large_buf_emit_chunks(large_buf, u_buf, available_bytes);

if (consumed_bytes > 0) {
req->has_large_buffers = true;
}

*bytes_sent_ptr += consumed_bytes;
}
}

if (packet_type == PACKET_TYPE_RESPONSE) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this is the best place for this - this seems to be detecting the end of a message, even when large buffers are not enabled for this protocol (i.e. mssql_max_captured_byted == 0). In this scenario, no buffer will ever be sent but yet this function is performing unrelated work for every packet.

Maybe it would be cleaner to have someething like mssql_respnse_complete() instead, to be called from handle_unknown_tcp_connection() alongside the large buffer call, as it is somewhat unrelated.

req->resp_len += bytes_len;
if (expected_len > 0 && req->resp_len < expected_len) {
bpf_dbg_printk("mssql response: partial, acc=%d exp=%d", req->resp_len, expected_len);
return -1;
}
Comment thread
Orenico10 marked this conversation as resolved.
}

return 0;
}
15 changes: 8 additions & 7 deletions bpf/generictracer/protocol_tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <generictracer/protocol_kafka.h>
#include <generictracer/protocol_mysql.h>
#include <generictracer/protocol_postgres.h>
#include <generictracer/protocol_mssql.h>

#include <generictracer/maps/tcp_req_mem.h>

Expand Down Expand Up @@ -142,6 +143,8 @@ static __always_inline int tcp_send_large_buffer(tcp_req_t *req,
return postgres_send_large_buffer(req, u_buf, bytes_len, packet_type, direction, action);
case k_protocol_type_kafka:
return kafka_send_large_buffer(req, pid_conn, u_buf, bytes_len, direction, action);
case k_protocol_type_mssql:
return mssql_send_large_buffer(req, u_buf, bytes_len, packet_type, direction, action);
case k_protocol_type_http:
case k_protocol_type_mqtt:
case k_protocol_type_unknown:
Expand Down Expand Up @@ -266,13 +269,11 @@ static __always_inline void handle_unknown_tcp_connection(pid_connection_info_t
}
} else if (existing->direction != direction) {
existing->is_server = is_server;
if (tcp_send_large_buffer(existing,
pid_conn,
u_buf,
bytes_len,
direction,
protocol_type,
k_large_buf_action_init) < 0) {
const enum large_buf_action response_action =
(existing->lb_res_bytes > 0) ? k_large_buf_action_append : k_large_buf_action_init;
Comment thread
mmat11 marked this conversation as resolved.
if (tcp_send_large_buffer(
existing, pid_conn, u_buf, bytes_len, direction, protocol_type, response_action) <
0) {
bpf_dbg_printk("waiting additional response data");
return;
}
Expand Down
4 changes: 3 additions & 1 deletion devdocs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
| gRPC | All | 1.0+ | All | Yes | No | Can't get method for long living connections before OBI started, will mark method with `*`
| MySQL | All | All | All | Yes | No | In the case of prepared statements, if the statement was prepared before OBI started then the query might be missed
| PostgreSQL | All | All | All | Yes | No | In the case of prepared statements, if the statement was prepared before OBI started then the query might be missed
| MSSQL | All | All | All | Yes | No | In the case of prepared statements, if the statement was prepared before OBI started then the query might be missed
| Redis | All | All | All | Yes | No | For already started connections, can't infer the number of the database, and won't add the `db.namespace` attribute
| MongoDB | All | 5.0+ | insert, update, find, delete, findAndModify, aggregate, count, distinct, mapReduce | Yes | No | no support for compressed payloads
| Couchbase | All | All | All | Yes | No | Bucket unknown if SELECT_BUCKET occurred before OBI started; Collection unknown if GET_COLLECTION_ID occurred before OBI started
Expand Down Expand Up @@ -61,8 +62,9 @@ Large payloads are streamed to userspace across multiple ring-buffer events and
| `OTEL_EBPF_BPF_BUFFER_SIZE_MYSQL` | MySQL | 65535 | 0 (disabled) |
| `OTEL_EBPF_BPF_BUFFER_SIZE_KAFKA` | Kafka | 65535 | 0 (disabled) |
| `OTEL_EBPF_BPF_BUFFER_SIZE_POSTGRES` | PostgreSQL | 65535 | 0 (disabled) |
| `OTEL_EBPF_BPF_BUFFER_SIZE_MSSQL` | MSSQL | 65535 | 0 (disabled) |

Equivalent YAML keys live under `ebpf.buffer_sizes.{http,mysql,kafka,postgres}`.
Equivalent YAML keys live under `ebpf.buffer_sizes.{http,mysql,kafka,postgres,mssql}`.

## GPU Instrumentation

Expand Down
9 changes: 9 additions & 0 deletions docs/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@
"type": "integer",
"x-env-var": "OTEL_EBPF_BPF_BUFFER_SIZE_KAFKA"
},
"mssql": {
"type": "integer",
"x-env-var": "OTEL_EBPF_BPF_BUFFER_SIZE_MSSQL"
},
"mysql": {
"type": "integer",
"x-env-var": "OTEL_EBPF_BPF_BUFFER_SIZE_MYSQL"
Expand Down Expand Up @@ -443,6 +447,11 @@
"description": "MongoDB requests cache size.",
"x-env-var": "OTEL_EBPF_BPF_MONGO_REQUESTS_CACHE_SIZE"
},
"mssql_prepared_statements_cache_size": {
"type": "integer",
Comment thread
Orenico10 marked this conversation as resolved.
"description": "MSSQL prepared statements cache size.",
"x-env-var": "OTEL_EBPF_BPF_MSSQL_PREPARED_STATEMENTS_CACHE_SIZE"
},
"mysql_prepared_statements_cache_size": {
"type": "integer",
"description": "MySQL prepared statements cache size.",
Expand Down
24 changes: 24 additions & 0 deletions internal/test/integration/components/mssqldb/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/mssql/server@sha256:49b45a911dc535e9345fbfd7101a1bd8a1e190a5f29b877ef75387a061e5fcf0

USER root

# Create a directory for our initialization
COPY setup.sql /setup.sql
RUN chown mssql /setup.sql

# Set required environment variables for the build-time run
ENV ACCEPT_EULA=Y
ENV MSSQL_SA_PASSWORD=p_ssW0rd

# Switch to mssql user for the initialization run
USER mssql

# Start SQL Server, wait for it to be ready, run the setup script, and then shut down.
RUN /opt/mssql/bin/sqlservr & \
bash -c "until /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' &> /dev/null; do sleep 2; echo 'Waiting for SQL Server...'; done" && \
/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$MSSQL_SA_PASSWORD" -C -i /setup.sql && \
pkill sqlservr && \
sleep 5

# Use the default entrypoint
ENTRYPOINT ["/opt/mssql/bin/sqlservr"]
12 changes: 12 additions & 0 deletions internal/test/integration/components/mssqldb/setup.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE DATABASE testdb;
GO
USE testdb;
GO
CREATE TABLE actor (
actor_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50)
);
GO
INSERT INTO actor (actor_id, first_name, last_name) VALUES (1, 'TOM', 'CRUISE');
GO
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Dockerfile that will build a container that runs python with FastAPI and uvicorn on port 8080
FROM python:3.14@sha256:61346539f7b26521a230e72c11da5ebd872924745074b19736e7d65ba748c366
EXPOSE 8080
COPY requirements.txt /requirements.txt
RUN pip install --require-hashes -r /requirements.txt
COPY main_mssql.py /main.py
CMD ["uvicorn", "--port", "8080", "--host", "0.0.0.0", "main:app"]
Loading