-
Notifications
You must be signed in to change notification settings - Fork 94
feat: Support MSSQL packets #1533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 38 commits
6dc1f66
da3ca69
6d107d6
9324bbe
1d879d1
8d491b9
4a49bfe
030bf97
ff02e18
87e8be5
dd817d6
d7b7fec
52f63f1
636762d
94e44c3
aa598a4
4d537ef
f519a85
4ede158
4daf722
922572b
afbfa36
47a6231
dac879f
2ffaf13
3c47303
692947b
de76d6f
887da01
3c6057f
4121611
0c880fe
d283e7e
3327bc2
18a86bd
64f637e
39cf71d
3fd2aa8
0f8e3c9
0a868e5
3add9cb
a033a37
85d0306
266e2d6
c9d4dda
375fab2
cb91f4e
d54fe0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| 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); | ||
|
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; | ||
|
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, | ||
|
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); | ||
|
Orenico10 marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. Maybe it would be cleaner to have someething like |
||
| 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; | ||
| } | ||
|
Orenico10 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return 0; | ||
| } | ||
| 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"] |
| 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"] |
Uh oh!
There was an error while loading. Please reload this page.