Skip to content

feat: S28.11 FreeRtosLwip TLS/mTLS (mbedTLS over LwipRawTcpStream)#481

Merged
DavidCozens merged 3 commits into
mainfrom
feat/s28-11-freertoslwip-tls
May 30, 2026
Merged

feat: S28.11 FreeRtosLwip TLS/mTLS (mbedTLS over LwipRawTcpStream)#481
DavidCozens merged 3 commits into
mainfrom
feat/s28-11-freertoslwip-tls

Conversation

@DavidCozens

@DavidCozens DavidCozens commented May 29, 2026

Copy link
Copy Markdown
Owner

Purpose

Closes #474 (E28). Wire a TLS/mTLS sender into the FreeRTOS + lwIP BDD target so the lwIP backend reaches the syslog-ng oracle over RFC 5425 TLS, completing the UDP (S28.09) / TCP (S28.10) / TLS (S28.11) progression — plus the CI plumbing to make the lwIP lanes required.

Draft / WIP. Plain TLS passes; mutual TLS is still failing server-side and is under active investigation (see Test Evidence). Opened as a draft to get CI + CodeRabbit signal in parallel. The bdd-freertos-qemu-lwip check is expected RED until mTLS lands.

Change Description

  • TLS/mTLS senderBdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c: a transport-swap clone of the +TCP ..._PlusTcpTcp.c (mbedTLS over a second SolidSyslogLwipRawTcpStream, SolidSyslogLwipRawAddress). All mbedTLS init (entropy/DRBG/PSA, baked PEMs, tls/mtls endpoint dispatcher) is transport-agnostic and copied verbatim.
  • CMake — mbedTLS subproject + PEM-baking blocks (mirroring Bdd/Targets/FreeRtos/CMakeLists.txt); new FreeRtosLwip/mbedtls_user_config.h. main.c wires the TLS slot, bumps the interactive task stack for mbedTLS init, and pins the TLS/mTLS host.
  • Transport fix 1 — host/SNI (silent bug). BddTargetTlsConfig defaults the host to "syslog-ng" (a DNS name). The numeric-only SolidSyslogLwipRawResolver rejected it and the sender never connected, with no error logged. Fixed in main.c by pinning host 10.0.2.2 + ServerName="syslog-ng" (mirrors the +TCP target, whose PlusTcpResolver ignores the host).
  • Transport fix 2 — Nagle (Tier-2). Packet capture proved a mid-handshake send-stall: the client cert flight was accepted by tcp_write but never put on the wire (Nagle holding sub-MSS segments). tcp_nagle_disable() on the pcb fixes it; TCP_NODELAY is the correct default for this small-record request/response workload.
  • CI — lwIP BDD lane opened to @tls/@mtls; new analyze-tidy-freertos-lwip / analyze-iwyu-freertos-lwip lanes; build-freertos-target-lwip, bdd-freertos-qemu-lwip, and both analyze-lwip lanes promoted into summary.needs.
  • Provisional — the lwipopts.h pbuf/segment/window bump (48/48/32, 6×MSS) was made while chasing the mTLS failure; packet capture later showed it is not the cause (RX queue peaks at 2, no ERR_MEM). Kept for now, to be trimmed to the minimum once mTLS is root-caused.

Test Evidence

  • Tests/Lwip 57 tests green, incl. new OpenDisablesNagleOnPcb (asserts TF_NODELAY on the pcb after Open).
  • Cross ELF builds clean under the strict bar; no PlusTcp symbols; mbedTLS + TLS-sender symbols present.
  • Plain TLS over lwIP: PASS on the QEMU oracle (tls_transport) — encrypted record delivered, ACK'd, logged.
  • mTLS over lwIP: FAIL (unsolved). Full mutual-auth handshake completes; syslog-ng validates the client cert (CN=solidsyslog-bdd-client, chains to the trusted CA, no error/RST) and ACKs the app-data record — but never logs it. Ruled out: heap (53 KB free), send ERR_MEM, RX pbuf exhaustion, 0.5-RTT timing, cert rejection. Both transports negotiate TLS 1.3 (oracle disables ≤1.2); the +TCP target passes the same 6515 listener, so the difference is lwIP's on-wire segmentation vs the server's 1-RTT app-data handling. Next: server-side TLS-keylog decrypt + strace of syslog-ng's SSL_read.

Areas Affected

  • Bdd/Targets/FreeRtosLwip/, Bdd/Targets/Common/ (Tier 3 BDD targets).
  • Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c (Tier 2 — Nagle disable; benefits every lwIP integrator).
  • .github/workflows/ci.yml, ci/docker-compose.bdd.yml.
  • Branch protection: the four lwIP checks are now in summary.needs; adding them to the GitHub required-checks set is a separate manual step.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added TLS and mutual TLS (mTLS) encryption support for FreeRTOS LWIP deployments
  • Bug Fixes

    • Fixed TCP stream latency issues by disabling Nagle's algorithm
    • Improved memory allocation overflow protection
  • Tests

    • Added test coverage for TCP stream configuration verification
  • Chores

    • Enhanced CI pipeline with additional static analysis lanes for LWIP builds
    • Expanded BDD test scenarios to include TLS/mTLS coverage

Review Change Stack

Wire a TLS/mTLS sender into the FreeRTOS+lwIP BDD target: mbedTLS over a
second LwipRawTcpStream in the SwitchingSender's TLS slot, mirroring the
+TCP target. Adds the mbedTLS CMake subproject + baked PEMs, the lwIP
mbedtls_user_config.h, opens the lwIP BDD lane to @tls/@Mtls, adds
analyze-tidy/iwyu-freertos-lwip lanes, and promotes the lwIP jobs into
summary.needs.

Two transport fixes found via packet capture:
- Pin the TLS/mTLS host to the numeric slirp gateway (10.0.2.2) with SNI
  "syslog-ng": the numeric-only LwipRawResolver silently rejected the
  "syslog-ng" hostname, so the sender never connected.
- Disable Nagle on the lwIP TCP pcb (tcp_nagle_disable): Nagle stalled the
  multi-segment TLS handshake flight mid-exchange. TCP_NODELAY is the right
  default for this small-record workload; covered by a new unit test.

Plain TLS passes the oracle. Mutual TLS is still failing server-side
(syslog-ng validates the client cert and ACKs the app-data but does not log
it) and is under investigation — the lwIP BDD lane is RED until that lands.
The lwipopts pbuf/segment/window bump is provisional (not the mTLS cause)
and will be trimmed once mTLS is root-caused. See DEVLOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c15a0e7-c316-4133-9548-7f33ea5aa946

📥 Commits

Reviewing files that changed from the base of the PR and between 04f5913 and b42be5f.

📒 Files selected for processing (3)
  • Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c
  • Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c
  • DEVLOG.md
✅ Files skipped from review due to trivial changes (1)
  • DEVLOG.md

📝 Walkthrough

Walkthrough

This PR finalizes FreeRTOS lwIP TLS/mTLS support by implementing mbedTLS-based TLS and mTLS transport over lwIP Raw TCP, fixing TCP handshake stalls via Nagle disable, integrating mbedTLS cross-compilation with baked certificate headers, wiring endpoint selection in the main target, and promoting all lwIP CI jobs to required status.

Changes

FreeRTOS lwIP TLS/mTLS Implementation

Layer / File(s) Summary
TCP Handshake Optimization: Disable Nagle
Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c, Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp
tcp_nagle_disable(pcb) eliminates latency stalls during multi-segment TLS handshake flights; new test OpenDisablesNagleOnPcb verifies TF_NODELAY is set after stream open.
Memory Allocation Safety
Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c
Pre-multiplication overflow guard nmemb <= SIZE_MAX / size prevents wrap-around before bytes = nmemb * size computation in allocation hook.
mbedTLS Configuration Header and Build Integration
Bdd/Targets/FreeRtosLwip/mbedtls_user_config.h, Bdd/Targets/FreeRtosLwip/CMakeLists.txt
New config header disables platform/filesystem/threading/time features; CMakeLists validates MBEDTLS_DIR, integrates mbedTLS subproject with warnings suppressed, generates baked PEM headers via xxd -i, and wires include/link paths.
TLS Sender: mbedTLS over lwIP Raw TCP
Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c
Layered sender with FreeRTOS memory allocation (pvPortMalloc/vPortFree), PSA external RNG via CTR_DRBG, demo entropy seeding, idempotent EnsureMbedTlsInitialised(), and Create()/Destroy() lifecycle; ignores mtls parameter and uses runtime BddTargetSwitchConfig_IsMtlsMode() dispatcher for endpoint/port selection.
Main Target Wiring: TLS Configuration and Integration
Bdd/Targets/FreeRtosLwip/main.c
TLS/mTLS endpoints pinned to QEMU slirp gateway 10.0.2.2 with "syslog-ng" SNI; interactive task stack increased; real tlsSender created via BddTargetTlsSender_Create() and wired into switching sender array; teardown destroys TLS sender before TCP resources to preserve pool lifecycle.
Test and Documentation Updates
ci/docker-compose.bdd.yml, DEVLOG.md
Tag filter updated to admit @tls/@mtls alongside @udp/@tcp; devlog records S28.11 implementation decisions (transport-swap sender, host/SNI fix, Nagle disable with test reference, lwipopts revert), verification results, and deferred resolve-failure logging.
CI Lanes and Job Promotion
.github/workflows/ci.yml
Adds analyze-tidy-freertos-lwip and analyze-iwyu-freertos-lwip jobs (with clang-19 compiler for IWYU); marks all lwIP jobs required as of S28.11; extends summary job needs; updates quality-monitor config with bdd-freertos-qemu-lwip JUnit pattern.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • DavidCozens/solid-syslog#439: Parent epic for E28 lwIP Raw API support; this PR implements the final TLS/mTLS slice completing the FreeRTOS+lwIP feature set.

Possibly related PRs

  • DavidCozens/solid-syslog#480: Introduces FreeRTOS+lwIP TCP over SolidSyslogLwipRawTcpStream and the Bdd/Targets/FreeRtosLwip target; this PR extends with TLS/mTLS and shares the same TCP stream modification (Nagle disable).
  • DavidCozens/solid-syslog#469: Adjusts existing IWYU CI lanes to advisory; this PR adds new IWYU analysis lane for the lwIP target.
  • DavidCozens/solid-syslog#421: Establishes the TLS/mTLS-by-switching design via BddTargetSwitchConfig_IsMtlsMode() on FreeRTOS+TCP; this PR applies the same dispatch pattern to the lwIP variant.

Poem

🐰 A rabbit hops through TLS lanes so fine,
mbedTLS + lwIP in perfect align,
Nagle's gone — no more stalls, hooray!
Handshakes now dance the lwIP way,
@tls and @mtls — green lights all day! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: S28.11 FreeRtosLwip TLS/mTLS (mbedTLS over LwipRawTcpStream)' directly and clearly summarizes the main change: implementing TLS/mTLS support for the FreeRTOS LwIP target using mbedTLS.
Description check ✅ Passed The PR description comprehensively covers Purpose, Change Description, Test Evidence, and Areas Affected sections from the template, providing thorough context about the implementation, transport fixes, CI updates, and known limitations.
Linked Issues check ✅ Passed The PR addresses all primary objectives from issue #474: TLS implementation with mbedTLS over LwipRawTcpStream is complete and tested (plain TLS passes), new analyze lanes are added and enabled, and five lwIP jobs are integrated into CI with summary.needs (though branch-protection promotion is noted as a separate manual step).
Out of Scope Changes check ✅ Passed All changes are scoped to the TLS/mTLS implementation for FreeRTOS LwIP: TLS sender, CMake integration, transport fixes (Nagle disable, host pinning), unit tests, CI workflow updates, and mbedTLS config. The overflow-protection fix in the PlusTCP sender is justified for parity and is a minor inline safety improvement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/s28-11-freertoslwip-tls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1344 passed)
   🚦   build-freertos-host-tdd-plustcp: 100% successful (✔️ 1652 passed)
   🚦   build-linux-clang: 100% successful (✔️ 1296 passed)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1296 passed)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-linux-mbedtls: 100% successful (✔️ 7 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu-plustcp: 86% successful (✔️ 42 passed, 🙈 7 skipped)
   🚦   bdd-freertos-qemu-lwip: 65% successful (✔️ 32 passed, 🙈 17 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 1149 passed)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1296 passed)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c`:
- Around line 91-110: In FreeRtosMbedTlsCalloc, the multiplication bytes = nmemb
* size can overflow before you detect it; change the logic to check for overflow
before multiplying (e.g., if (size != 0 && nmemb <= SIZE_MAX / size) { bytes =
nmemb * size; ... } else return NULL) so that you validate nmemb and size
against SIZE_MAX (or sizeof(size_t) max) prior to calling pvPortMalloc; update
the function FreeRtosMbedTlsCalloc to perform the overflow guard using nmemb <=
SIZE_MAX / size (and keep the existing zero checks, pvPortMalloc/memset
behavior) and return NULL on overflow.

In `@DEVLOG.md`:
- Around line 72-75: The DEVLOG entry for "Resolve-failure logging" is
contradictory: it declares resolve-failure logging as implemented ("not
deferred") while it sits under the Deferred section; update DEVLOG so status is
unambiguous by either moving the bullet (the text referencing
numeric-resolver-rejects-a-hostname and SolidSyslog_Error) out of the Deferred
section into a non-deferred/Done section, or reword the bullet to explicitly
mark it deferred (e.g., prefix with "Deferred:") and remove the
TDD/implementation phrasing; ensure the text still references SolidSyslog_Error
and the numeric-resolver-rejects-a-hostname path so reviewers can find the
related work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ea5f283-3464-4cf0-b9b5-5d09d1365a25

📥 Commits

Reviewing files that changed from the base of the PR and between ba7f9b1 and 04f5913.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c
  • Bdd/Targets/FreeRtosLwip/CMakeLists.txt
  • Bdd/Targets/FreeRtosLwip/lwipopts.h
  • Bdd/Targets/FreeRtosLwip/main.c
  • Bdd/Targets/FreeRtosLwip/mbedtls_user_config.h
  • DEVLOG.md
  • Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c
  • Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp
  • ci/docker-compose.bdd.yml

Comment thread Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c
Comment thread DEVLOG.md Outdated
- FreeRtosMbedTlsCalloc: guard the nmemb*size multiply with
  nmemb <= SIZE_MAX/size *before* multiplying, instead of the
  post-multiply (bytes/nmemb)==size check. The old form was correct
  (unsigned wrap is well-defined and the division detected it) but the
  check-before-multiply idiom is clearer and analyzer-friendly. Applied to
  both the LwipRaw and PlusTcp mbedTLS senders to keep the clones in sync.
- DEVLOG: reword the resolve-failure-logging item so it no longer reads as
  "not deferred" while sitting under Deferred — it's pending work queued on
  this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1344 passed)
   🚦   build-freertos-host-tdd-plustcp: 100% successful (✔️ 1652 passed)
   🚦   build-linux-clang: 100% successful (✔️ 1296 passed)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1296 passed)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-linux-mbedtls: 100% successful (✔️ 7 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu-plustcp: 86% successful (✔️ 42 passed, 🙈 7 skipped)
   🚦   bdd-freertos-qemu-lwip: 65% successful (✔️ 32 passed, 🙈 17 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 1149 passed)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1296 passed)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

…6/16 + 4xMSS

The mTLS-over-lwIP failure recorded during S28.11 was a measurement artifact
(rm-while-open in the manual probe loop destroying the file syslog-ng held
open), not a defect. The real fix was the already-committed tcp_nagle_disable.
Re-tested at main's original lwipopts sizing: mTLS delivers 5/5 manually and
the full lwIP BDD lane (@tls, @Mtls, TLS 1.3, TCP->TLS switch) is green, so the
provisional pbuf/segment/window bump is reverted. DEVLOG updated with the
root-cause correction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DavidCozens DavidCozens marked this pull request as ready for review May 30, 2026 07:45
@DavidCozens

Copy link
Copy Markdown
Owner Author

Lifted out of draft — TLS and mTLS over lwIP are green

The mTLS-over-lwIP "blocker" tracked during this story turned out to be a measurement artifact in the manual verification harness, not a defect:

  • The probe loop rm -f received_mtls.log before each run while syslog-ng held the file open → syslog-ng kept writing to the now-unlinked inode → the line-count check always read 0.
  • The real BDD harness counts a baseline→delta on the file (no rm), so bdd-freertos-qemu-lwip has been green in CI the whole time.
  • The earlier strace recvmsg→EAGAIN evidence was captured before the tcp_nagle_disable fix was built into the ELF. That Nagle fix (already committed) is the actual fix.

Verified this session

  • mTLS delivered 5/5 in a corrected manual sweep (counting syslog-ng's own source(s_mtls) processed counter — never rm-ing the open file).
  • Full lwIP BDD lane: 13 features passed, 0 failed; mtls_transport junit status="passed" skipped="0"; record delivered to received_mtls.log.

lwipopts.h reverted (b42be5f)

The provisional pbuf/segment/window bump (16/16/16→48/48/32, 4→6×MSS) was chasing the phantom failure — the "flaky at lower values" note was the same artifact. Re-verified green at main's original 16/16/16 + 4×MSS; the file is now identical to main.

Remaining (your call)

  • Resolve-failure logging (numeric resolver silently rejecting a DNS-name host) — agreed to add a SolidSyslog_Error TDD'd; fold into this PR or take as a follow-up?
  • Branch-protection required-checks update to add the lwIP lanes is your manual GitHub step.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1344 passed)
   🚦   build-freertos-host-tdd-plustcp: 100% successful (✔️ 1652 passed)
   🚦   build-linux-clang: 100% successful (✔️ 1296 passed)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1296 passed)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-linux-mbedtls: 100% successful (✔️ 7 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu-plustcp: 86% successful (✔️ 42 passed, 🙈 7 skipped)
   🚦   bdd-freertos-qemu-lwip: 65% successful (✔️ 32 passed, 🙈 17 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 1149 passed)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1296 passed)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@DavidCozens DavidCozens merged commit cec1f77 into main May 30, 2026
27 checks passed
@DavidCozens DavidCozens deleted the feat/s28-11-freertoslwip-tls branch May 30, 2026 07:54
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.

S28.11: FreeRtosLwip BDD — TLS (mbedTLS over LwipRawTcpStream) + lwip analyze lanes + promote lwip jobs to required

1 participant