Glutton has two output paths: process logs through Go slog, and optional producer events sent to HTTP or hpfeeds sinks. Handlers can emit both, but they're configured separately.
producer.NewLogger(...) creates a JSON slog logger that writes every record to:
- stdout
- the path configured by
--logpath
The file writer uses lumberjack rotation: 200 MB max size, 356 days max age, compression on. Every record carries the sensorID attribute (read from / written to <var-dir>/glutton.id).
The --debug flag is parsed but not wired into slog.HandlerOptions, so it does not currently lower the log level.
Producer events follow the producer.Event schema:
| JSON field | Type | Meaning |
|---|---|---|
timestamp |
string (RFC 3339) | UTC time the event was produced. |
startedAt |
string (RFC 3339) | Connection (or datagram) start from the connection table (md.Added). Go does not omit a zero time.Time, so an unknown start is 0001-01-01T00:00:00Z, not absent. |
durationMs |
number, optional | Milliseconds from startedAt to produce time. Omitted when 0. |
transport |
string | tcp or udp. |
srcHost |
string | Source IP. |
srcPort |
string | Source port. Unlike dstPort, this is a string. |
srcPtr |
string, optional | First reverse-DNS name when a PTR lookup already ran (not on CIDR-matched scanners). |
dstHost |
string, optional | Original destination IP (TPROXY LocalAddr / UDP dest). This is the sensor's address; strip it before showing events publicly. |
dstPort |
number | Original destination port from metadata. |
sensorID |
string | Glutton sensor ID. |
sensorVersion |
string, optional | Build version (VERSION / sensor_version). |
rule |
string, optional | Rule match string when metadata includes a rule. |
ruleName |
string, optional | Optional name from rules.yaml. |
handler |
string | Handler name supplied by the protocol handler. |
payload |
string (base64), optional | First-frame payload bytes. |
payloadHash |
string (hex), optional | SHA-256 of the (sanitized) top-level payload. |
frameCount |
number, optional | Number of decoded frames when decoded is a slice. Omitted when 0. |
endReason |
string, optional | Why the session ended (client_close, timeout, handler_close, read_error, write_error, max_frames, evicted). evicted means the handler's session table was full and the least recently active session was flushed early. Omitted by handlers that do not set it. |
tls |
object, optional | Present only when the sensor terminated TLS. See TLS details. |
scanner |
string, optional | Scanner classification from scanner.Classify(...). Omitted when empty. |
decoded |
array, object or null, optional |
Handler-specific decoded data. See Decoded data. |
All optional fields use omitempty: an empty string, a zero number or a nil value is left out of the JSON instead of being sent as "", 0 or null.
Events are emitted only when (1) producers.enabled is true so a producer object exists, (2) a handler calls ProduceTCP(...) or ProduceUDP(...), (3) the matched rule does not set produce: false, and (4) at least one sink is enabled. Before output, configured addresses values are scrubbed from payload bytes (ASCII and UTF-16LE) and replaced with 1.2.3.4. The same sanitizer runs on every string and byte slice inside decoded (frame fields such as from, to, endpoint_url, nested structs, maps), so the sensor address never appears in decoded output; fixed-size byte arrays are left alone. Events from sensors built before commit 937dcd8 (after v1.0.1) only scrubbed payload and path.
tls is set when the rule has tls: true, or tls: auto and the client opened with a ClientHello. decoded and payload then hold the decrypted plaintext protocol, so a display should say that the session was TLS.
| Field | Type | Meaning |
|---|---|---|
serverName |
string, optional | SNI from the ClientHello. |
alpn |
array of strings, optional | Protocols offered by the client. |
version |
string, optional | Negotiated TLS version, e.g. TLS 1.3. |
cipher |
string, optional | Negotiated cipher suite. Empty (so omitted) when the handshake failed. |
clientHello |
string (base64), optional | Raw ClientHello records, capped at 4 KiB. |
truncated |
boolean, optional | Set when clientHello was cut at the cap. |
Value encodings inside decoded follow Go's encoding/json:
- Byte slices (
[]byte) are base64 strings. Framepayloadfields are always base64. - Fields documented as "(hex)" are hex strings produced by the handler (
spi_i,spi_r,token,session_idonopenvpn,challenge, and so on). - Fixed-size byte arrays are JSON arrays of numbers 0–255. Today these are only the
bittorrentfieldsprotocol_identifier,reserved,info_hashandpeer_id; show them as hex. - Every other array of numbers is a list of IDs, not bytes:
cipher_suitesandextensions(dtls, uint16),etypes(kerberos),encodings(rfb, int32, can be negative). Show these as lists; joining them as hex gives wrong values. - Nested objects (
headeronsmbandmongodb,questionsonmdns,submessagesonrtps) are JSON objects or arrays of objects.
Array-of-frames decoded entries share these JSON names when the handler fills them: direction, payload, command (leaf operation), path, status (writes), truncated. Handler-specific fields sit beside them. A display that shows only the shared fields hides the most useful data for most handlers, so show every key a frame carries and use the table below to order them. Some fields are easy to miss but matter for reading a session:
http:dest_portandsrc_porton reads, because a session can span connections and ports while the top-leveldstPort/srcPortbelong to the first connection.rdp:ntlm_domain,ntlm_userandntlm_workstationon theNTLMAuthenticateframe.sip:from,to,call_id,username, andvariant/visiton writes.jabber,smtp,opcua,mqtt,dicom,pop3:username(andpasswordonjabber).dtls,jabber:server_name.
Example shape:
{
"timestamp": "2026-05-15T12:00:00Z",
"transport": "tcp",
"srcHost": "203.0.113.10",
"srcPort": "54321",
"dstHost": "192.0.2.10",
"dstPort": 80,
"sensorID": "00000000-0000-0000-0000-000000000000",
"sensorVersion": "v0.0.0",
"rule": "Rule: tcp",
"frameCount": 1,
"endReason": "client_close",
"handler": "http",
"payload": "R0VUIC8gSFRUUC8xLjENCg0K",
"decoded": [
{
"direction": "read",
"command": "GET",
"path": "/",
"session_id": "11111111-1111-1111-1111-111111111111",
"payload": "R0VUIC8gSFRUUC8xLjENCg0K"
}
]
}decoded is handler-specific:
| Handler | decoded |
Notes |
|---|---|---|
http (Go) |
Array of per-direction frames: direction, command, path, query, host, user_agent, status, session_id, dest_port, src_port, payload |
Keep-alive HTTP. command is the HTTP method. Writes set status (e.g. 200). Responses set a session cookie; frames that share that cookie (per source host) are grouped into one produced event when the session idles out (conn_timeout). Requests without the cookie (most scanners) join the source IP's latest live session, across connections and destination ports, so the top-level dstPort/srcPort are those of the first connection and reads carry their own dest_port/src_port. A source session stops taking new cookieless connections after 500 frames or one hour. Shares the idle session table with mcp. |
http (Spicy) |
{method, url, path, query} |
Request body is the event payload. |
tcp |
Array of per-direction frames: direction, command, status, payload, payload_hash |
Catch-all. Client bytes are one read frame (capped by max_tcp_payload). The reply is a canned service response (protocols/tcp/banners, from honeytrap per #53): a payload signature wins (SSH- → ssh banner, TLS record → tls-alert) and sets command on the read; otherwise the destination port picks it (80 http, 135 dcerpc-bind-ack, 139 netbios-session, 1433 mssql-prelogin, 4899 radmin, 8009 ajp-404); otherwise random bytes. Writes set status to the response name or random. Ports 22/2222 (ssh), 110 (pop3) and 5900 (rfb) get their banner on connect, before any read. On 5900 a valid RFB ProtocolVersion reply is tagged command rfb and answered with the security handshake (status rfb-security: 02 02 01 for 3.7/3.8, 00 00 00 02 for 3.3) instead of random bytes; the rfb handler (routed by default) takes the session further. Clients that send nothing get no reply. |
udp |
Array of read frames: direction, payload, payload_hash, truncated |
Catch-all. One datagram per event (capped at 1024 bytes). truncated is set when the datagram was longer. The handler does not reply. Datagrams starting with a SIP request or status line are rerouted to sip; datagrams with RakNet offline magic are rerouted to raknet; APPLICATION 10/12 Kerberos AS-REQ/TGS-REQ are rerouted to kerberos; CoAP version-1 GET/POST/PUT/DELETE datagrams are rerouted to coap; IKE headers whose length field equals the datagram length (optionally after the 4-byte non-ESP marker) are rerouted to ike; Source Engine queries (ffffffff + A2S request type) are rerouted to a2s; PlayStation DDP request lines (SRCH/WAKEUP/LAUNCH * HTTP/1.1) are rerouted to ddp; WS-Discovery SOAP envelopes are rerouted to wsdiscovery; KNXnet/IP headers (06 10, a request service type, length field equal to the datagram length) are rerouted to knx; epoch-0 DTLS ClientHello records are rerouted to dtls. |
proxy_tcp, proxy_udp |
Per-direction entries: direction, payload, payload_hash, bytes, truncated |
Only when capture_traffic.enabled is true. Samples are capped by max_tcp_payload; truncated is whether more bytes were forwarded than captured. proxy_udp emits one event per flow when the flow idles out or closes. |
sip (TCP/UDP) |
Array of per-direction frames: direction, command, path, status, from, to, call_id, user_agent, username, payload, truncated, variant, visit |
command is the SIP method and path the Request-URI on reads. Writes set status: 200 for OPTIONS, REGISTER (with or without credentials) and BYE; 100, 180, then 200 with an SDP answer for INVITE, so toll-fraud scanners go on to dial and the number shows up in path (on UDP the 200 follows the 180 after a 2–6 s ringing delay, and the first sip.reject_invites (default 2) new INVITE calls in each visit of a source IP (see returning sources below) get 100 then 404 Not Found instead, so the scanner tries its next dial prefix); 401 challenge then 403 after credentials for SUBSCRIBE/MESSAGE/NOTIFY/PUBLISH/REFER/UPDATE/INFO; 481 CANCEL/PRACK (on UDP, a CANCEL while the INVITE is still ringing gets 200 and the INVITE 487 Request Terminated); 501 unknown. The top Via of each response gets received=<source IP> when the client asked for rport or its sent-by host differs from the packet source, and a valueless rport is filled with the source port (RFC 3261 §18.2.1, RFC 3581). user_agent is the client User-Agent on reads and the honeypot Server header on writes. username is the digest Authorization username; the digest response is not copied into decoded. UDP sets truncated when a datagram exceeded 4096 bytes. UDP groups a call into one event: an INVITE opens a dialog keyed by source IP and call_id, and later datagrams with that key (ACK, BYE, CANCEL, retransmits, re-INVITEs) append their frames to it. A retransmitted INVITE gets the latest response again (the 180 while ringing, then the final response), and the final response (200, 404 or 487) is resent at RFC 3261 T1 backoff (0.5 s doubling to 4 s) until an ACK arrives; each resend is a write frame. The ACK to a 404 or 487 produces the dialog with endReason client_close; an unACKed 404 ends with timeout and an unACKed 487 with client_close after 64T1, without a BYE. If no ACK to the 200 arrives within 64T1 (32 s), the honeypot hangs up like Asterisk/pjsip with a write frame whose command is BYE (Reason: SIP ;cause=408 ;text="Request Timeout", sent to the caller's source address) and produces the dialog with endReason timeout. Otherwise the dialog is produced with endReason client_close once a BYE or CANCEL is answered, timeout after conn_timeout (at least 32 s) without a datagram, max_frames at 32 frames, evicted when 256 dialogs are open, and handler_close on shutdown. Datagrams outside a dialog (OPTIONS, REGISTER, a stray ACK/BYE) are still one event each with handler_close. Header lines ending in a bare LF are accepted (replies are always CRLF), and a UDP datagram that was not truncated may omit the empty line after the headers; payload keeps the raw bytes. A message that still does not parse is one read frame with no command and endReason read_error. Generic UDP peeks the SIP start line (CRLF or LF) and reroutes here from any port. Returning sources: write frames carry variant (the response variant) and visit (the source IP's visit number, 1 for a first visit). A visit is all SIP traffic (TCP and UDP) from one source IP with no gap longer than recall.visit_gap (default 30 min); each new visit moves to the next variant, so the same scanner sees a different outcome when it comes back. answer (visit 1, 4, ...) is the behavior described above. auth challenges REGISTER and INVITE without credentials with 401 (WWW-Authenticate digest, no 100 first) and handles them like answer once they carry an Authorization header; on UDP the ACK to the 401 keeps the dialog open, so the authenticated re-INVITE (same call_id, higher CSeq) lands in the same event. busy accepts REGISTER and rings INVITE (100, 180), then sends 486 Busy Here instead of the 200; on UDP its ACK produces the dialog with client_close. sip.reject_invites 404s apply first in every variant; their count starts over with each visit. A dialog keeps the variant of the visit it started in. |
openvpn (UDP) |
Array of read frames: direction, command, opcode, opcode_name, key_id, session_id (hex), ack_count, packet_id, malformed, payload, truncated; write frames add status |
command copies opcode_name. ack_count/packet_id are parsed from client hard resets (opcodes 1 and 7); malformed marks a reset too short to hold them (e.g. the 13-byte scanner probe). Replies only when openvpn.reply is true, then a write frame P_CONTROL_HARD_RESET_SERVER_V2 follows a well-formed reset. |
mdns (UDP) |
Array of read frames: direction, command, path, questions (qname, qtype, qtype_name, qclass), payload, truncated |
command/path are the first question's qtype name and qname. The handler does not reply. |
l2tp (UDP) |
Array of per-direction frames: direction, command, status, message_type, message_name, host_name, vendor_name, tunnel_id, assigned_tunnel_id, ns, nr, payload, truncated |
command copies message_name. SCCRQ probes get a write frame with status SCCRP. |
raknet (UDP) |
Array of read frames: direction, command, packet_id, packet_name, protocol, magic_ok, mtu, payload, truncated |
command copies packet_name. Parse-only; no Open Connection Reply or Unconnected Pong. 0x05 OCR1 sets protocol (byte after magic) and mtu (datagram length). Generic UDP peeks the same magic and reroutes here. |
kerberos (UDP) |
Array of read frames: direction, command, path, msg_type, msg_name, pvno, realm, sname, cname, etypes, from, nonce, payload, truncated |
command copies msg_name; path copies sname. Parse-only; no KRB-ERROR or AS-REP. Truncated or non-DER datagrams still emit a frame with raw payload (msg_name UNKNOWN). Generic UDP peeks APPLICATION 10/12 and reroutes here. |
ike (UDP) |
Array of per-direction frames: direction, command, status, version, spi_i, spi_r (hex), encryption, prf, integrity, dh_groups, ke_group, notifies, vendor_ids (hex), nat_t, payload, truncated |
udp/500 and udp/4500 (nat_t when the non-ESP marker is present; replies keep it). command is the exchange name. Reads list every offered transform name (key length suffixed, e.g. AES_CBC_256); IKEv1 records header and Vendor IDs only, without a reply. IKEv2 IKE_SA_INIT requests get a stateless reply: INVALID_KE_PAYLOAD asking for MODP_2048/ECP_256/ECP_384 when the KE group differs, a full IKE_SA_INIT (chosen transforms, random KE and nonce) when it matches, or NO_PROPOSAL_CHOSEN. Writes set status and list the chosen transforms. Datagrams over 4096 bytes are capped with truncated. |
coap (UDP) |
Array of per-direction frames: direction, command, status, type, code, code_name, message_id, token (hex), path, observe, payload, truncated |
command copies code_name. Writes also set status (CONTENT / CREATED / DELETED). GET .well-known/core returns CoRE Link Format </ps/temp>,</ps/hum>. Generic UDP peeks version-1 request codes 1–4 and reroutes here. |
a2s (UDP) |
Array of per-direction frames: direction, command, request_type, query, challenge (hex of wire bytes), status, payload, truncated |
Valve Source Engine queries (udp/27015). command is A2S_INFO, A2S_PLAYER, A2S_RULES, A2S_SERVERQUERY_GETCHALLENGE, A2A_PING or UNKNOWN; query is the A2S_INFO string (Source Engine Query). A2S_INFO without a challenge, and PLAYER/RULES with challenge ffffffff, get a 9-byte S2C_CHALLENGE write (status S2C_CHALLENGE, random challenge). Info/player/rules data is never sent and no reply is larger than the request, so the sensor is not an amplifier. Datagrams over 1024 bytes are capped with truncated. |
ddp (UDP) |
Array of per-direction frames: direction, command, version, client_type, user_credential_present, host_type, status, payload, truncated |
PlayStation Device Discovery Protocol (udp/987 PS4, udp/9302 PS5). command is SRCH, WAKEUP, LAUNCH or UNKNOWN; version is the client device-discovery-protocol-version on reads and the advertised one on writes. user_credential_present records only that a user-credential header was sent, never its value. SRCH gets a write HTTP/1.1 620 Server Standby (status 620) from a console identity derived from the sensor address: host_type PS5 for client versions 0003xxxx, else PS4. Replies are limited to one per source IP per minute (the reply is ~3x the probe). WAKEUP/LAUNCH are recorded only. Datagrams over 1024 bytes are capped with truncated. |
rtps (UDP) |
Array of read frames: direction, command, version, vendor_id, vendor, guid_prefix, submessages (kind, flags, length), writer_entity_id, writer_sn, participant_guid, user_data, entity_name, domain_id, locators, vendor_strings, payload, truncated |
RTPS/DDS discovery (udp/7400, 7401, 7410, 7411). command is DATA(p) for an SPDP participant announcement (writer entity 000100c2), DATA(w)/DATA(r) for SEDP, else the first non-INFO submessage kind; UNKNOWN if unparseable. Parse-only: no reply. vendor is set for known vendor IDs. vendor_strings are printable runs from vendor-specific parameters (heuristic). Truncated datagrams keep whatever parsed before the cut. Generic udp peeks the RTPS magic and reroutes here. |
wsdiscovery (UDP) |
Array of per-direction frames: direction, command, message_id, types, scopes, address, status, payload, truncated |
WS-Discovery SOAP-over-UDP (udp/3702). command is the last segment of wsa:Action (Probe, Resolve, Hello, Bye) or UNKNOWN if unparseable. A Probe for wsdp:Device (or with no types) gets a write ProbeMatches (status ProbeMatches) from a stable per-sensor device identity; a Resolve for that identity gets ResolveMatches. Typed probes for other services (e.g. ONVIF) and other actions are recorded only. Replies are limited to one per source IP per minute and to 4x the request size. Datagrams over 4096 bytes are capped with truncated. |
knx (UDP) |
Array of per-direction frames: direction, command, service_type, hpai_ip, hpai_port, status, payload, truncated |
KNXnet/IP (udp/3671). command is SEARCH_REQUEST, DESCRIPTION_REQUEST, CONNECT_REQUEST, CONNECTIONSTATE_REQUEST, DISCONNECT_REQUEST or UNKNOWN (truncated header, bad magic, length not equal to the datagram length, malformed HPAI); service_type is the hex code (e.g. 0x0203) when the header was readable. hpai_ip/hpai_port are the endpoint the client claims, which is often its real address behind NAT; it is recorded, never contacted or replied to. DESCRIPTION_REQUEST gets a write DESCRIPTION_RESPONSE and SEARCH_REQUEST a SEARCH_RESPONSE (status) from a stable per-sensor TP1 gateway identity (derived serial and MAC); CONNECT_REQUEST gets a CONNECT_RESPONSE with E_NO_MORE_CONNECTIONS. Other services are recorded only. Replies always go to the datagram source and are limited to one per source IP per minute (the reply is ~5x the probe). Datagrams over 1024 bytes are capped with truncated. |
dtls (UDP) |
Array of per-direction frames: direction, command, client_version, session_id, cookie_present, cookie_valid, cipher_suites, extensions, server_name, status, payload, truncated |
DTLS handshake probes on any UDP port (rerouted from the generic udp handler by payload sniff). The read frame has command ClientHello (UNKNOWN when the record is truncated or malformed) and client_version (DTLS 1.0, DTLS 1.2 or hex). cipher_suites are the offered suite IDs, extensions the extension types in wire order, server_name the SNI. cookie_present is set when the hello carried a non-empty cookie and cookie_valid when it is the cookie issued to that source address (both absent when false). A hello without a valid cookie gets a write HelloVerifyRequest (status ok; 44 bytes, 16-byte HMAC cookie bound to the source IP and port, record sequence echoed from the request), so scanners send the second, cookie-bearing hello, which arrives as its own event. A valid-cookie hello is recorded and not answered: the handshake is not emulated. Datagrams over 1024 bytes are capped with truncated. |
mqtt (TCP) |
Array of per-direction frames: direction, command, packet, client_id, username, topic, topics, qos, payload |
command copies packet. CONNECT gets CONNACK accept; password bytes are not copied into decoded. |
memcache (TCP) |
Array of per-direction frames: direction, command, status, payload |
Writes set status (STORED, END, ERROR, CLIENT_ERROR, VERSION). set frames aggregate the command line plus the data chunk. |
modbus (TCP) |
Array of per-direction frames: direction, command, function_code, unit_id, address, quantity, status, payload |
command copies function_code. Exception writes set status Exception. |
dnp3 (TCP) |
Array of per-direction frames: direction, command, dest, src, status, payload, truncated |
DNP3 data link layer. command is the link function (REQUEST_LINK_STATUS, TEST_LINK_STATES, CONFIRMED_USER_DATA, …), BAD_CRC (header or data-block CRC mismatch) or UNKNOWN (not DNP3 / partial). dest/src are always present (0 is a valid probe address, and the value on UNKNOWN/BAD_CRC header failures). Writes set status (LINK_STATUS, ACK, NOT_SUPPORTED). Capped at 512 read frames; the last kept read is truncated and endReason is max_frames. |
opcua (TCP) |
Array of per-direction frames: direction, command, path, message_type, service, endpoint_url, security_policy, application_uri, application_name, username, payload |
command is service (or message_type if empty). path copies endpoint_url. Username identity is recorded; password bytes are not copied into decoded. |
mctp (TCP) |
Array of per-direction frames: direction, command, method, cseq, func_version, segments, status, return_code, payload, truncated |
HiSilicon DVR control protocol (tcp/9000). command is the HI_SRDK_* function, method the request method (REMOTE), segments the number of body data segments. Writes set status 200 and return_code 0. Bodies over 64 KiB are stored up to the cap with truncated. Non-MCTP traffic on the port falls back to tcp. |
dicom (TCP) |
Array of per-direction frames: direction, command, path, status, pdu_type, called_ae, calling_ae, application_context, abstract_syntaxes, transfer_syntaxes, implementation_class_uid, implementation_version, username, message_id, sop_class_uid, sop_instance_uid, move_destination, payload_hash, payload, truncated |
DICOM Upper Layer (tcp/104, tcp/11112). command is the DIMSE command (C-ECHO-RQ, C-FIND-RQ, C-STORE-RSP, ...) for P-DATA-TF frames and the PDU name (A-ASSOCIATE-RQ, A-RELEASE-RP, A-ABORT) otherwise. path copies called_ae. Every association is accepted; C-ECHO/C-FIND/C-STORE/C-GET/C-MOVE writes set status Success, N-* services UnrecognizedOperation. A DIMSE message's command and data set PDUs are aggregated into one read frame (capped at 1 MiB with truncated); C-STORE data sets are stored under payloads/dicom and hashed into payload_hash. User identity usernames are recorded; passcodes are not copied into decoded. |
mongodb (TCP) |
Array of per-direction frames: direction, header, opcode_str, command, status, payload |
command is the BSON command name. Writes set status ok. |
mcp (TCP) |
Array of per-direction frames: direction, command, path, status, session_id, payload |
command is the JSON-RPC method or HTTP verb. Writes set HTTP status. Shares the idle session table with HTTP. |
telnet (TCP) |
Array of per-direction frames: direction, command, path, message, payload_hash |
Login reads set command username/password; shell reads use the first token. wget/curl lines set path to the http(s) URL and payload_hash when the sample fetch succeeds. IAC negotiation is a separate read frame with no command. Process log: Info telnet login with src_ip/src_port/dest_port/username/password; shell lines at Debug. |
smtp (TCP) |
Array of per-direction frames: direction, command, status, mailbox, params, username, payload, truncated |
Reads set command to the upper-cased SMTP verb (verbs are case-insensitive). MAIL FROM/RCPT TO reads set mailbox (address without angle brackets) and params (ESMTP parameters such as SIZE=100). The DATA body is one DATA read holding at most 500 lines / 256 KiB; past either cap the rest of the body is read and discarded and the frame sets truncated. Client lines keep at most 1024 bytes (truncated on the frame when cut). The greeting and HELO/EHLO replies carry smtp.hostname. RCPT before MAIL, a nested MAIL, and DATA without an accepted RCPT get 503; more than 100 recipients get 452; RSET, HELO/EHLO and a completed DATA reset the transaction. EHLO advertises AUTH PLAIN LOGIN; AUTH continuation lines are AUTH reads, and the frame that carried the identity sets username (the password is not stored). Every AUTH attempt is refused with 535. STARTTLS is refused with 454. Writes set status to the 3-digit reply code. |
ftp (TCP) |
Array of per-direction frames: direction, command, path, status, payload, payload_hash |
command is the FTP verb. STOR/RETR set path. Writes set status to the numeric reply code. |
rfb (TCP) |
Array of per-direction frames: direction, command, status, version, security_type, challenge, response, key, text, encodings, payload, truncated |
RFB 3.3/3.7/3.8 (RFC 6143), routed from tcp/5900-5910. Handshake commands are ProtocolVersion (version is the negotiated 3.3/3.7/3.8), Security (offered types, e.g. VNCAuthentication,None; 3.3 clients are given VNCAuthentication), SecurityType (the client's choice), VNCAuthChallenge, VNCAuthResponse, SecurityResult (status OK/Failed), ClientInit and ServerInit. VNC authentication accepts any password (SecurityResult OK) and continues into the session, so the client reveals what it does after login; the challenge/response pair is still recorded in hex. Choosing None (or passing VNC auth) continues to ServerInit (1024x768, desktop name ubuntu:1 (ubuntu)) and records client messages: SetPixelFormat, SetEncodings (encodings), FramebufferUpdateRequest, KeyEvent (key on key-down, the character or a hex keysym like 0xff0d), PointerEvent, ClientCutText (text). A FramebufferUpdateRequest is answered with a FramebufferUpdate write frame showing a static fake desktop, clipped to the requested rectangle, in the client's pixel format; encodings on that frame is the one encoding used (0 Raw, or 2 RRE when the client lists it before Raw). The screen never changes, so incremental requests are answered only before the first update. Each session sends at most 16 MiB of updates; later requests go unanswered. Write frames keep their first 4096 bytes (truncated), so a full Raw frame is never stored. Message bodies keep 4096 bytes (truncated); bodies over 1 MiB end the session. A read without command is non-RFB input or an unknown message type. Sessions stop at 256 frames (endReason max_frames). |
iscsi (TCP) |
Array of per-direction frames: direction, command, message, payload |
command is the opcode name (LOGIN_REQUEST, LOGIN_RESPONSE, …). One produced event per TCP session. |
bittorrent (TCP) |
Array of per-direction frames: direction, command, message, payload, truncated |
command is handshake. |
jabber (TCP) |
Array of per-direction frames: direction, command, path, status, mechanism, username, password, tls, server_name, payload, truncated |
XMPP client-to-server (tcp/5222, tcp/5223). The honeypot waits for the client (no banner). Frames are split per stanza, not per line. Reads set command to the element name (stream, starttls, auth, iq, message, stream-end, ...) and path to the stream to attribute. Writes use stream (header, path is from), features (PLAIN + legacy iq-auth, plus STARTTLS before TLS), proceed, failure, iq, stream-error, and stream-end; status is the condition (not-authorized, invalid-mechanism, not-well-formed, policy-violation, proceed, result). SASL PLAIN auth and jabber:iq:auth set reads record username/password (never validated; auth always fails and the stream is closed). A first byte 0x16 (direct TLS, tcp/5223) or STARTTLS adds a tls read frame with the raw ClientHello in payload and SNI in server_name; later frames set tls and carry decrypted XML. Frames are capped at 4 KiB (truncated) and sessions at 32 reads. Non-XML input is stored as one read frame without command. |
pop3 (TCP) |
Array of per-direction frames: direction, command, status, username, payload, truncated |
POP3, plaintext on tcp/110-style ports or POP3S on tcp/995 (rule tls: true, so TLS details are in the event's tls field, not in frames). The greeting is the first write frame (status +OK), so payload is the honeypot's banner. Reads set command to the upper-cased verb; USER also sets username. PASS has no parsed field (the raw line stays in payload) and always fails. A POP3S scanner that sends no ClientHello, or a non-TLS client, produces an event with empty decoded (null) and the raw bytes as payload. Sessions are capped at 32 commands and 4 KiB lines (truncated). |
whois (TCP) |
Array of per-direction frames: direction, command, status, payload, truncated |
WHOIS (tcp/43, RFC 3912). One query line per session: the read frame sets command to the query (CRLF trimmed; capped at 512 bytes, truncated when exceeded) and the write frame (status no-match) is a generic registry-style "no entries found" line. A client that sends nothing yields an empty array. |
adb (TCP) |
Array of read frames: direction, command, payload, truncated |
command is the service prefix before :. Bodies longer than 255 bytes are clipped with truncated. |
smb (TCP) |
Array of per-direction frames: direction, header, command, path, setup, status, nt_status, account, native_os, native_lanman, total_data_count, payload, truncated |
Direct TCP (port 445) length-prefixed SMB1 and SMB2. command is the opcode name. Tree Connect sets path to the share (IPC$); NT Create AndX sets path to the filename. Trans2 frames set setup (TRANS2_SESSION_SETUP, …). NT Transact reads set total_data_count. Secondary fragments (0x33 / 0x26 / 0xa1) get no reply until the one that completes an open NT_TRANSACT (DataDisplacement + DataCount >= TotalDataCount), which is answered with STATUS_INVALID_PARAMETER; Echo copies request data. EternalBlue-class traffic can be SMB_COM_NT_TRANSACT (0xa0, often total_data_count 0x103d0) plus many 0x33 sprays on one TCP session (not only 0xa1 / SMB2 grooms). Writes set status / nt_status. Session Setup copies Native OS/LanMan and account (no password). header JSON uses numeric tid/uid/mid/pid and hex flags2. |
rdp (TCP) |
Array of per-direction frames: direction, command, cookie, protocols, ntlm_domain, ntlm_user, ntlm_workstation, header, payload |
command is ConnectionRequest, ConnectionConfirm, TLSClientHello, TLSHandshake, MCSConnectInitial, MCSConnectResponse, NTLMNegotiate (client Type 1), NTLMChallenge (server Type 2 reply), NTLMAuthenticate (client Type 3), or TSRequest (CredSSP frame with no recognised NTLM type). CredSSP/NLA (HYBRID) follows TLS: the server replies to Negotiate with a random Challenge; the Authenticate frame carries ntlm_domain, ntlm_user, and ntlm_workstation decoded from the UTF-16LE payload. The Confirm selects a single protocol (CredSSP, else TLS, else standard). TLSClientHello holds all client handshake bytes (capped, truncated set if cut) and TLSHandshake the server's flight; SNI/ALPN are in the event's tls field. cookie is mstshash; protocols is the RDP_NEG bitmask name. header is the TPKT (empty on TLS and CredSSP frames). |
When producers.http.enabled is true, Glutton marshals each event as JSON and POSTs it to producers.http.remote with Content-Type: application/json. From source:
- HTTP client timeout: 10s; TLS handshake timeout: 5s.
- Events from private source IPs are skipped.
- Userinfo in the remote URL is used as HTTP Basic Auth.
- Query strings in the configured URL are preserved.
When producers.hpfeeds.enabled is true, Glutton connects to the broker at startup and publishes gob-encoded producer.Event values to producers.hpfeeds.channel.
producers:
hpfeeds:
enabled: false
host: 172.26.0.2
port: 20000
ident: ident
auth: auth
channel: testTreat all logged payloads as attacker-controlled data in downstream pipelines.