Skip to content

contrib/mysql: add classic protocol support#4967

Draft
pablogonzalezpe wants to merge 3 commits intosecdev:masterfrom
pablogonzalezpe:contrib-mysql
Draft

contrib/mysql: add classic protocol support#4967
pablogonzalezpe wants to merge 3 commits intosecdev:masterfrom
pablogonzalezpe:contrib-mysql

Conversation

@pablogonzalezpe
Copy link
Copy Markdown

Summary

This PR adds a new scapy.contrib.mysql module implementing support for the MySQL classic protocol over TCP.

The current scope covers a first usable subset of the protocol for dissection and packet building, including:

  • packet framing (payload_length + sequence_id)
  • Protocol::HandshakeV10
  • Protocol::SSLRequest
  • Protocol::HandshakeResponse41
  • OldAuthSwitchRequest
  • AuthSwitchRequest
  • AuthSwitchResponse
  • AuthMoreData
  • OK_Packet
  • ERR_Packet
  • EOF_Packet
  • COM_QUERY
  • COM_STMT_PREPARE_OK
  • text resultsets:
    • column count
    • column definitions
    • text rows
    • EOF / OK terminators
  • prepared statement metadata flows
  • some legacy field-list style flows seen in real captures

Regression tests are added in test/contrib/mysql.uts.

Scope

This PR is intentionally limited to a usable MVP for the classic protocol.

Not implemented in this first version:

  • TLS-encrypted MySQL payloads after SSLRequest
  • compression
  • binary resultsets
  • full command coverage
  • full authentication plugin coverage
  • full prepared statement execution support

Validation

UTScapy

Added regression tests for:

  • handshake packets
  • auth switch and auth more data packets
  • OK / ERR / EOF packets
  • COM_QUERY
  • text resultsets
  • resultsets with multiple columns and NULL
  • resultsets terminated with OK when CLIENT_DEPRECATE_EOF applies
  • COM_STMT_PREPARE_OK metadata flows
  • legacy field-list style flows
  • multi-packet streams

Result:

  • UTScapy: 15/15 passing

Lint / typing

Validated locally with:

  • tox -e flake8 on Windows: OK
  • tox -e flake8 on WSL Ubuntu / Python 3.9: OK
  • tox -e mypy on Windows: OK

On WSL Ubuntu / Python 3.9, tox -e mypy reports existing repository-level typing issues outside this module.

Real PCAP validation

The contrib was also tested against these public MySQL captures:

  1. umitproject/packet-manipulator/audits/pcap-tests/mysql.pcap
  2. colinnewell/pcap2mysql-log/test/captures/big-data.pcap
  3. arkime/tests/pcap/mysql-allow.pcap

In local validation, the MySQL messages in these 3 captures were successfully parsed with the current implementation.

Notes

This PR follows the earlier discussion in #4954.

I tried to keep names close to the MySQL protocol naming where possible, while avoiding collisions with Scapy internals when needed.

Feedback is welcome on:

  • whether this is the right MVP boundary for scapy.contrib
  • whether any naming should be adjusted further
  • whether prepared statement metadata support should stay in this PR or be split out

Comment thread scapy/contrib/mysql.py
return repr(val)


class MySQLCapabilityFlagsField(LEIntField):
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.

Is this "redefinition" of a FlagsField really necessary?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The goal here was not to redefine FlagsField semantics, but to keep this serialized as a regular little-endian integer while providing a readable representation in show().

I used this small wrapper because MySQL capability flags are 32-bit little-endian values and the current code keeps the raw integer behavior unchanged. If you prefer using a native FlagsField pattern here, I can adjust it.

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.

A standard FlagsField can provide LE representation:

class FlagsField(_BitField[Optional[Union[int, FlagValue]]]):
    """ Handle Flag type field
   ...
   ...

   :param name: field's name
   :param default: default value for the field
   :param size: number of bits in the field (in bits). if negative, LE                   <-----
   :param names: (list or str or dict) label for each flag
       If it's a str or a list, the least Significant Bit tag's name
       is written first. 

Comment thread scapy/contrib/mysql.py
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 81.45833% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.33%. Comparing base (66ef96a) to head (43288ea).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
scapy/contrib/mysql.py 81.45% 89 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4967      +/-   ##
==========================================
+ Coverage   80.31%   80.33%   +0.01%     
==========================================
  Files         381      382       +1     
  Lines       93630    94110     +480     
==========================================
+ Hits        75202    75600     +398     
- Misses      18428    18510      +82     
Files with missing lines Coverage Δ
scapy/contrib/mysql.py 81.45% <81.45%> (ø)

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread scapy/contrib/mysql.py Outdated
"""
Authentication response encoding depends on client capabilities.

- CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: string<lenenc>
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.

It's not the scapy way to introduce a new Field for such a handling. More correct would be to use "ConditionalFields" or MultipleTypeFields

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That is fair, thanks. I refactored this part to use MultipleTypeField in MySQLHandshakeResponse41 instead of a dedicated MySQLAuthResponseField.

The auth response still supports the same three encodings (lenenc, 1-byte length, and NUL-terminated), but the field selection is now expressed in a way that is closer to Scapy conventions.

I also re-ran the MySQL UTScapy tests as well as flake8 and mypy after the change.

Comment thread scapy/contrib/mysql.py
@polybassa
Copy link
Copy Markdown
Contributor

Please try to increase the test coverage, since this looks like AI-generated code.

@pablogonzalezpe
Copy link
Copy Markdown
Author

Thanks, that is fair feedback.

I will add more tests to exercise the currently uncovered branches in \scapy.contrib.mysql. The current version already includes UTScapy regression tests and validation against real MySQL pcaps, but I agree that the patch coverage can be improved further.

@pablogonzalezpe
Copy link
Copy Markdown
Author

I added more UTScapy tests to exercise uncovered branches, including auth-response variants, helper edge cases, fallback/error paths, and incomplete stream reassembly.

Comment thread scapy/contrib/mysql.py
return _flag_repr(int(val), MYSQL_CLIENT_FLAGS)


class MySQLStatusFlagsField(LEShortField):
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.

same here, please reuse existing fields.

Comment thread scapy/contrib/mysql.py
return bool(flags & mask)


def _flag_repr(value: int, mapping: Any) -> str:
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.

This function is already part of FlagsField.

remove it

Comment thread scapy/contrib/mysql.py
return _flag_repr(int(val), MYSQL_STATUS_FLAGS)


class MySQLCharsetField(ByteEnumField):
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.

Please use existing Field definitions, if the derived class is not providing "new" functionality.

Comment thread scapy/contrib/mysql.py
LEShortEnumField.__init__(self, name, default, MYSQL_CHARACTER_SETS)


class MySQLColumnFlagsField(LEShortField):
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.

Use FlagsField.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants