Commit graph

289 commits

Author SHA1 Message Date
8b866ae946
add worker pool with bounded concurrency for job dispatch
Replace unbounded goroutine spawning with fixed-size worker pools:
100 workers for SNMP, 20 for MikroTik, 50 for ping. Prevents file
descriptor exhaustion and device overload when handling thousands
of concurrent jobs. Pool provides backpressure when saturated.
2026-02-12 09:50:17 -06:00
07e7ed2745
replace fmt.Sprintf with strconv in hot paths
Use strconv.FormatInt/FormatUint instead of fmt.Sprintf for integer
conversions in snmpValueToString. Use encoding/hex + strings.Builder
instead of fmt.Sprintf loop in formatHex. Use strconv.FormatUint for
WebSocket ref counter. Reduces allocations per OID significantly.
2026-02-12 09:47:42 -06:00
82df96cd59
batch SNMP GETs into chunks of 60 OIDs per PDU
Previously each OID was fetched individually, meaning 1000 OIDs
required 1000 round trips. Now OIDs are batched into groups of 60
(the SNMP max PDU size), reducing round trips by ~50x. Also
pre-sizes the OID value map to avoid repeated rehashing.
2026-02-12 09:41:48 -06:00
92f7c2ed85
fix SNMPv1 walks failing due to unsupported GETBULK
use GETNEXT-based WalkAll for v1 devices instead of BulkWalkAll
2026-02-11 13:04:32 -06:00
01cd109d10
cleanup 2026-02-11 12:42:49 -06:00
289bc3d4f1
fix docker tags by replacing colons in timestamp version
Docker tags cannot contain colons. Use a separate tag-safe format
(colons replaced with hyphens) for Docker tags while keeping the
RFC 3339 timestamp as the version baked into the binary.
2026-02-11 11:48:05 -06:00
297cb8fc91
use UTC timestamp for version instead of git describe 2026-02-11 11:44:19 -06:00
39641b73d4
enable ping in docker container for non-root user
add libcap and setcap cap_net_raw+p on /bin/ping so ICMP works
without container-level capabilities. add NET_RAW cap_add to
docker-compose.example.yml as fallback.
2026-02-11 10:58:30 -06:00
894dcb18e4
skip ping test when raw sockets unavailable 2026-02-11 10:54:15 -06:00
9611cfd620
fix missed errcheck in websocket test 2026-02-11 10:50:00 -06:00
5ad95fac42
fix errcheck lint violations in test files 2026-02-11 10:48:41 -06:00
a36a2b7fd7
increase test coverage and improve logger output 2026-02-11 10:42:19 -06:00
a757bd7615
Optimize CI caching for Blacksmith runners, fix SNMP bulk walk on Mikrotik
- Dockerfile: add BuildKit cache mounts for Go module and build caches,
  pin both stages to Alpine 3.23
- CI: replace BUILDKIT_INLINE_CACHE with registry-based layer caching
  (mode=max) for branch and release builds
- .dockerignore: exclude test files, Rust legacy, nix, and other
  non-build artifacts from Docker context
- SNMP: lower MaxRepetitions from default 50 to 10 to prevent Mikrotik
  routers from timing out during bulk walks
2026-02-11 10:31:10 -06:00
Graham McIntire
86da744e68
Merge pull request #21 from towerops-app/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 4 to 6
2026-02-11 10:20:14 -06:00
dependabot[bot]
51210a5986
Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:20:06 +00:00
Graham McIntire
720ccb40e2
Merge pull request #20 from towerops-app/dependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 4 to 6
2026-02-11 10:19:59 -06:00
Graham McIntire
62e31e3d05
Merge pull request #18 from towerops-app/dependabot/github_actions/golangci/golangci-lint-action-9
Bump golangci/golangci-lint-action from 7 to 9
2026-02-11 10:19:40 -06:00
Graham McIntire
91820976ae
Merge pull request #19 from towerops-app/dependabot/github_actions/actions/setup-go-6
Bump actions/setup-go from 5 to 6
2026-02-11 10:19:26 -06:00
Graham McIntire
b03edf8f9b
Merge pull request #17 from towerops-app/dependabot/github_actions/actions/download-artifact-7
Bump actions/download-artifact from 4 to 7
2026-02-11 10:19:14 -06:00
dependabot[bot]
c360705fd4
Bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:57 +00:00
dependabot[bot]
e8ae0769a5
Bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:53 +00:00
dependabot[bot]
a72ad8a130
Bump golangci/golangci-lint-action from 7 to 9
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7 to 9.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v7...v9)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:49 +00:00
dependabot[bot]
5757bd4a13
Bump actions/download-artifact from 4 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 16:18:45 +00:00
Graham McIntire
ee29613047
Merge pull request #16 from towerops-app/golang-rewrite
Full rewrite in golang
2026-02-11 10:18:12 -06:00
58d1af4f84
refactor some code 2026-02-11 10:17:01 -06:00
37f411ec55
cleanup 2026-02-11 10:07:27 -06:00
769ac1f623
Full rewrite in golang 2026-02-11 10:04:30 -06:00
99997e0c1f
Add OID count to SNMP job completion logs
Shows how many OID values were collected and sent back, making it
visible at info level whether SNMP walks returned data or not.
2026-02-11 09:14:36 -06:00
477b5f06cb
Promote job lifecycle logging from debug to info level
Job receipt, start, and completion messages were all at debug level,
making it impossible to tell if the agent was processing jobs without
setting LOG_LEVEL=debug.
2026-02-11 09:09:10 -06:00
81a5d26655
Cleanup from dokku testing 2026-02-11 09:01:18 -06:00
a9bf0b0f8b
Switch from Alpine (musl) to Debian slim (glibc) to fix SNMP SIGSEGV
musl libc has weak fork-safety guarantees in multi-threaded processes.
When Tokio's runtime is running and we fork() for SNMP operations, musl's
internal state can be inconsistent in the child, causing SIGSEGV on every
SNMP walk to Ubiquiti devices. glibc handles this via pthread_atfork.

- Dockerfile: Alpine → rust:1.93-bookworm build + debian:12-slim runtime
- CI: Remove ensure-netsnmp-base/manifest jobs, simplify build deps
- Delete Dockerfile.netsnmp and netsnmp-base.yml workflow
- Add NULL pointer guards in snmp_helper.c for fork-safety
- Add SNMP exception type handling (NoSuchObject/Instance/EndOfMibView)
- Add crash reproduction tests
2026-02-11 08:41:06 -06:00
fc14fa7798
Disable embedded perl in net-snmp build 2026-02-11 08:06:37 -06:00
2542909690
Add missing file dependency for net-snmp configure 2026-02-11 08:03:44 -06:00
0b0d326892
Add net-snmp base image as CI prerequisite
The ensure-netsnmp-base jobs check if the pre-built image exists in
GHCR before any Docker builds start. First run compiles from source
on native runners (~5 min), subsequent runs skip with a ~5s check.

build-branch and build-release now depend on ensure-netsnmp-manifest.
2026-02-11 08:01:23 -06:00
2386f0d5a5
Upgrade net-snmp to 5.9.5.2 to fix SIGSEGV in SNMP walks
Alpine 3.23 ships net-snmp 5.9.4 which has segfault bugs in varbind
construction and buffer overflows in octet string handling. Version
5.9.5.2 fixes these issues.

Adds a pre-built base image (Dockerfile.netsnmp) compiled from source
so the net-snmp build doesn't run on every agent deploy. The base
image is built separately via the netsnmp-base workflow and referenced
in the main Dockerfile.
2026-02-10 17:58:43 -06:00
06c45cd404
Fix SIGSEGV in forked SNMP child: remove double init_snmp() call
In Fork mode the parent never calls snmp_init_library(), so
pthread_once remains untriggered. The child called init_snmp()
directly, then snmp_open_session() triggered pthread_once calling
init_snmp() a second time. This double-initialization of libnetsnmp
caused signal 11 in every child process.

Let snmp_open_session() handle initialization via pthread_once,
which correctly initializes exactly once and sets numeric OID format.
2026-02-10 17:35:49 -06:00
0db587338e
Use Blacksmith runner for amd64 release build
Switches from ubuntu-latest (2 vCPU) to blacksmith-4vcpu-ubuntu-2404
to match the arm64 build runner and speed up Rust compilation.
2026-02-10 16:52:59 -06:00
4573869607
Reduce log noise by changing INFO to DEBUG for routine operations
Changed the following from INFO to DEBUG level:
- Channel join messages
- Job execution start/completion messages
- SNMP operation messages (GET/WALK)
- Device health check results
- MikroTik job execution
- Device poller thread lifecycle
- Credential test messages
- OID collection results

These messages were generating excessive noise during normal operation.
Kept INFO level for:
- Initial connection establishment
- Shutdown/restart messages
- Update operations
- Errors (already at ERROR/WARN level)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 16:49:58 -06:00
73b75c8aa4
Disable MIB loading in forked SNMP child processes
Net-SNMP was crashing (signal 11) on every operation despite fork isolation.
The issue was that init_snmp() tries to load MIB files by default, which can
cause crashes if MIB files are missing, corrupted, or incompatible.

Since we only use numeric OIDs (e.g., "1.3.6.1.2.1.1.1.0"), we don't need
MIB name resolution at all. This change explicitly disables MIB loading in
the child process by:

1. Setting MIBS and MIBDIRS environment variables to empty
2. Calling netsnmp_set_mib_directory("") to disable MIB directory search
3. These must happen BEFORE calling init_snmp()

This should fix the persistent SIGSEGV crashes in SNMP operations.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 16:39:32 -06:00
7b793b315a
speed up tests: reduce SNMP timeout from 1s to 200ms 2026-02-10 16:19:54 -06:00
57ace0297b
fix clippy needless_return warning 2026-02-10 16:15:37 -06:00
55d001d9f8
Add fork()-based process isolation for SNMP operations
Each SNMP GET/WALK now runs in a forked child process. If libnetsnmp
triggers a SIGSEGV or other fatal signal, only the child dies - the
parent agent logs the crash and continues operating for all other devices.

Key changes:
- C helper: snmp_get_isolated() and snmp_walk_isolated() using
  fork+pipe pattern with 60s alarm watchdog and mutex-serialized forks
- Rust: IsolationMode enum (Fork/Direct) controlled by
  TOWEROPS_SNMP_ISOLATION env var, defaults to Fork
- New CrashRecovered error variant with signal info and logging
- Device poller logs crash recovery events at error level
- Startup logs active isolation mode

Set TOWEROPS_SNMP_ISOLATION=direct to disable isolation for debugging.
2026-02-10 16:12:41 -06:00
54d85c7c06
Remove GitLab CI configuration 2026-02-10 16:03:31 -06:00
ab0db979a1
format 2026-02-10 15:48:51 -06:00
7bffbecd21
Fix function_casts_as_integer warnings in crash handler
Cast function pointers through *const () before converting to
sighandler_t, as required by Rust 1.93+.
2026-02-10 15:44:15 -06:00
6bb33d1f8a
Update CI Rust toolchain to 1.93 to match Docker build 2026-02-10 15:30:15 -06:00
c3e411a565 Fix AMD64 FFI crash by using native AMD64 runners
The AMD64 builds were crashing due to cross-compilation. Blacksmith runners
are ARM64, so when building linux/amd64 on them, Docker uses QEMU emulation
which breaks the C FFI code in native/snmp_helper.c.

Solution:
- AMD64 builds now use GitHub's ubuntu-latest runners (native x86_64)
- ARM64 builds continue using Blacksmith ARM64 runners (native)
- Both architectures now build natively, eliminating FFI segfaults
2026-02-10 15:07:53 -06:00
29b94c09d4
Fix segfault from Alpine version mismatch, add crash handler
The builder (rust:1.93-alpine) uses Alpine 3.23 but the runtime used
Alpine 3.19. The net-snmp-dev headers from 3.23 don't match
net-snmp-libs from 3.19, causing a segfault on the first SNMP call.

Also adds a SIGSEGV/SIGBUS/SIGABRT signal handler that prints a
diagnostic message instead of silently exiting with code 139.
2026-02-10 14:14:07 -06:00
fa46d279a7
Use short SNMP timeouts in tests to avoid 90s waits
Tests against unreachable hosts (192.0.2.1) were waiting 10s * 3 attempts
per request. With cfg(test) overrides (1s timeout, 0 retries), the full
test suite runs in ~3s instead of ~90s.
2026-02-10 14:00:56 -06:00
d91effd3eb
Add integration tests for TLS CryptoProvider configuration
Verifies that ring is properly installed as the default crypto provider
and that all TLS-dependent operations work: rustls ClientConfig, reqwest
client, tokio-rustls connector, and WebSocket TLS with webpki roots.
These tests catch the CryptoProvider panic before it reaches production.
2026-02-10 13:55:32 -06:00