← All documentation · English | 繁體中文
Naming and paths
Naming
One name, everywhere: jt-snmpd.
It used to be split three ways: jt-snmpd as the technical identifier, “JT SNMP
Agent” as the human-readable display name, and JT-SNMP for the data directory.
Separating a display name from an identifier is ordinary Windows convention —
the firewall shows as “Windows Defender Firewall” while its service is mpssvc —
but here it produced confusion rather than clarity: you find jt-snmpd on GitHub,
meet a different name in Apps & Features, and find a third spelling on disk.
Since 0.9.6 they all agree.
| Item | Name | Reason |
|---|---|---|
| Project and repository | jt-snmpd | The trailing d follows daemon convention, as in snmpd and sshd: it reads as a resident service |
| Windows service name | jt-snmpd | sc query jt-snmpd is the obvious thing to type |
| Service display name | jt-snmpd | Matches the service name, so services.msc shows one name rather than two |
| Product name (MSI, Apps & Features) | jt-snmpd | What you find on GitHub is what appears in the control panel |
| Installer title | jt-snmpd Setup | |
| Service description | A read-only SNMP agent serving Windows host metrics through standard MIBs | |
| Executable | jt-snmpd.exe | The service itself |
| Management CLI | jt-snmpdctl.exe | Separate from the service binary, so the two cannot be confused, as with systemctl |
| Install directory | jt-snmpd | Which also takes the space out of the path, and with it a whole class of unquoted-service-path findings |
| Data directory | jt-snmpd | JT-SNMP before 0.9.5; the installer moves it, see below |
| Firewall rules | jt-snmpd (UDP 161), jt-snmpd (ICMPv4) | |
| Group policy key | HKLM\SOFTWARE\Policies\JasonTools\JTSNMPD |
The registry key is deliberately unchanged: renaming it would break every existing GPO |
Upgrading from 0.9.5 or earlier
The installer moves %ProgramData%\JT-SNMP to %ProgramData%\jt-snmpd.
That step is not optional. state\index-map.json holds the ifIndex assignments,
and losing it makes LibreNMS delete every port, rediscover, and orphan the
historical RRDs. state\ms-snmp-restore.json is the only record of what the
built-in SNMP service looked like before it was disabled.
If the move fails it copies instead and says so in the log, because a duplicated
directory is recoverable and a lost one is not.
tests/test_data_dir_migration.py holds this in place.
Install paths, with program and data strictly separated
What follows is what is actually on disk, not a plan. Entries marked (planned) do not exist yet.
%ProgramFiles%\jt-snmpd\ <- the program, read-only. Never ProgramData
jt-snmpd.exe
msi-configure.ps1 called by the MSI on install and uninstall
_internal\ the PyInstaller one-folder runtime
jt-snmpdctl.exe (planned) management CLI
mibs\ (planned) MIB files, for LibreNMS and snmpwalk
%ProgramData%\jt-snmpd\ <- configuration and state, writable
config.json written by the installer, read by the agent at its entry point
state\index-map.json the ifIndex assignments (lose it and LibreNMS rebuilds every port)
state\engine.json engineID and engineBoots
state\ms-snmp-restore.json the built-in service's original start type and state
state\disk-maxtemp.json the highest disk temperature actually observed, kept across restarts
logs\jt-snmpd.log rotated at 5 MB, 3 kept (4 files, about 20 MB)
logs\msi-configure.log the installer's own log
secrets\usm.dat SNMPv3 localized keys, DPAPI machine scope
This listing once named
config.yaml,engine-state.jsonandms-snmp-migration.json. All three were wrong. Paths in documentation have to be checked against a running machine before they are written down.
Rules about paths
-
ImagePath must be quoted. Measured:
C:\程式集測試\JT SNMP 代理程式\jt_snmpd.pyunquoted was truncated at the first space toC:\程式集測試\JT, and the process died with nothing in any log. This is the unquoted service path finding that security audits raise most often. -
The ProgramData ACL has to be verified and reset, not merely created.
C:\ProgramData’s default ACL lets Users create subdirectories, so an attacker can create ours first and keep write access to it. Create-if-not-exists is not enough. The target ACL isSYSTEM: Full,Administrators: Full, and nothing else. -
Non-ASCII paths have to work. A customer may install under a path in their own language. Verified below.
Verified on hardware, 2026-08-24, Windows 11 build 26200, Traditional Chinese
Running the agent from C:\程式集測試\JT SNMP 代理程式\ — non-ASCII and a
space:
LISTENING 0.0.0.0:16162 varbinds=131
fs_encoding=utf-8
sysDescr = Hardware: AMD64 Family 25 Model 80 Stepping 0 AT/AT COMPATIBLE
- Software: Windows Version 6.3 (Build 26200 Multiprocessor Free)
sysServices = 76 <- see the note below
ifName = 乙太網路 <- non-ASCII, correct UTF-8
ifDescr = Red Hat VirtIO Ethernet Adapter
hrProcLoad = 8 <- a real CPU sample
diskIODevice = PhysicalDrive0 <- UCD-DISKIO
sysServicesis not a way to tell the two agents apart, although it was used as one here for a while. It comes fromRFC1156Agent\sysServicesin the registry, which an administrator sets from the Agent tab of the service’s properties, so it describes the machine rather than the software. Measured on a Windows Server 2016 domain controller, an untouched built-in service reports 76 — the same value this agent reports. UsesysDescr, or the presence ofjtAgentVersionunder the private subtree, which the built-in service has no way to produce.
The encoding rule, which came out of a real failure
An SNMP OCTET STRING is bytes, not text. pyasn1 encodes a str as latin-1 by
default and raises PyAsn1UnicodeEncodeError on anything outside it. On a
Traditional Chinese Windows installation the network adapter’s alias is
non-ASCII — 乙太網路 is simply what “Ethernet” is called — so in the target
environment this is not an edge case, it is guaranteed.
Everything goes through octet(), which encodes to UTF-8 explicitly. A bare
rfc1902.OctetString(str) is not allowed.
The same applies to files: every open states encoding="utf-8". Windows’ open()
defaults to the system ANSI code page, cp950 on a Traditional Chinese
installation, and writing anything outside it raises UnicodeEncodeError.