diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c9824524df2..e769da78364 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -28,6 +28,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha1...master[Check the HEAD d *Packetbeat* - Renamed the flow event fields to follow Elastic Common Schema. {pull}9121[9121] +- Renamed several client and server fields. IP, port, and process metadata are + now contained under the client and server namespaces. {issue}9303[9303] *Winlogbeat* diff --git a/libbeat/common/endpoint.go b/libbeat/common/endpoint.go index 70989ba9bd7..5ae920a363a 100644 --- a/libbeat/common/endpoint.go +++ b/libbeat/common/endpoint.go @@ -19,27 +19,28 @@ package common // Endpoint represents an endpoint in the communication. type Endpoint struct { - IP string - Port uint16 - Name string - Cmdline string - Proc string + IP string + Port uint16 + Domain string + + // Process metadata. + Process } // MakeEndpointPair returns source and destination endpoints from a TCP or IP tuple // and a command-line tuple. -func MakeEndpointPair(tuple BaseTuple, cmdlineTuple *CmdlineTuple) (src Endpoint, dst Endpoint) { +func MakeEndpointPair(tuple BaseTuple, processTuple *ProcessTuple) (src Endpoint, dst Endpoint) { src = Endpoint{ - IP: tuple.SrcIP.String(), - Port: tuple.SrcPort, - Proc: string(cmdlineTuple.Src), - Cmdline: string(cmdlineTuple.SrcCommand), + IP: tuple.SrcIP.String(), + Port: tuple.SrcPort, } dst = Endpoint{ - IP: tuple.DstIP.String(), - Port: tuple.DstPort, - Proc: string(cmdlineTuple.Dst), - Cmdline: string(cmdlineTuple.DstCommand), + IP: tuple.DstIP.String(), + Port: tuple.DstPort, + } + if processTuple != nil { + src.Process = processTuple.Src + dst.Process = processTuple.Dst } return src, dst } diff --git a/libbeat/common/tuples.go b/libbeat/common/tuples.go index 096643619c0..1e3ca847865 100644 --- a/libbeat/common/tuples.go +++ b/libbeat/common/tuples.go @@ -20,6 +20,7 @@ package common import ( "fmt" "net" + "time" ) // In order for the IpPortTuple and the TcpTuple to be used as @@ -155,22 +156,31 @@ func (t *TCPTuple) Hashable() HashableTCPTuple { return t.raw } -// CmdlineTuple contains the source and destination process names, as found by +// ProcessTuple contains the source and destination process names, as found by // the proc module. -type CmdlineTuple struct { - // Source and destination processes names as specified in packetbeat.procs.monitored - Src, Dst []byte - // Source and destination full command lines - SrcCommand, DstCommand []byte +type ProcessTuple struct { + Src, Dst Process +} + +// Process contains process information. +type Process struct { + PID int // Process ID. + PPID int // Parent process ID. + Name string // Name of process (or alias given by cmdline_grep config). + Args []string // Process arguments. + Exe string // Absolute path to exe. + CWD string // Current working directory. + StartTime time.Time // Start time of process. } // Reverse returns a copy of the receiver with the source and destination fields // swapped. -func (c *CmdlineTuple) Reverse() CmdlineTuple { - return CmdlineTuple{ - Src: c.Dst, - Dst: c.Src, - SrcCommand: c.DstCommand, - DstCommand: c.SrcCommand, +func (c *ProcessTuple) Reverse() ProcessTuple { + if c == nil { + return ProcessTuple{} + } + return ProcessTuple{ + Src: c.Dst, + Dst: c.Src, } } diff --git a/packetbeat/_meta/fields.common.yml b/packetbeat/_meta/fields.common.yml index 3deafbfd909..183fca16159 100644 --- a/packetbeat/_meta/fields.common.yml +++ b/packetbeat/_meta/fields.common.yml @@ -4,29 +4,8 @@ These fields contain data about the environment in which the transaction or flow was captured. fields: - - name: server - description: > - The name of the server that served the transaction. - - - name: client_server - description: > - The name of the server that initiated the transaction. - - - name: client_service - description: > - The name of the logical service that initiated the transaction. - - - name: ip - description: > - The IP address of the server that served the transaction. - format: dotted notation. - - - name: client_ip - description: > - The IP address of the server that initiated the transaction. - format: dotted notation. - - name: real_ip + type: ip description: > If the server initiating the transaction is a proxy, this field contains the original client IP address. @@ -37,24 +16,6 @@ the `client_ip` for non proxy clients. format: Dotted notation. - - name: client_geoip - description: The GeoIP information of the client. - type: group - fields: - - name: location - type: geo_point - example: {lat: 51, lon: 9} - description: > - The GeoIP location of the `client_ip` address. This field is available - only if you define a - https://www.elastic.co/guide/en/elasticsearch/plugins/master/using-ingest-geoip.html[GeoIP Processor] as a pipeline in the - https://www.elastic.co/guide/en/elasticsearch/plugins/master/ingest-geoip.html[Ingest GeoIP processor plugin] or using Logstash. - - - name: client_port - description: > - The layer 4 port of the process that initiated the transaction. - format: dotted notation. - - name: transport description: > The transport protocol used for the transaction. If not specified, then @@ -66,31 +27,45 @@ The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows. required: true - - name: port - description: > - The layer 4 port of the process that served the transaction. - format: dotted notation. - - - name: proc + - name: server.process.name description: > The name of the process that served the transaction. - - name: cmdline + - name: server.process.args description: > The command-line of the process that served the transaction. - - name: client_proc + - name: server.process.executable + description: > + Absolute path to the server process executable. + + - name: server.process.working_directory + description: > + The working directory of the server process. + + - name: server.process.start + description: > + The time the server process started. + + - name: client.process.name description: > The name of the process that initiated the transaction. - - name: client_cmdline + - name: client.process.args description: > The command-line of the process that initiated the transaction. - - name: release + - name: client.process.executable + description: > + Absolute path to the client process executable. + + - name: client.process.working_directory + description: > + The working directory of the client process. + + - name: client.process.start description: > - The software release of the service serving the transaction. - This can be the commit id or a semantic version. + The time the client process started. - key: flows_event title: "Flow Event" @@ -120,15 +95,6 @@ These fields contain data about the transaction itself. fields: - - name: direction - required: true - description: > - Indicates whether the transaction is inbound (emitted by server) - or outbound (emitted by the client). Values can be in or out. No defaults. - possible_values: - - in - - out - - name: status description: > The high level status of the transaction. The way to compute this diff --git a/packetbeat/_meta/sample_outputs/cassandra.json b/packetbeat/_meta/sample_outputs/cassandra.json index 41b94077f92..3e4f58e041f 100644 --- a/packetbeat/_meta/sample_outputs/cassandra.json +++ b/packetbeat/_meta/sample_outputs/cassandra.json @@ -1,53 +1,64 @@ { - "@timestamp": "2016-08-24T16:21:07.817Z", - "beat": { - "hostname": "Medcl.local", - "name": "Medcl.local" + "@timestamp": "2016-06-28T09:16:17.891Z", + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" + }, + "bytes_in": 110, + "server": { + "domain": "host.example.com", + "ip": "127.0.0.1", + "port": 9042 + }, + "bytes_out": 871, + "host": { + "name": "host.example.com" }, - "bytes_in": 88, - "bytes_out": 215, "cassandra": { "request": { + "query": "SELECT * FROM system_schema.tables WHERE keyspace_name = 'mykeyspace' AND table_name = 'users'", "headers": { + "version": "4", "flags": "Default", - "length": 79, + "stream": 52, "op": "QUERY", - "stream": 23, - "version": "4" - }, - "query": "SELECT * FROM system_schema.keyspaces WHERE keyspace_name = 'mykeyspace'" + "length": 101 + } }, "response": { "headers": { + "version": "4", "flags": "Default", - "length": 206, + "stream": 52, "op": "RESULT", - "stream": 23, - "version": "4" + "length": 862 }, "result": { - "result_type": "rows", + "type": "rows", "rows": { "meta": { - "col_count": 3, - "flags": "GlobalTableSpec", + "col_count": 19, "keyspace": "system_schema", - "table": "keyspaces" + "table": "tables", + "flags": "GlobalTableSpec" }, "num_rows": 879461 } } } }, - "client_ip": "127.0.0.1", - "client_port": 52749, - "client_proc": "", - "client_server": "Medcl.local", - "ip": "127.0.0.1", - "port": 9042, - "proc": "", - "responsetime": 6, - "server": "Medcl.local", "status": "OK", + "responsetime": 5, + "client": { + "port": 52749, + "domain": "host.example.com", + "ip": "127.0.0.1" + }, + "agent": { + "type": "packetbeat", + "hostname": "host.example.com", + "version": "7.0.0" + }, "type": "cassandra" } diff --git a/packetbeat/_meta/sample_outputs/dns.json b/packetbeat/_meta/sample_outputs/dns.json index 5458807aee3..4cd4eb6f275 100644 --- a/packetbeat/_meta/sample_outputs/dns.json +++ b/packetbeat/_meta/sample_outputs/dns.json @@ -1,152 +1,218 @@ -{ - "bytes_in":28, - "bytes_out":271, - "client_ip":"192.168.0.1", - "client_port":57522, - "client_proc":"", - "client_server":"macbook", - "count":1, - "dns":{ - "additionals_count":0, - "answers":[ - { - "class":"IN", - "data":"54.148.130.30", - "name":"elastic.co", - "ttl":59, - "type":"A" - }, - { - "class":"IN", - "data":"54.69.104.66", - "name":"elastic.co", - "ttl":59, - "type":"A" - } - ], - "answers_count":2, - "authorities":[ - { - "class":"IN", - "data":"j.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"k.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"h.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"g.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"l.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"c.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"a.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"m.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"d.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"f.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"b.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"e.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - }, - { - "class":"IN", - "data":"i.root-servers.net", - "name":"", - "ttl":18336, - "type":"NS" - } - ], - "authorities_count":13, - "flags":{ - "authoritative":false, - "recursion_allowed":true, - "recursion_desired":true, - "truncated_response":false - }, - "id":46497, - "op_code":"QUERY", - "question":{ - "class":"IN", - "name":"elastic.co", - "type":"A" - }, - "response_code":"NOERROR" - }, - "ip":"10.0.0.1", - "method":"QUERY", - "port":53, - "proc":"", - "query":"class IN, type A, elastic.co", - "request":"ID 46497; QR query; OPCODE QUERY; FLAGS aa rd; RCODE NOERROR; QUESTION class IN, type A, elastic.co", - "resource":"elastic.co", - "response":"ID 46497; QR response; OPCODE QUERY; FLAGS rd ra; RCODE NOERROR; QUESTION class IN, type A, elastic.co; ANSWER elastic.co: ttl 59, class IN, type A, 54.148.130.30; elastic.co: ttl 59, class IN, type A, 54.69.104.66; AUTHORITY Root: ttl 18336, class IN, type NS, j.root-servers.net; Root: ttl 18336, class IN, type NS, k.root-servers.net; Root: ttl 18336, class IN, type NS, h.root-servers.net; Root: ttl 18336, class IN, type NS, g.root-servers.net; Root: ttl 18336, class IN, type NS, l.root-servers.net; Root: ttl 18336, class IN, type NS, c.root-servers.net; Root: ttl 18336, class IN, type NS, a.root-servers.net; Root: ttl 18336, class IN, type NS, m.root-servers.net; Root: ttl 18336, class IN, type NS, d.root-servers.net; Root: ttl 18336, class IN, type NS, f.root-servers.net; Root: ttl 18336, class IN, type NS, b.root-servers.net; Root: ttl 18336, class IN, type NS, e.root-servers.net; Root: ttl 18336, class IN, type NS, i.root-servers.net", - "responsetime":77, - "server":"", - "shipper":"macbook", - "status":"OK", - "@timestamp":"2015-08-23T04:43:51.861Z", - "transport":"udp", - "type":"dns" +{ + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" + }, + "@timestamp": "2018-11-30T01:09:42.755Z", + "agent": { + "hostname": "vagrant-2012-r2", + "type": "packetbeat", + "version": "7.0.0" + }, + "bytes_in": 28, + "bytes_out": 463, + "client": { + "ip": "10.0.2.15", + "port": 63639 + }, + "dns": { + "additionals_count": 0, + "answers": [ + { + "class": "IN", + "data": "2406:da00:ff00::1715:432e", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::36e1:d64a", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::36e1:dd80", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::6b15:ed5f", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::6b15:edbc", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::6b15:efc5", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::b848:f22f", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + }, + { + "class": "IN", + "data": "2406:da00:ff00::b849:f5e9", + "name": "elastic.co.", + "ttl": "59", + "type": "AAAA" + } + ], + "answers_count": 8, + "authorities": [ + { + "class": "IN", + "data": "a.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "b.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "c.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "d.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "e.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "f.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "g.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "h.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "i.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "j.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "k.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "l.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + }, + { + "class": "IN", + "data": "m.root-servers.net.", + "name": ".", + "ttl": "41863", + "type": "NS" + } + ], + "authorities_count": 13, + "flags": { + "authentic_data": false, + "authoritative": false, + "checking_disabled": false, + "recursion_available": true, + "recursion_desired": true, + "truncated_response": false + }, + "id": 7, + "op_code": "QUERY", + "question": { + "class": "IN", + "etld_plus_one": "elastic.co.", + "name": "elastic.co.", + "type": "AAAA" + }, + "response_code": "NOERROR" + }, + "host": { + "architecture": "x86_64", + "id": "42f11c3b-3062-4874-9ea3-50e0e08c4434", + "name": "vagrant-2012-r2", + "os": { + "build": "9600.0", + "family": "windows", + "kernel": "6.3.9600.16384 (winblue_rtm.130821-1623)", + "platform": "windows", + "version": "6.3" + } + }, + "method": "QUERY", + "network": { + "direction": "outgoing" + }, + "query": "class IN, type AAAA, elastic.co.", + "resource": "elastic.co.", + "responsetime": 46, + "server": { + "ip": "10.0.2.3", + "port": 53 + }, + "status": "OK", + "transport": "udp", + "type": "dns" } diff --git a/packetbeat/_meta/sample_outputs/flow.json b/packetbeat/_meta/sample_outputs/flow.json index a7cb0bf8379..d51644e16d3 100644 --- a/packetbeat/_meta/sample_outputs/flow.json +++ b/packetbeat/_meta/sample_outputs/flow.json @@ -1,34 +1,75 @@ { - "@timestamp": "2018-11-15T14:41:24.000Z", - "destination": { - "bytes": 460, - "ip": "198.51.100.2", - "mac": "06:05:04:03:02:01", - "packets": 2, - "port": 80 - }, - "event": { - "duration": 3000000000, - "end": "2018-11-15T14:41:24.000Z", - "start": "2018-11-15T14:41:21.000Z", - "type": "flow" - }, - "flow": { - "final": true, - "id": "FQQA/wz/Dv//////Fv8BAQEBAgMEBQYGBQQDAgGrAMsAcQPGM2QC9ZdQAA", - "vlan": 171 - }, - "network": { - "bytes": 470, - "packets": 3, - "transport": "tcp", - "type": "ipv4" - }, - "source": { - "bytes": 10, - "ip": "203.0.113.3", - "mac": "01:02:03:04:05:06", - "packets": 1, - "port": 38901 - } -} \ No newline at end of file + "@timestamp": "2018-11-30T01:17:10.010Z", + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" + }, + "flow": { + "id": "EQwA////DP//////FBgBAAEIACfVnVpSVAASNQIKAAIPgtMmkVFbuwG7AAAAAAAAAA", + "final": false + }, + "network": { + "bytes": 7566, + "packets": 28, + "type": "ipv4", + "transport": "tcp" + }, + "process": { + "exe": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", + "start": "2018-11-30T01:15:50.119Z", + "pid": 3468, + "name": "chrome.exe", + "args": [ + "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" + ], + "ppid": 1860 + }, + "source": { + "bytes": 2008, + "process": { + "name": "chrome.exe", + "args": [ + "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" + ], + "ppid": 1860, + "exe": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", + "start": "2018-11-30T01:15:50.119Z", + "pid": 3468 + }, + "mac": "08:00:27:d5:9d:5a", + "ip": "10.0.2.15", + "port": 23377, + "packets": 12 + }, + "destination": { + "packets": 16, + "bytes": 5558, + "mac": "52:54:00:12:35:02", + "ip": "130.211.38.145", + "port": 443 + }, + "agent": { + "hostname": "vagrant-2012-r2", + "version": "7.0.0", + "type": "packetbeat" + }, + "host": { + "name": "vagrant-2012-r2", + "os": { + "kernel": "6.3.9600.16384 (winblue_rtm.130821-1623)", + "build": "9600.0", + "platform": "windows", + "version": "6.3", + "family": "windows" + }, + "id": "42f11c3b-3062-4874-9ea3-50e0e08c4434", + "architecture": "x86_64" + }, + "event": { + "end": "2018-11-30T01:16:45.645Z", + "duration": 3965826800, + "type": "flow", + "start": "2018-11-30T01:16:41.679Z" + } +} diff --git a/packetbeat/_meta/sample_outputs/http.json b/packetbeat/_meta/sample_outputs/http.json index abba940dda1..3542ba0e6c1 100644 --- a/packetbeat/_meta/sample_outputs/http.json +++ b/packetbeat/_meta/sample_outputs/http.json @@ -1,43 +1,70 @@ { - "@timestamp": "2015-04-26T08:42:15.822Z", - "agent": "vagrant", - "client_ip": "10.0.2.15", - "client_port": 52980, - "client_proc": "", - "client_server": "vagrant", - "count": 1, - "http": { - "code": 302, - "content_length": 258, - "phrase": "Found", - "request_headers": { - "accept": "*/*", - "connection": "Keep-Alive", - "host": "google.com", - "user-agent": "Wget/1.13.4 (linux-gnu)" + "@timestamp": "2018-11-29T22:49:03.308Z", + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" }, - "response_headers": { - "alternate-protocol": "80:quic,p=1", - "cache-control": "private", - "content-length": "258", - "content-type": "text/html; charset=UTF-8", - "date": "Thu, 23 Apr 2015 08:42:16 GMT", - "location": "http://www.google.de/?gfe_rd=cr&ei=aLA4VcKbI-iH8QeVjoCIDg", - "server": "GFE/2.0" + "server": { + "port": 80, + "ip": "192.16.31.23" + }, + "status": "OK", + "bytes_out": 593, + "type": "http", + "method": "GET", + "bytes_in": 379, + "client": { + "port": 10437, + "process": { + "exe": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", + "start": "2018-11-29T22:48:59.276Z", + "pid": 2928, + "ppid": 1860, + "name": "chrome.exe", + "args": [ + "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" + ] + }, + "ip": "10.0.2.15" + }, + "host": { + "name": "vagrant-2012-r2", + "architecture": "x86_64", + "os": { + "version": "6.3", + "family": "windows", + "kernel": "6.3.9600.16384 (winblue_rtm.130821-1623)", + "build": "9600.0", + "platform": "windows" + }, + "id": "42f11c3b-3062-4874-9ea3-50e0e08c4434" + }, + "query": "GET /", + "responsetime": 29, + "path": "/", + "http": { + "request": { + "headers": { + "content-length": 0 + }, + "params": "" + }, + "response": { + "code": 301, + "phrase": "Moved Permanently", + "headers": { + "content-length": 292, + "content-type": "text/html; charset=iso-8859-1" + } + } + }, + "agent": { + "type": "packetbeat", + "hostname": "vagrant-2012-r2", + "version": "7.0.0" + }, + "network": { + "direction": "outgoing" } - }, - "ip": "173.194.112.198", - "method": "GET", - "params": "", - "path": "/", - "port": 80, - "proc": "", - "query": "GET /", - "request": "GET / HTTP/1.1\r\nUser-Agent: Wget/1.13.4 (linux-gnu)\r\nAccept: */*\r\nHost: google.com\r\nConnection: Keep-Alive\r\n\r\n", - "response": "HTTP/1.1 302 Found\r\nCache-Control: private\r\nContent-Type: text/html; charset=UTF-8\r\nLocation: http://www.google.de/?gfe_rd=cr&ei=aLA4VcKbI-iH8QeVjoCIDg\r\nContent-Length: 258\r\nDate: Thu, 23 Apr 2015 08:42:16 GMT\r\nServer: GFE/2.0\r\nAlternate-Protocol: 80:quic,p=1\r\n\r\n", - "responsetime": 28, - "server": "", - "status": "OK", - "tags": ["service12.example.com", "vagrant"], - "type": "http" } diff --git a/packetbeat/_meta/sample_outputs/nfs.json b/packetbeat/_meta/sample_outputs/nfs.json index 0da67352f47..f2a7a06ebc5 100644 --- a/packetbeat/_meta/sample_outputs/nfs.json +++ b/packetbeat/_meta/sample_outputs/nfs.json @@ -1,41 +1,56 @@ { - "@timestamp": "2016-03-28T06:18:18.431Z", - "beat": { - "hostname": "localhost", - "name": "localhost" - }, - "count": 1, - "dst": "127.0.0.1", - "dst_port": 2049, - "nfs": { - "minor_version": 1, - "opcode": "GETATTR", - "status": "NFSERR_NOENT", - "tag": "", - "version": 4 - }, - "rpc": { - "auth_flavor": "unix", - "call_size": 200, - "cred": { - "gid": 500, - "gids": [ - 491, - 499, - 500 - ], - "machinename": "localhost", - "stamp": 4597002, - "uid": 500 + "@timestamp": "1937-03-13T21:53:13.273Z", + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" }, - "reply_size": 96, - "status": "success", - "time": 25631000, - "time_str": "25.631ms", - "xid": "2cf0c876" - }, - "src": "127.0.0.1", - "src_port": 975, - "type": "nfs" + "type": "nfs", + "rpc": { + "call_size": 212, + "auth_flavor": "unix", + "cred": { + "stamp": 1459253713, + "machinename": "ani", + "uid": 500, + "gid": 500, + "gids": [ + 500, + 500, + 499, + 491 + ] + }, + "reply_size": 152, + "time": 1092074000, + "time_str": "1.092074s", + "status": "success", + "xid": "00000008" + }, + "agent": { + "hostname": "host.example.com", + "version": "7.0.0", + "type": "packetbeat" + }, + "host": { + "name": "host.example.com" + }, + "nfs": { + "version": 4, + "tag": "readdir", + "minor_version": 1, + "opcode": "READDIR", + "status": "NFS_OK" + }, + "status": "OK", + "client": { + "ip": "127.0.0.1", + "port": 50676, + "domain": "host.example.com" + }, + "server": { + "ip": "127.0.0.1", + "port": 2049, + "domain": "host.example.com" + } } - diff --git a/packetbeat/_meta/sample_outputs/pgsql.json b/packetbeat/_meta/sample_outputs/pgsql.json new file mode 100644 index 00000000000..d350044c3b4 --- /dev/null +++ b/packetbeat/_meta/sample_outputs/pgsql.json @@ -0,0 +1,42 @@ +{ + "@timestamp": "2015-01-10T19:45:43.161Z", + "@metadata": { + "beat": "packetbeat", + "type": "doc", + "version": "7.0.0" + }, + "bytes_out": 202, + "bytes_in": 25, + "type": "pgsql", + "agent": { + "type": "packetbeat", + "hostname": "host.example.com", + "version": "7.0.0" + }, + "server": { + "ip": "127.0.0.1", + "port": 5432, + "domain": "host.example.com" + }, + "pgsql": { + "error_code": "", + "error_message": "", + "error_severity": "", + "iserror": false, + "num_rows": 4, + "num_fields": 3 + }, + "client": { + "ip": "127.0.0.1", + "port": 37881, + "domain": "host.example.com" + }, + "method": "SELECT", + "status": "OK", + "responsetime": 1, + "response": "a,b,c\nmea,meb,mec\nmea1,meb1,mec1\nmea2,meb2,mec2\nmea3,meb3,mec3\n", + "query": "select * from test", + "host": { + "name": "host.example.com" + } +} diff --git a/packetbeat/_meta/sample_outputs/psql.json b/packetbeat/_meta/sample_outputs/psql.json deleted file mode 100644 index 153d56c8b7c..00000000000 --- a/packetbeat/_meta/sample_outputs/psql.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@timestamp": "2015-04-26T08:44:15.704Z", - "agent": "vagrant", - "bytes_out": 517, - "client_ip": "127.0.0.1", - "client_port": 40761, - "client_proc": "", - "client_server": "vagrant", - "count": 1, - "ip": "127.0.0.1", - "method": "SELECT", - "pgsql": { - "error_code": "", - "error_message": "", - "error_severity": "", - "iserror": false, - "num_fields": 11, - "num_rows": 1 - }, - "port": 5432, - "proc": "", - "query": "select * from auth_user", - "request": "select * from auth_user", - "response": "id,password,last_login,is_superuser,username,first_name,last_name,email,is_staff,is_active,date_joined\n1,pbkdf2_sha256$12000$xiwL2Qa8fIAD$A1awK5qtRhxrS0+fm5/IvDzaMLv6uhyERADfsaICtuI=,2015-04-13 05:51:11.041624-07,t,admin,,,,t,t,2015-04-13 05:51:11.041708-07\n", - "responsetime": 2, - "server": "vagrant", - "status": "OK", - "tags": ["service12.example.com", "vagrant"], - "type": "pgsql" -} diff --git a/packetbeat/docs/fields.asciidoc b/packetbeat/docs/fields.asciidoc index f8a93649d21..a5a73bde25b 100644 --- a/packetbeat/docs/fields.asciidoc +++ b/packetbeat/docs/fields.asciidoc @@ -1322,155 +1322,115 @@ These fields contain data about the environment in which the transaction or flow -*`server`*:: -+ --- -The name of the server that served the transaction. - - --- - -*`client_server`*:: +*`real_ip`*:: + -- -The name of the server that initiated the transaction. - +type: ip --- +format: Dotted notation. -*`client_service`*:: -+ --- -The name of the logical service that initiated the transaction. +If the server initiating the transaction is a proxy, this field contains the original client IP address. For HTTP, for example, the IP address extracted from a configurable HTTP header, by default `X-Forwarded-For`. +Unless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients. -- -*`ip`*:: +*`transport`*:: + -- -format: dotted notation. +example: udp -The IP address of the server that served the transaction. +The transport protocol used for the transaction. If not specified, then tcp is assumed. -- -*`client_ip`*:: +*`type`*:: + -- -format: dotted notation. +required: True -The IP address of the server that initiated the transaction. +The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows. -- -*`real_ip`*:: +*`server.process.name`*:: + -- -format: Dotted notation. - -If the server initiating the transaction is a proxy, this field contains the original client IP address. For HTTP, for example, the IP address extracted from a configurable HTTP header, by default `X-Forwarded-For`. -Unless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients. - - --- - -[float] -== client_geoip fields - -The GeoIP information of the client. - - -*`client_geoip.location`*:: -+ --- -type: geo_point - -example: {'lat': 51, 'lon': 9} - -The GeoIP location of the `client_ip` address. This field is available only if you define a https://www.elastic.co/guide/en/elasticsearch/plugins/master/using-ingest-geoip.html[GeoIP Processor] as a pipeline in the https://www.elastic.co/guide/en/elasticsearch/plugins/master/ingest-geoip.html[Ingest GeoIP processor plugin] or using Logstash. +The name of the process that served the transaction. -- -*`client_port`*:: +*`server.process.args`*:: + -- -format: dotted notation. - -The layer 4 port of the process that initiated the transaction. +The command-line of the process that served the transaction. -- -*`transport`*:: +*`server.process.executable`*:: + -- -example: udp - -The transport protocol used for the transaction. If not specified, then tcp is assumed. +Absolute path to the server process executable. -- -*`type`*:: +*`server.process.working_directory`*:: + -- -required: True - -The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows. +The working directory of the server process. -- -*`port`*:: +*`server.process.start`*:: + -- -format: dotted notation. - -The layer 4 port of the process that served the transaction. +The time the server process started. -- -*`proc`*:: +*`client.process.name`*:: + -- -The name of the process that served the transaction. +The name of the process that initiated the transaction. -- -*`cmdline`*:: +*`client.process.args`*:: + -- -The command-line of the process that served the transaction. +The command-line of the process that initiated the transaction. -- -*`client_proc`*:: +*`client.process.executable`*:: + -- -The name of the process that initiated the transaction. +Absolute path to the client process executable. -- -*`client_cmdline`*:: +*`client.process.working_directory`*:: + -- -The command-line of the process that initiated the transaction. +The working directory of the client process. -- -*`release`*:: +*`client.process.start`*:: + -- -The software release of the service serving the transaction. This can be the commit id or a semantic version. +The time the client process started. -- @@ -7094,16 +7054,6 @@ These fields contain data about the transaction itself. -*`direction`*:: -+ --- -required: True - -Indicates whether the transaction is inbound (emitted by server) or outbound (emitted by the client). Values can be in or out. No defaults. - - --- - *`status`*:: + -- diff --git a/packetbeat/flows/worker.go b/packetbeat/flows/worker.go index 2cc97c24d08..6c9d3b2455f 100644 --- a/packetbeat/flows/worker.go +++ b/packetbeat/flows/worker.go @@ -344,29 +344,49 @@ func createEvent( } network["bytes"] = totalBytes network["packets"] = totalPackets - fields["network"] = network - fields["source"] = source - fields["destination"] = dest // Set process information if it's available if tuple.IPLength != 0 && tuple.SrcPort != 0 { - if cmdline := procs.ProcWatcher.FindProcessesTuple(&tuple, proto); cmdline != nil { - src, dst := common.MakeEndpointPair(tuple.BaseTuple, cmdline) - - for key, value := range map[string]string{ - "client_proc": src.Name, - "client_cmdline": src.Cmdline, - "proc": dst.Name, - "cmdline": dst.Cmdline, - } { - if len(value) != 0 { - fields[key] = value + if proc := procs.ProcWatcher.FindProcessesTuple(&tuple, proto); proc != nil { + if proc.Src.PID > 0 { + p := common.MapStr{ + "pid": proc.Src.PID, + "name": proc.Src.Name, + "args": proc.Src.Args, + "ppid": proc.Src.PPID, + "executable": proc.Src.Exe, + "start": proc.Src.StartTime, + "working_directory": proc.Src.CWD, + } + if proc.Src.CWD != "" { + p["working_directory"] = proc.Src.CWD + } + source["process"] = p + fields["process"] = p + } + if proc.Dst.PID > 0 { + p := common.MapStr{ + "pid": proc.Dst.PID, + "name": proc.Dst.Name, + "args": proc.Dst.Args, + "ppid": proc.Dst.PPID, + "executable": proc.Dst.Exe, + "start": proc.Dst.StartTime, + "working_directory": proc.Src.CWD, + } + if proc.Dst.CWD != "" { + p["working_directory"] = proc.Dst.CWD } + dest["process"] = p + fields["process"] = p } } } + fields["source"] = source + fields["destination"] = dest + return beat.Event{ Timestamp: timestamp, Fields: fields, diff --git a/packetbeat/include/fields.go b/packetbeat/include/fields.go index 0557c6a824c..ca405f535b0 100644 --- a/packetbeat/include/fields.go +++ b/packetbeat/include/fields.go @@ -31,5 +31,5 @@ func init() { // Asset returns asset data func Asset() string { - return "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" + return "eJzs/Xt3GznSGIz/v58CR/ucn+2EoiTfxuP89k00kjyjjC1rJXlnnyebI4HdIIlVs9EDoEVzknz396AKQAPdaF4kyjObV95zdkSyu1AACoW61y65ZYv3hGXqT4Rorgv2npwcXf6JkJypTPJKc1G+J//Pnwgh5gcy5qzI1fBPxP71Hn6B/9slJZ2x94ROWKnhGw/yMPhqIkVdvScv7cfEOObf1ZQhIDsOyUSpKS+JnjKSU00JHYlaw0clxnpOJSOs1FwvBoSPCS0XA6KnVJNMFAXLtBqQnGn8Q0giRorJO6YIu2OlVkSUhJKpUBp+1fSWKTJjVNWSzeIHhuTkK51VBVOEl1lR54z8wKhWQ5ylIjO6ILRQgsi6NK/ZoaQawgrCrIb/yc1LTWlRkBEjlajqgmqWkznXU4Ms5YUiYgxzxLWQdVnycmKgmi8NOsFkJJlPmWTwE0yLTGlVsZLlMKcpC2dE5lTBPMuhXfSxELoUmoXb4Kb6npzikBlVzOAEUyZjIUkhJmrQ4Dg0REC4ImNesBGjekg+CEkOzz8NCNfmBz1lHn48Lbu9tKr2zIR4xoYBIfByLOSMGkohuWCKlEKTbErLCSN87EECcXBFlHlHT6WoJ1Pya81qM4JaKM1mihT8lpGf6fiWDsgFyzkSRSVFxpQKHvRQVZ1NCVXko5goTdWU4JzIJSy8W0K9qNh7pHC3qO1TEp4UQxRclP57Qgp2x4r3JBOSBd8i2Fu2mAuZB9/3nB3z728IOiKfYYwFIQx39z15O9wf7u/K7GUaT/P/j4HkmSGVpRgaRsCV2U4KWNgjTUtzYib8jpVEC0JL+zo+bX+esqIa10VIG0jm0k2c6LkgHyydEl4qTcuMKWJ4SeuoKTO4OW8RrFGtDVeoZ7QkktGcjgpGFKuoRDLlipSM5eYAlmQ+5dm0O1wE0BFvJmZm8LEUs8SanI5JKYg7aLAMeALdV2KsWUkKNtaEzSq9GKY2fSxEervNTj7Gdl8tqjW22x13MwBRmi4UocXc/MfvAy1zoqaiLvKGDEaLgE/WiuXDeMlKz7r8DjTPzwGWHWbEmkeAj/OxIZQIXD/RRAQzo9mUlyy9/BZEeg94/hg78KXkv9aM8NzclGPOJG6HOV6wDs/5mIiSEfaVK61eJPbnxKFvmDpeAvD+3O0GsHyeJ6f8jr4ev9nfz9NTZtWUzZikxXVq8uyrZmXO8octwIkb4yFrgCwpJ6W5jopiYS8hRWgmhVJEMqWpNIKG4Q83SOo8v/G31rLFGXcFqhFVLJanfmi+seLUwWpxyoAhimknSplzVTgxBJmToWFLv1pUuPRwBSvmHjSPZGI2M/IQTtdAMVsBsgqKU+vdh+Ecd/6b5jOzbrNqp7PFOdWrGZJkv9Zcsvw90bJmqRXeebl/8HZ3/83uy1dX++/e7795/+r18N2bV/+xsx7xHFPN9gyaRs4qAzFLSD7hpZHdEtTyAWUkJ2hqe59ZOTYN0MhmE1YyaWAODL+LQBrBB97g+Ki5ehIjX9gVwUWHi8/sVbhFXd5PJ+renKdZ6f/xj51KirzOzDr+Y2dA/rHDyruX/9j5n2uu9UduRNuxG0QBSzd3vaYTwmg2xWn0zKKgI1asOw8x+ifLdGoa/4uVd+9JM5GBEU0LnlHEeCzE7ojK/7PejH5mi707WtSMVJTL9vqbf0cot7iZ0jwnM2bkgUDw1cLtH7nEGxCkYKsclUxpFtMKzs5oJ0VBYHw8w0oLQxpUuSVexuxvcpHdMnkDN+/N7Tt1Y5e4Z/1nTCk6WVeI0Oxrcvl3fmJFIcgvQhb5mmTTOWzM4WIPged95ifzpP05JWWVROgpk2ZDQHhIwov3LBNlRjUrY4ZFSM7HYybN0bZb0PBbbQ7yWDJWLIhiVGZTI0UOjZA3qwvNqyIGZcdXeEGB3LdwaGRiNuJG3+OlFnCLdafn9igreEdPPwq/W09RP7SADE/L2RhGp7hSvOSaUy3ghqWkZHou5K1Zo5LBeUJZHLdKsgmVOaheRgUTpRoET6J+NuI5l/gFLci4EHMiWWbYAyqZV0fnFhyKww1mHXTMF+bxABlQLRQrc3z88t/PSEWzW6afqxcIH8mhkkKLTBSdQZBjG4GgNZyEy4mZI+d0XLcYWtJSUUBgSC7FjHkV1VAdXMRMzsiOu2KE3DF0JtmYyWj4sjUdhaqz/dle3riHI+atC4ERBYYlBpVy4nawAR7ijJzXEksoF9Sqhuk3pgxeGpT+iewTDRvWVGENSSQBpllHw9saYIZacEd2gZ14Tng/7ZtXa/Kn6MElzOf03PBsyZS32uD69bN6c0KF9OecnJ7fvTZfnJ7fvXWwWB+TrYTUa86gEOVkvTmcC6mXYu9ZPM0eQ0P5dHi01iI6NHIxo/xRLCiWLnGA1uh/Jp+YljxTHXxGC83WFTxau+LvvYN3r9dD8QczGBq6xlLMwiNrJCVzqgPzVJeA4Cw9GNuXa1IWjrYWuh1UJyzUv+1t9WP0Zeu6WoHNj0x4yzItSUalXIR2ZUpUxTI+5hkpBAp8RDLkQ2hxAuYTi1rS4BnbKZnkd4Z1mfnS0rAIGHXYWd6QbZGAdQVfeenWIhQNnt46D52J60rwFsJL1oeQj6KccF3naG4pqIYPsVXFE8Gz/0V2ClHuvCe7370avj14/e7V/oDsFFTvvCev3wzf7L/5/uAd+T/PUvMxMhkvWamvW4bGVbPqnucVcwoNjn7UnimdCamn5HDGJM9oGu261HLx6Egf4Tgwag+uR7SkeRJJySZclI+O4wUMswzFv9ZsxLLkOnL9DRaR66Ur+EmUWjJaLNtorsR1JvJvstmnl5+JGatvww+XbPa3wNNu+Eo0d/96lMK0b7sTVr57o/hFMbnrVJLgSdRGHBMdEGsJRpFSjMlE0rIuqDQUY5UryfBaGP6pu11o9vTWd+QuXOJlkrFSM2k1hXEhhCRlPRsxCU5KsAU5mVy1QCOKBammC8XNH867mTlSVh10zgTYzc3jxQKVUl4SWmsxg5trwoSbd8+OjYTSotzN7UltlEVR521dsflqPVXxA963wTWKEoCowUHJy7GkSss603XoxWwWxtoeY8/ISsfl2ApraK9XoWeHluTk6CX6Uc0tN2Y6mzKFewd3Ng+GR/dwg7O56GODQuSY5srb/2MkPEBZl9axLNlMaO8vIKLWiucsGCuNHSXWTxqCDF2p8LKlvtj+gWAbUGDasMOHHlo7QLxwm9t3KynueM5kV9hMHHlPjSx7+TAhPrrwYcYOEe/GD41iLHs5IJOMDYiQMaPhE65pITJG27qAD3u4o7ygI16Y6+w3USasX8umWqtdRpXePcgeNuPDAA1i0DCkgNYmIEmg9WYzeyaDN8laM1hpDPYzW28C9ma5D9bOGTd8oAPJo853D16+ev3m7Xfvvt+noyxn4/31JnFqMSGnx478YAqRQ7Af/7TDfTsuMI9acF2tg5z7Ne0dvs/q6pfDGct5PVsP8U+OOwVu5DXwphnIb1ujibdv33733Xfv3r37/vvv10P8quHiiAvE7MgJLflvNk4g9xZk65dcNCbj+KI2QgCH2CNC0XC0q1lJS01YecelKGdpi1NzIR7+cukR4fmA/CjEpGB4n5PPFz+S0xxDpND4DS7jCFTjOk2ZlfGC8ZzeSQutr9eTGPxbsZXR2gI7zpHAmumU9zY6BM281iSsRC0zIKYATMvhOWVFZcRmFFvwxhxRFRCNH0M5PX9hGJXmjbaxoWnSvv1YLOACwZMZLenE3OjAY/00ku5p9AD18K3HDFbwaBHe9lH58Wd08rhMM5QjYDRvQkDU5lSRUc0L7YWjHiQ1nTwWjs1hsRjSvnvyMVeqwaLRtjsI9Plne1Ho+Gjxi+v73H+wOB33pTcoM6V5GdrXLAc77vywHg8L3lvDDRMMD3qqB4PG2j3re0kAXe6AKUMPDHK9JpSX/CGdJ8FS/Kt6UPqn8O3dKKtxeTxfSkiu/2oOlfBEOjcFHKA/sFdlCc4dfJ9cK0+ule6snlwrT66VdRfxybXy5Fp5cq3c17XCvNAT5d+RtRWMT0zT3fBm9NerFgbY75Sc1BuPvSw8/+jSjYs7iOHQmYDZKaLFkNywTA3tQzeYGSTjQGdzqc5qpTFCErapL+zZ/Ptlykrya83kAiLfMKjdKxS8zHnGFNndtfboGV04hMwCq4JPprpYxIfHh3sGMwIYMCtEszByGy81m2C2kCI0/6dBGyW2CKDKpmxG/drYe7Z3SmBxrCUGnNp3uCIHkOY1YpoekKSRJ3igAeoJVUrRsuqdBF+tndfZmNYySJuqJAPpFeCDukLLBbnlZT40jMbMdIaRoviAngYuNMxwNFtTMHSQmU10SZ0Qbomhu+3USK4VK8ZBLkSJ8KPVXN+/9a3ydcY2k7OL65air5edTjNmT8B0c6Hnj5I7hmMb6I6ro92yuxKeXO864c0nd/dJQ0Z6SRmgDfGwr7rHBl2ICUErteRZRHVDcgi/xiHTTvFxNGkmGGQBKzFjeoqzpk1q75B8bOLdgeu5rGQIHuYzZm5h50oz3xoQzds+mVmMw8h5B4S6pFgCOU3Ob2594U1QN2q9ZMQwgtspo9QZm4xiF6ql4GFIxoSPmJ4zZsawsYGGnVMXNowD2NhqTGzOCqHMTA7dUq9eVmc1EpIZoQH0kAJgUc0mAj9G6d8GifSCpnOqo3UNSaBZ2hmbCbkghv0ZAA5Q3spFv6uLkkn06PImK90+pjJamolCZvr9LvpHZV2nx2brvcHT89975AeaG6GL6Xas1uacG/jRzdqX+jfhd+CAax/6uTmXzjsZJe04iBEsd/UMwCprANjTE4hvTpvG6yzErfHoRUANf7qBJ24G5EZpqpn5gxZUzm6G5BcqzQGAdP5xDXE2XjoRYyOtDMg8Fj2qgoIRyQZOGOHZ5mbRLGOVhpxnG0OBt5OTcAakKhhVwDAjkGCFzmjdFpY9IQDePRcMntDFo1wyyCfsCH3b70WGKZ9MbSZC+gbo2bnTmA64QkYEaQ9m26e0tHs4xMyQm4GL51GsVDYJvlFGaExWFv0GTy/LUpcZsgYZxBvGtkAGEcRasQQZpGihNromeCqBx6apAmf2GDQBCel4M2W00sB5ba75UibhdU+bDNTQBy9jYvAE0Bz8KY0tkJYa3NbeBNcLHHjg9bs0z81Ztxf2LlzYLL+Jt/JmzAu2m0lmrs8bTBLCtESumoxmd3/amXIz1gwU7uR5hT2qqFJmXXcxHTq9UaLWmXg876OZjR1iFSs/DX4OdouWdrsHAQmrOMyvGSE2pphj6XK5mvsfH7Y7perMbI7NpBxTXtSSxYw5gtnPpDc5kTHIXia95om0c0hv8GMVj7hgIAGi4G1Xpe7J3DzHGdE7AYE1PsKhyYM2BAtmpD4VSuR18eg1T3AUa6taq/IHlh4ImUn0RgBVeRsVVmkQ0teuSR7h2UL9WqQXw6Cm2Lqe0nuvhh2mz5whSkPUaGG8sc/ekOeGnSmmyZ6VshXTL8yqxLM3ekBsUKlH5i0jnONyASeOTnm4zCjum8VGq0rL3mOTqXnZIIF1kMAU5b+y+20IGLEets3mkQTUc8IUu2OS63UloD4P4853a+ZUX9rxWleaQ6Ml3PwytUbfdPyaf8uKCjMGLsLScLgg5s1rgT732uzPM0XqimjR4rrR/WQ44ozeMgI6lR2OM1e4olRcadAq0c63tBiCTbot7k35fyZfDBHpuqSaQV4wV774EMcKVmoq5iUGmGW6WJAF04Zc/zfJBRZ6EPI2AmnkB8PbFZmzooh+OlXk//fng5ev/4sLcPPWNR9R8r+haISQtwYROFFgyWhsZBFAjErk2a1KUunOJavIwfdk/937l2/fH+xjPObRyYf3+4jHJctqs934Kdo3s3NGCkHRTuITB0P74sH+fvKduZAzdwGNayOqKC2qiuXuNfyvktlfDvaH5n8HLQi50n95OTwYvhy+VJX+y8HLVy/XPAiEXNA52Mt8EQAxBt+B9OT/xYZx5mwmSqUl1WgIQjsv1ymtwrJ1vJ0sVfAyZ18Z2rJzkV0HQeo5V2b7c+RYtDSPj1gLIlYSYDnWoOG+ZpY0zIh5v/nNNdpnbsLthbHfkzEtIqG9QSP8rXNoplRNHyTeNdTVBF+n/jr84eh47Z37iaopeV4xOaWVgpp1UMVtzMsJk5XkpX5hNlPSud0HLcxygQzVYjhk7c31F2gt21EF24k1OraAIx5sGERJS6FYJso85R44tXV6hqAiAI3hZ1bmQGK3peFJwK1QN2iqbbU9E45lZ8zzbMCkRNrFEZpQ2K68yGds7WyJe2kE/mg1kwhqLUaFd54p4ssQNTUGrcEuvnUs2rHmX0hG8wV5zoaTodGhaF1ocrlQhkg8YPUC77IInqhsVQuIup5zlZJrDxu53o+PowNneE+oOeaiBPPl6bHFY+eklqJie4czpZnM6WznRawS0tFIsju0p7pXLq92XoCJtiQ//fR+NmuuZk4L99Tu/pv3+/s77RpZ3lSDSuaaVN8q8rRkS60yjNA7CVjJYkr24T6Jutl0I4lzpXmZWQv2fwt+szVCgq/c4B2JxCrhcHvah4euOg2gqrD6YEMVjkOn5SZbkKOFDLKfgpcoabYmzrEyVFjxMII5WgSF7iRDWgdXU0aLIblp5nmDnoWwBqv/Ld6ar1rSTLvrJcRw0No3j6yfAg8rWTX7Y2vpZVijr6qMHCXA4WBuYDTKGAUIPXyJzenwrOaRBL6hR8MM0HDHNuZdolxBa64IIaxfvPlm/f3aD8JZNFyrqWrY1QkMm92AhW562JCNrzxq1uRkGEdykWim+Z2R/s06jblU2tWu7ZsY28jmv+m0zC21clIwVDglP40IoplSQVfPSHJ1e61aLHAZYxwXgq7pob3g6pYAbCxny0VHQ7O8W1nBnChRgLnHVTp0/74oRhailrYw0DPltSErEpjTtnKK16WQsw02cIO5noGtkv/GchhvxbQH3l1WgNS+b3jIwf7+koqzM8pLDPXBKrJmOUAfBWMtWOnNBWwLJ6HxTyk+ad0GDXIKKvkBmDnFoieKMUKt2RWmgmsbVFa05aASDu4xL1pFIJ0z27q7PzQP9K3jIUBpe0yJNY3EPixwOisyMiKeY4XWkWu+h2Ab55YE+wZgPgQ0XBk6d8lRpUTGm2rXoDe60l1RnSlctD1rM3E+VCDiAdFToZgt0IfWahjs1Mnj5JMouRZwPfyPD6ef/qcr5gf2MJvaDNW9IHwETb3OntpNzqDjMcPLwjzenkNYD9IafTbyyDYB5LpRoPoOTFoSjrb5nBqkhE3+LuLD2tR7lBOmr7c15hWAgymA2KEWs4KXtyo5NgwQxZg9YOSQOcBueuidIw4H3F6rtCjEnDCqFmaNNANSGS0ssTkQgfXDa6eVVdLaCxravx8wH5gDOJPBxDkgOZdw1uySvkguac6iagAPGP8YIPVkSy4lKV6GMUAPQOHUAGpMWC7gBzlW6f+2fCaFSh3ENmyJtow8Ct4Do199OT1+gZzE3qZBpNbzS/ixWSwi5mWrFpc3NM7DDNWHUg1AewYmcNlJwvNpH9tZmnPJZ1QukLfBmvzYmnZ69CglY2vjhyntvWPP7k+e/vDvv329n0bok6HZcNd5SUSmadGyxSZRU/y3dVGLjERdGjCQzNCQPmVYiLUtCiPS0Dx3asyNgXZDeCyzgJP4Js1iZlFm8nIkI3k8QvKjkZQhmAoWyUZKgBA9E7k5QXly9OwxRp8xTTGmHDzXeULYCgnW5UgFX60fTYiEGkQTzpiVBZtIWHhGWZFSGhZYsDtadiKDo0iqLUR9bcfi1h+0inN3BfKBbe9VBdVGyvwdUpVD5yOgltj3oOWD3fafmm/WrZDrqpdEMratckoyMatqjVGNtvwHRI1DRF/QJiZhuwz7xDRSKnaFKYMQxbgZDBZ3KFeHMJqZ2sruLmZxSmU+p5INyB2XuqaFK76hBuQYKgQE1RBQ3fm5HjFZMg3G1JzdN+HYzCpNDA/3Qv9kYYdVRVLmGx2U/HdWg7nzd944DG+gPr6ZumS6lljiac1iJY81w7O1ZgfpmtbGB/MK5hTM5UvJvzq91Kbf1EXLI/5rTQvg4jbfF2bmgn4NMjbYqYkxMtIKhiMpc7Zb9ZdYxnNfNhuVZC3MO33VFh4zqBXPc8rCd6g8oTpPnu0qgnVUBmBAsM48z9/NFcDLybguImC8RAvMWoVd3kdJH7XzTt5APw7YwmF3kbadxA8cg1cu9fzb5rz/ZI/XitEfu7tNz/H6IKQtseMqkNleENYiEtVfM6CgRdWNr5F0E5vnTsfkbjZwhVuCTDnPfgeh3T8o6BMYdSKIDRGuQXg+7lJmU64ZVOy796I2Dt+v795ev329plP3c8Uk1U2zrgiZRKK7CGVce5k3MC4BRvDEZknv5vB9vmw3q0uHBYsW4uHOSlaDd/99BF2L6tquadsrb5avAqtU/Mqu7wrX+rrTxGoXWO912LaP3Cd33klyEfBHSD7t7LsbmDyHLm0ZK7VQA1KP6lLXAzLnZS7mbft2U9iIyjkvHzGTtiHvTzQzRPL3nQdMFhX6BLZjOuOtS/ih+OZsxGm5CbaXFg27FdCbJp9SPSAIawCdLkYqD7clMZlu8unDZ3OwPzx4OXy7K7OXD9kAl08JQrykc6K0hJKEiWncGsm32OosXg9fD/d3Dw5e7tp8gYfMBfFbY0pPxUISu/tULOSpWEiM61OxkKdiIU/FQlooPhUL2V6xkKnWLSv0T1dX5/ab+1ZhNyB8TMt9K5Zig6vhjOmpeDTT8k9aV24ogkMl49J/PLkakPPPl+b/v1wtxxhaack1K5PfK3EJ4Td5V7DebvgU+maX1fu9vVEhJkP77TATs72+mahKlIoNlaa6Vm2es2o264cb2+XH0QiO1mE7fhav91+vwHck8kQWy/YyAcd1UcBiNkibIZPYYq/BuZBFT/p5bz2cLZK2HaOnNkuiJkshJjE7+Oi/WH78m/6DYbp5UwDinmwAlqS7RA+3rn0Uk+ZmcFGjfamd0EePRRmyvxxenN0MyM3JxYX5z+nZh883yWU+ubhIT+3ByUD9WTNwwYBR+dPCTCxU6TZKxuhdxtbRaFrQ+qC+oBcmKBpRWCQcpOCJCNyIjTF7ueAa/Via1BCg7BPPKyqTdYpO0d8gqa96RG7sEDc2aB8JNfRMGJ3Ph+1WcdwrCcnDQgoTeVt5vHbyg84EW8ZWdI1M6R3zMf7K0Bi6qjNXvqmqCs5ytNyyMhPQztKIGmweC1m8ZAp6ftzZtqEFoyXktq3sSnqvVCGihM0BetbJFfq1ZhLcMNY6ic6VtdKFIlZko/ZidnQWfbm+l9yFAHabimZiNqtLu+YYaCbumHQMzXo/ZRxEaH2ftiem/elezlUH1kcyt6MAnUXingz00f3dvls+WqGhoJYgyjYNbcRmt0hJ8Qrkr1/4mKcn8VgullP0o36+PIUwm6LVet78ZgmOfKQLJodQDnoAxaDN/1+ybEDOTz8NCNNZamLm8fSUOC3pNaoMj7U9hJwenh2Sc9telpzBaOS5kwbn8/nQoDEUcrKHkcZQm2jPNaTdRfy6Xwy/TvWsaBnACbnUtMypzCHw2NUO8N1th8hqaMEnJaaaIoGfMf2hEPNOU3JC1AfsyIsHCBJd8FS6Vrap+SUJ7G0PXUlaqg2Kdm+0/JeQr6084Qc7bpMoS6UZbQoKMPIzwg8VzlhhdviSwpAjef7l+HxAro7OkSR3T48+nQMtDl+kVuHq6Dy9DkEX8scixkOcFHILNLQGo1racMHccsS1pJIXCxsBj2Ua4rWY8nKi8G6c8UwKF32Ni0sLJZrknvBhdbuo2IDw7Nc4a21MMzYS4nZA9JxrjcEDITtwarfiurY3dFME8I6VeQvDJiLcp2Ixo9zkxPkyfI4Q3oJ7uWGDp+cYcKli9My2Y9fqOZcuTS9J7Ienn9Lb7I7io8jT33lW6YZBsxxhX4egMw1IAcT/T5qZNfaknMAqUlzTc/GNux9jMscOuJP+gu7a47ELw29p5UaQwNSeRmB63+Jo/4nwciTqDqf7T0TUOv0DLzWTsZqAP5hzmfyhLiHdtosjFCad0aoKSlraqnpGytmFLjRk1qQ42HqEAy/GwAUZnxosgeII2cB5pgi4JMzi3XE27yuRmsbELbWQpGKSz5hmsh+z1hEJsGxjFqFk/gsRCT45zw2VPFHhpnUocSzknMqc5dePE/4SNLLwCWM2cj74yapflRRf0/aIg+9fDg+GB8OX6VlYMVgvrh8vkPMQcvmx9iTgDxpG0Frg9BwLI1peR62YQP3c2oyCNLbQWJAfeqWUEi1EsUsnpVCaZ0RZISXsjRVTdCHmKd3yI6OyxFwtqr1JbcL1tB6BMc1sNRTv3fOLucvzXVWxLLkjzw7eTz//Z3X2+qf//OnHN5/+fe/d9FT+/fzX7PV//PW3/b88i1F4lI4Wy+xdQtPChnsDswarI6z1SBhVxvHInoIAN7ZBBECw5anCliHue1cdYEBunKRkf0KS5pKoepZcwFdv3/VcdA9pmbFyTSz0B62KhZFYl+aXxMr4H1euzcvXXY26FcDjQpbib9eMQS49tG6yX8UyTgvHWwc+mwXDNRupz2YX+VZ1OdMs0wMHGR7HxMDVsHadmmBvk6BQkhMunRxHSVYrLWY++BjhQA9DiCe182plKIpyzCdQrk8LIutyg3kqMdZmoKCKmwuAHnPJ5rQo1MDc9LJWuC4aqWivkjAfAOICZN2dFVyHipVKSDUgczaKRg7Ag9+qEEqRFFCzXofnn+zcrWHDbXFo2aBFscSwYeUlBAu+MFouBriUOCvl91e5REzcY9Vc/kuWsp0QST5ZG+OvNasRJDm5+ghR8KIEUnBXhC2hENfztjTi6xVARaecQT1cO3vojXhydDm8Rxnvb9eOqROd9w07a3k66Qz+LaPs+7HoKGdbw8EzQRwiavuYQONhHRCWxa42eLQ8Pk2VN8lp8cgmJ48GjmZ94l1kHi1mehq3c/Xb4+oBrlMR0aj0YAo3jNLdbM6c1UBcVEwNu66hCNiNUw7kzYDcOGZs/ua5gv9UypZY/bqAP0RR4MPI0s1fDVtOe5gc2KcI5acI5acI5acI5acI5SVzeYpQfgjDe4pQfopQjnF9ilB+ilB+ilBuofgUoby9CGUhJ7TkvyUaqH/u/rJ+QFAI1l3HrJQ8m+LygVWrrwvLrKLlwly6uDAecKhltuJ4hnGnuikrKijcRqWk5cTVcNe2i0BQAJ6WGJAFITZxc3I/bjiZ+0ZaPmagULhTpFNB6PetIRKu3TCmvFYfzR7NeX2ae6i23NWUe7XklIac1I872nFCN96QkhJa8XapaQvacFsXTk7kwUdiuR68yRSXHJqOFvwQPLv67zIs76X7JiexjWD4lXrvJgveqyAm0e9ovQ/Bfqm+u8kcVum6pO0gtB6SmO2dR1/epytrL7PzzSCHPW/SsrkpoaMFhHc4n03UUAViZX1zSZ7vRafXBpeEodDIk113q2HF8xsixpqVRGm6UK4ioOsBie1djUIaRMBkouKolkPNp0KMaBF0BXIoB0LPprx07boz63uxz/0axRzRNoqx3Ra+qYDgUEqwOWLzL6CANTHiJYOSJxNJZ1bulUTxGS9oOnind0JVcnG3kNbkZlNRqJ3TKezTFDuZJGIUtruiVE7qWU9X5090YRQIlDuRjCspNMs0OJS55ncs7dEKlvd/7Cg13RmQnd3C/L8RHsx/XbOUtzv/Mz159pVlNfQeeKwlOBxBLWqGQf32jDoG0QyfnNVereTeiJd7vdQD3PGxdw8G6WlgZWYCvw8wdwQPiHbl7anyc8U4zCNaYlRs2BMg9qAEBX4IJSMp5gp8eS4NxyLk1nLORqSCmvmuiZURXcveSuXQnycfPuTUNcmAL1+v7aeCpgWnx49T6r65t1/uH7zd3X+z+/LV1f679/tv3r96PXz35tV/rHl9X7luwCGZ2gL4PajPhbzl5eQao46STUzvI4HsTcWM7dEirPy7EnWLC/G4OGtndMVH4oa1asfixkX05briRtOThWH/S1cEc0wzXnBtxIaK3wkgZCpFDT2gK86w/nDTuY+4dD/4TbWrlttAbsUY9N2c0XJh1I+MNUEiV+GgHib2TwK/MyqeswGBHCIfLoyHilupQVWihHQvm5rViMY3dtmGgTf4ENrZSaZZ2A2sCdRgahAkvo0YqcucSdcT2mqFAxuWOSBRX23smj0g7iEjArl4tDD2dUhOsaS9nRYtCgjo1KJBmVc3AxTmKEhXpV0XWBRqswNOz4mW/I7TolgMSCnIjGoNGVngmdcwAJXQi2oB6WYLs1DBIO/pcDTMhvnNfWuZJkJmeg/SumEzh4XPNTXLAiQkXGG0VuJpELTRide7vEe0nn0pkf5mKQ3quKX7p8OlgPFSkk2ozDHgTEEd80HwJGYnjLiPgTSyMGbwZELmCvvVXB2d+0L82PbPYYboZIybz3alsDF7QS7//czGXT5Xvhq0AdUMj+CxJp1POmqPYYukFovu5Ftx/qVynVeBHdhAOUIzXTsTJ/ZdYXJGdjykHay8O7YxJ27ksoWscpUp4Wer7jh7bCJN0FWky5CBqRbwEHfbOO4yAk2huyli3oTucQhr/GddZo0OZZvk43spMM0SlkIHwAyd4BbZHtYPSvz+BlFrYbRY9OgR8siWtRWS+cwXp+d3bxvG2nM1b5BVtoFiIaReiv23DztcigaWan0MTCxZ4gCt0R8lUr7Jo3j3ej0Uf4DQeai/3eR52dgx24gfjlofAT0khr3Bdk0h+dzGtK+DbgfVpxCJpxCJ7qyeQiSeQiTWXcSnEImnEImnEIn7hkjYLPOumth8ub6T2qWst3USHf5mFC2J92bT9QHjJmjoHSkK8EL3BT+Mue3q2/h2oMoDWgPcHR/YUHB480Yrz2ELzUq2Vs0/CDKwt5msyxK1ZphAXxUe7lsKY3H/wvd/sp3e3fv4+IzeMkW40cGU4qNWM1Yt2qsapMThDpZBsa5+1Hw/AGfekQzCCyRnZQZ2YaVqplB7NDAly81kbPMRsPdEAI1IZ2NdXB9AnrvmhT4fq8wbWoBnFC8n0P7INjVpY9q49F99x96w0ZjtU/Y2e/39dy/zEft+vH/w3Wt68PbVd6PRu5evvxv31AR5ULZSYwxmBVWaZ2je2rWzWtMSHApCjuab5BV7ppbkr4S8zgOAjBbbbAT6jYGxzRdlKcRcAdebx83J3XI3Ch8023AnUTbE7drwmN9t44GYIJFbxz2JMUDKduy4cURYNu0lIhCHBdadsuga0si50pKPagPGVQBBepE12Ne8+j4VSqt27/XmiKA9yNlF3KSx8ICdWo930lYRgk68YkxOwp0PtwCmZdNQw87HWVEr3UpaQZfNByHJD4xq1QXDlVk11xKckkxU3uLu1xF6cUVwrTV5TEpBHBzfOeUxGlz0nIhNfCJBPte9TgMAcHZvm2qMnaMSV0/EJM39Jlpk7FAwUFdwSwDYyjGNMY6JZdDaOV96JhrhJlrI9jEJvFr6UVLsjmxHGBigtS+bBvdsTEOvhi+H67bz+JsNe2mRTiiprEM/DXeEepbi1oik1EZpMo0N8GKBxUfcGFk2RTw968SqKZsxSYtHrMFx4sboiCmNfEGe8zHc5NCCtxOzRQJ5pelfBZ3ulOs0LBl4Lm0xJk/WPL8huYDOXenaRe/o6/Gb/f1xM6InaPBNtWTc8Lv1RFx8ZR2Lu29O2mwh2uT2nIU9ArW+hT2seGLN7PeUYr+BjRzLVXQJ4F/DRp7C/newkS9D4xFt5Eif/3I2ckTbGp3D0ig9VPRHMJT349zB98la/mQt787qyVr+ZC1fdxGfrOVP1vIna/km1vJIk6hlEasRXy4+Llcavlx8dDesbbaJ9Qargmlmfh2gZK8yo1wNbFwdVDKkenpP6b6/P8C2UuJcY/SmaH8todqiC29sej336gFnAmxnVJvnu5XJBmEZnhwWcoZR5xRr5JvFiwBClB+FcEqaQQxsISaW6szrXNksjX/WSjc9zl3xuWbBu/qqr3KfaJHuwFOwqM+p8kgP/E63JaQ+JTZe57DctjXdDDPx/vXrV3towvmvv/4lMun8WYvKgO/5OU0tZjEfi1JOx36vUM/lM6O62TWEAMZaoQF0gGymKYDvE1kjiDe1LIYG5s3AbDjE7OloiyTLRKm0rME6IyRxG4VkGZ/4Dom2NuReW5BeZzzij7XSlwDdu42wpc/A14vegYns9BxD7Nh88/7GNXKoaKAKA+T+1dlMOd3ObI+xk3fvbOPtSk37tMTcB0N65vQ7/mIDMIXVU2ydQSg3jdGpxQJZNuhH8T3ctBcfomkfCpNb0o6q8QKNT4TvNIKv3nTVIr/U8Yx69NmkVaQ//LjUbBJ5D9Y0jnTW+/XrV+m+S69f9WneevpYtHEOjTj6KMMe2zZJOMQgJvyxMDOHDAawzMoLPYAr/oIZlm38IzB+Li3WkyJzONf/Fc41+wp1Q4PC1uGIEIOPx8A1pokAlcLAAUr2Re6CucDr/jcKY45q7Z+KZ6BbC4HW4qZryazSDV4wBXwi9kghhJZ7JvIPkhHTc2YrX+u5wNPelw0t6WQWWzO2S5dC6sCrAALTWNto75s/3wREqkXVu5l/TjJph3zP3GrF5GNmYX6x8Ft022t3U6oFe8scAOH3YxOuS0uiVxtmSJhNAa942zGQrtAAj6LUC50P2R0NSE4L0ojOQ9chzXd8As8KaMah5dx8w5nCE+xBwUBTqrDuuJ7SEl7n+aDRREooIrJwUjjwB3BaETFucJquWUdCy3pVGQkMBI6+Ckye0fed4hKJAhSxZ+ePEMjzueXVqNuBPd60b/an53xsJ5CEFiMWyQPLpMepud5dTnQhJo1wtQRPI4a3bVYPSB48BITJCXS3iWTHFZznmUItw6CClaPvKC+aDN0O4mxG+eNpx+bgwQhO3uvBYkrVowlBNqDMMYFpHNQVsiZ0QMODUDNIlIsZtGEyjyQuoS+KjevCrPINkAYUP5D2A4Tf+BAVKHwOlE+LmB22upVktDQXmr3Ge5ar7RvY6nr9CFEdnkFzNAjA/ToMTQBR9z9f2hdQU4b0YpmJZUwpKhc9N09cKqe5f0j4/Wa3EIJ0d1HjYzeqjq1k4ZKz3a1o3l2gZcSDU1Mxt50T52zkvfsQlhIUQcYsXSqN7FV7xKMqIb+P8aqvVeWyA2PncRcHfzSLmtRwdj6J33hR0L03w33ynJ9PRcn+Czk6/0Lwb/L5khy8vD7ABlKuls8LclhVBfuFjX7meu/t/pvhwfDgDXn+809Xnz4O8NkfWXYrXrhYlL2Dl8N98kmMeMH2Dt6cHLx+Ry7pmEq+93b/9fBgZ5Ob5D7MGQdbby1DB1NDFhtUNd/Omf5bdyfbmERu3OF+ehGx18Rwe2uJpLH5WlpEnqp1P1XrfqrW/VSt+6la95K5rFWt+8/kis0qISlYor5CeC/T5LvhPsmpmo4Elbly9UmG7hXIoKiVJhPhXV2ZGi5m4AGDMgJzrhjRTGlFclE+06RpYOujpRjV4Z2CK0QL7tNgKqqn7+2NBZHU3fdbTVKWw/APhxPxnZuhBIn75fPx5/epRmXW3rjHMrWHyRt7B9+9i/BqjdW//T372e7NYm9si9klu4MQ1K6sO2eS+UbWGCHdntCXKjfaz5gXzKzeHudqz3oKaZYJqE9RLIY9cvqwojqbbj6hc/NaSqwMhZHEcDNe+s4zGwz3ybx2n+HoP+81nHntHsOhLLP5eKE85IMCnGDUM5ZQidkF4XybTC0t4fQM2tnBNQZNbV93UEvXtSz8UQPX81oH4LKWPKOakpnIayzKVSuwSA/DkM8g6mGL57nrkokcdX/aNWCRvf3JC7M/4KfEEEeui34mZjNRwns+sNqZgcCyUdi6Irb/zp9iPTRiq5rP2G+NiL5E53ZPojyga1k2SUntXSOiDHIezZTCgpA2GWoYc/md/wbF2TSdVTvR2ge1xMyKcsnyyAKL0nn0XGNoqyfm+nr5Vk/Jy/2DtwNy8PL9qzfv37wavnq1hqHBo9S0FcWVLcTEluwBYsR6L1CELJqUpr56YV/ZIdvHeQHPon3aF9DSpGKY3YSBMkyGVXc8DEyziQbGDY/WUYz+yTKnHOCH6w2o25MfsDzX6Q9ozgXoRxgwKVssIVRDegY5MS+1FDAo5pPn3FZLMuoYpAzYVDIYx2cH9PWYa+Vn3ScpBFDDpbYnFw9ic3aP3OckmGS91yAEGYirvONSQIt5sEi5M+TWsdl8yC0uxBxTdGila4n2xNRRl4wWTefdVr5Dz5xPx3H9JK45HnHwMAaIoAVRiq/Qw9DZDz0c52O1hcRtEJWtoNRECjSK3QchyU9XV+eDsNRiJ7CAfdWSNjliNKLIhiVfXZ2TKaM59JNvcgFv/r77wZVlM3/dBJL/l7KAspJRqE3OQaPPB5haOacLBS4nipXpMJCNazIzIlAQ2wceT5zsNa9uYEqlKHG97Co0xGs52bHQZmKl0GF8vCflVkP+Zeyr00AcvceurntUwut0DPKxz3SEJQ+YTFbZkv31jPnIDc9p67xqodmcuGUYBomLIVE9j/YeyeHT4vKvHwfkguUcy6hdfPn0wvx3xxyEnZApBP2JO7dGhKUtzeTqLwYX/xKkw0veFaHE9GuM4Gsv7dIhgwK3S4Y0rIaW+W7By+0N3Skv23dHpYrEWsaQqhW7bMy+cpxL5t5bNTNGYfm4YQXTFRd+anq+xmg0hq32tCXqsTx25S62Rt0SAd1z9AfRkL0FVtJQa8xt0lCMwvJxN6Wh1vQaGrLyA3Cpa5dYZIWInQ/mVj8xX+6kRYl1BAkQDbAee59QYB4ZjgMHFMoFIyEKRssVwkGZG72KQTkElIi5Ika7cZ9KhA9xsXDjm9uUFi5EMM6mkgy62WMfF83kjOUQ1ASzsEnUZbFoy7hmAL5CbD018HyVzdPjplpBUL5zxrTNVjbXuJdIuqPdFX5l1lVz//bx8CzqqeK8ie/2Xw4PfiVj6aIo3eVFMahiV9PJBOSb0IQdiCVzjm2mfXALRJWBmFVDtBWdPFOd8QuudGCyHHOpdEOScO47JHkV3MwPpcxIdOwQaMzCNdX1GrxtyidTm8+BryQkCoyum9MFxgfMqhpi8gO1FAQ5krOKlbly5Syc3IQ52rYeqpEgUfibMWr0Ew+Dl/g+9N8eRxB6pBH80lWnucY6x6H+8vnn4MNJoFSZz7bEY/vrI2Q8+HW0pDOmp2LFkQmui707Jkd7+FJyURthXVsbaxh4bl+EG+/5jydXA3L++dL8/5crlJiVIKJ8gZL+5V8/hkCIGZo8vzz5eHJ01QRVfjk/Prw6GZDjk48n5r8NlNZ5lSyKmVoy10JMwLbm3sD7EFAJiRXipRXRIjHrSN7/cvERb7m6chcdcEZVUDUlz/deIAAv3fJxmLhxs2cUbLV3cDOIoHrsmmduEJA5YIar2QSQ8MEmOSjcFrhibfS2WQCv5Rj5f8yhewQEyxRFtALWnBQtcxDfnKTsJeuOkkCLJSxbZbdMsa5o6CZagubZcKLm0Vu22MVjDikX9unm9OJbt6x904Sx0huYTJooaIihmdYzaiZIc7SagLYXTpNr5OnNrgVlTZQwp8nIMBA0cfPjyRWxpHJtEzQMsn/RTGlLGFZh4pH1rQ0HDxjhVpEGiLaMeACvvemSzmLrkmZf1xCNrEkUATDNpIq32VwfVKL0ZFiF0e7MRIPno72/mko+1rsX50ftt5s3mhvXR8dGkylFk8zfZ6rGdjgWFKa3g/nVXmBh3JSrb+HtHmCgch11oCBTcF1oJivJvF1F0jnW2bcuq25V/SkrqnFdoAovRT0qmJoKqNnf3OKSzpvb+wI+RDNL3tNu/PA0upr/aaMSrOaGVGB2zTwVlx9vjqyjENpUfzdfz3kQEvqcVlXBbZZ+QRdQJK1YWL464iWViwa+By9qGUqcmCtFw0j1NIFg3XLFtj5TBPt7TzWS/GaMqloy6IYTCICfgq/J80AcVC82EQVD6GF5hY7G2UdxuGJGwVpD5TPXV1aI7BY1Mq6JFuLWyX+QlpoYOLifJMu4siZOgz0vCq5YJsp2NRrQAmLNsaqv+9A0sI/Ov2yMVd9YUI3kmq9Q2cD0ALnIhgTgnTYtBImqWOqI/8bawk2XHi1nIwUrJ3rapIxhkrX5rmnpQALmd3XkTLOp1cQvnEUUC64kZi3qNW6d3mkjOf1rzTsvVUBY62qh3i5h6A1qYmqX1eRT2V2jhaDNSJRPQnxNK9XOOh+jNZY8l4wWu0aG2G3ym1+A7pTRxpYMLZYgz8P2dTHv4/C7WuxaRIwOUpdu0cEBd3x2GUprfmxf846rTNwxF4fcf5IzKfxJjg8uWgW2ssTOzB63tTArP2KEKSOdcjXFKUTEhou/AWPqnU4haL7VuRhWzjCpecQAPJaubZGFh7QueXB0e87oLYPGauDJhggjD8omsCoyZ0Vx7xXJxeyei3JaLpkE6l5aaFqkV86DOf78qbV6pyV0SPGM6ZdX5Ize8QkS/hWHKPTD89O0uunyALMmCfAmF7Mj3KiPMMZJmd9EuQidJy41lSDnO99mIeo8cG2ajy6dCqo1UiMbpu/+T/ZXFJez6FVFaJ43kQM0z6/hgWsH0plLhey1C5lHh/DW0IG1E/PuKJat8O5HpYEiDIfk3FpjHHeCar7ZywGZZAwcTzmfcE0LkTHaNo8HuLkKqderbZT4IDk9dihNhdKuQuwaI6z2PPgxQvfDeqPYB64Dj55fZ/1yOGM5r2crFCgEEbn71hvcRrbwguvFdRCp0jge1S6jSu8eZMtROAwAEQhkCRzsXCE6XPVEp8QkB7ETflc9KvaX3a/rk559xeDyoxCTguFJ6x8da/4sH8AW61kxP3vQc5HdwvmxJ/3YfU4Ax9/AyNquC2p/M2dWTYXU1+iPb2LdaZlNhXTj7fpT3iPre7TIysQa0qotRnkJ2Vr+x4fklXmAUSvYxHAzOnlghElIFwDOX7iIwJwqMqp5oUkoEHRRaYW836d+rR+zjEpodccq6IgVqjNaFGtElscbrcDlFFYCx/FEa/MfLMn+hJ8SQE7LsQgJ1QoxMetpaNN8n6JM8r/+jxv5th4xWTI0Gdnxfw6/S2DR/O4vzvgWbICScPTlB6l5aeVhipDe7EBVIt8CQQUrUFmHQDe3xwxVP/TYBiOdi5x8OT3uDmT+X1U0296kGojdwUTeyah64GAiZz1LuO5xXG8ghEZmtOqOBHmsIKBubbgAZHrMbbK4YNws4nbLht0Ck0+Oi3Ath6GzX6vA9Hb46a/nHRub+bKpWIou96ZMcooFWKhko8MvWVUsdlu1BTctNgy4AiQoHejKqUop5MA1JTdfTrWu0iM2VXb2X3e3B18J7LH33Jkr9lUT9rUqbMwphCkYLBNpiFlBldpN8Kr1l+UD5YUZxvpIAWJiJPx5q0OdHifGYV+xOvn2RBgHcclgu1sIy7Wggl4D+M8TzZiWotaJ64Yqxe+6w8cBMCuGPx0TxfSA5AKcp5lkVDdT3/u1ZnVqAfJWnOqDxvYuEwd29fhQIWl7s/cYlA1k0jc2rbXYzVmrWOADRg8A4qBoK6xLLLGQuJF355R3ucW9Bg9iBcF3a6jANrp1ng08dikuIkpVz5jc1XTNk3waxBE1BjkAMiB3tOA5mKFd0xTbhsIQQ8mKFB2ygt8xuWhhsCmDadr07ppDNYHeyWXuBt71N5Ubj2g6STI7MKnvQgXeB+LTGP2969UuC9CI6/Xtgqh+Y1JEdlgCZS3mxWI3Z1lBJcvxxRST9hu5XcQdWIVRYX0HSopa83Kye8sWD+Ol1s3tAAaBECQ+PjS73frpcS0vbCo8odltKeYFyyfWXzgOvOhptKCA7uMda8Wg740lJhfCGtj1pzROyq9qZ+DHylIdnPl4F7nUw5A+Rt4HXVb4uJ/x8fEuFJzb6mgAMTEYUOsDJTJ3WNFo6YMYVXOMQ3Z3Z9T31Plglu08dJ2bqFfrK2TeCTiliowYK0kl2R0XtSoWxI+KtMLjMoNCElpiJ/bepsGuuNbD0D5sTlJTrmvJSaJyUrsAgHjYTRS7z5VN2ApytzxkEL5wYaA2KV6RauhbpsTlhe6oNGsaBaBF60TLnAaB4A/eXw/Q8cLUaZrZMOWHDXphZScPzl+S6YtGs1JvQW7+dPrppHHm9cnORqvaA42oHxdWZiLnZVd+uD8+DmRiBaznfDVp3t/i6K5BHMqGdYVpCSQlQc1SWvJmypModysmFVewCM8PILkp/ObliwQGleRC8gRXX1/scDN2oAZk3xzN75MUiKXVuChTSulGEz4MYioCuEG0epKbg7ovHji0TSPE+ubFgmiRVJMqLtsFGx9AUQ08b7zp64DjROFtrrG7rJaur0953s6UPbjUUA/nYm6URcXaVXXCSgUPXsYjo9gbmRiL5qWkK1pV2xsmDDfiQcpQRpWiZS5pYCE8ct91zIT+F3L3eu/VZgbDcCSyOof7NFklq0GgMRHkTeCVBdRvfgwjTNNIdBDpDBlebO2RyNKbpX/E1aO2Q7yWYUBWFE0KMWkx9SQyV5g9EdbVaGehdAceF02tgu6wqXpGiZE/GCBAvAswoQqbryQ7nXhIlOcjGZ09dGxyiOPY1BwEag4PwYLfNhtPQXpPs0+OKfrkKf8i+buNnSeTmkpaagaanJX8m8da4YQ4axpCRhPD3/tXQLSJa+PZH5auCodN1EAccq4MP6mNGopqE810TQuHXD9KGML4IDo8dIX3mxhkZ1iPAyRHIl8E9b1njDyn9g+uSMFn3AYKv3zz9tMPhJf2/VY5uFS6xjqL2Y3O/etHGxyJRqKAdCBQ1x32pHgSBYuTzZlWzBvJt+JalngtvIGj8TrTtS11r7BvNdR/gbPzTNnHn5jcE5N7YnKPx+T+lDz6mIl6v5N/3Onl62s6IdhNj3RLlr/X9kbsqC66donW/MW8/yynVmCdVQjqhqwz/RCfsp5d9+BEVpFUB7WLNjE1bgEzRtzqCRospHYtRnDGNF2KXN+idbA7ErNKQB792O2Vi2xKo7B8BUMkb9miHZuTRrafqJIofy6LhV81bEeimCY/FmJEi2sw76hroyENXBIooNGKgOvDWrfcub8HykG260p8+y7CB+F7jhHUcd6izWrDL3TU8WdmIy2Cx1djnoniuu1mS2O/5Kh1UF9y3DJR1LNSEcVsFKEN2nPZMxQy/PI6g4tzxVEMZ1LdssW1hf64kzn/2c8C6sWjc9YsYpLZtdCkE15OrqEMx7YpBtPAG/hYTAyztDBRG4sb+VZ5rsflX7+cXPz73snfT46+XJ1g8pyZbu3AWTuDlpzdsYDcctfJ3xdyQD86V7if/bfNEr600SVnnQyezoKIGc9zYNLepaRYgpi8WJlN2Yxed4J3YtzWug2vmkXRwgiXIeh+WWq9y7EXwXUWsINql8RdZgGOY2jkThR3LF9+I664bDbG6yrq8Gq1R7+tZkctfsvRWnabbAcnvCvWRqjjXdkmRuExUJTnhI7HyGlxWPKccdv90l5yA9tcalGxARnXJfjfITOHTiaSTQwnMRBfrFpmOWHbmxVAM2wVWdXOhy9nR1enn892oEbd4Y8/Xpz8eHh1sjNovLDeIboc0bJdxP5Bi8/8ku3Fy7UciaDO2IOR+Fwy1/8PCv3RbOrXAo/yc6rADGM+JLaxcX6xikqWGvQBnO/84uT88OLkoTzPIXfN+xZl44Xr8D03hhVHTo+Xb6Jkv15vTw1IHOTG4vCkDvy+KD+pAzH2T+rAkzrwcHWAtGz9j8tNfbqYi/a1WCZVAvz3xFifGOsTYyVPjPUPzliTLg1VV5WQuiPP98T4kdVxfp2lCKI8URWGsrx1RUSFqYdUsgYPR4S2mrRtiWR9XpmYYUESGvnFaEk+nxvF77JRIJKzpbWeGurJ2iFmZH1HTioo2Qau23qMqjUO1szEuce/kBnLprTkamYbpaznBIqS4joUuZxeU6Yxe/dBo1uAGRnJTg8bnAV0cq0V6/GQzak0jC/tHV8zFgBKuNmxHbwBRr+LLKslJhv9gr8Av4dEfbihk0jFpY832myovUuqGnIKWpR56Hy/4IkF/CTLGL+zxZabEj4QRU04WhgvTn48vbw6uQDX4+/h9Osw0aZ1Tb/av8LcuebQV0EAP8SfQtqWwcP8yTLN7xiEhiYsjGQsikLMo0o2EFFqSaVk8z3XoghSwHvn0unluI1FhBxxXi0xnAjZP+o6fu/0kAbsNzNWW7rOrRc3KCiIAxGXgNqF9WSyfjJZP5msn0zWj2iy7rn9o1LtIS4rb/9GPHL1E1y1GBDEozbnUbRRO0aLlsS+DwUZwpuMxuWKAVY5sFXxQaksXXI/q5CEZ0I2ZQFndGHhbSpLtGo+xGuzbkBgMCs791RkZS8OM5UaZWOZIlrCeyGyDcGqwUQHVdc2QsPerA+/qd0V7UpDYFmN7ovrXMuS0fw6EyVmRWXtQN9116qDZ/eCDgax7SUs/oFJQks+mWCSZ3gsEotKWpkNPO22IhuHii0zqhihTLWVe1oYrQBSn0DM7U50BfoA4NFxlwxyYCz60PX2thRzV0AVZ+H7Mvq8C5q7TFzgjSwnzxUvM2B7ddl0yvR71URa+M18sXL/QLP6Vvs3pXfA4ptE3pzoTr3yPmRHhchu26UNHnXD5lOhWDuDH4oPWroHM0k2BaPRpsQ3l1xHxSjTE9rk5B9bwS4Sy0HhN2PZg85nRryrV612TjW9tku0FMFulnA/gqcaWs04Fysssz0WVBGqbm1xO/AVmBNgddmoDEAK221rE8Dtvfbgop0ph5YiVoRbgdJWNYkt4KP0TG/Rg989QHWJbC2qy5TCpKxn1wb3WrKtibVrXh7sa8UkhyLDlFgcjF4GfJRlddPBYi2W5JZ+q9scWgk322IqJ8BQtreqG2oL/Wj7Mq3TrLp7HWR9Hv90dH73upPyiV9HGZ59FVYdRLJRTbigKvn15umu/ztavbCv2OnxwOgwtMzFzNFgZu6R0lrYojfR1jlAP4W1wFn7J3SEQgu4uWWUEhlvu1R8FZcwHVX5+tk0hBV7d625tZv4ZauNdxZkWRZ+vBpn/uBZWIQVtDITRPnF4jRiE1p6cyPNfq254ugCjK94yUo2p0WrtyFZ4p28xxbaZCiJ7arjndCCcKuzkqmYw0+GPt32oEQagePQGKQqFmR3l1gbCvQAUJoISUZS0Nx8SNbks112OxPivXl3V0GVrKC5cNQbMpXqbYtcbTZYi/bjfsbeeRMuEPbb5ZPS55k1SMWwwl7F0F0BrxSqyM5C1HIn6rScoF0z3BZnYxcwnIufoEtRQ3WkVqxTIZ9A6a2vmijNKle1aySEVlrSagk9S1bQxUOnAUDCySR4jHWE0ix0uEWQnvMhGxKKS4Ag8alUCQ9LujOabXoYrzxOzxT5dHjkkX6OTUr0XKQGtBte3qOManfBwnvXnWxrLQqsT0NfEWhIvqB3OYJkFurzhw8nF+acmw+HRz8vq1IkqutkYdIu+r6ajSEhYC7rz80bcSq0Kj1HGKhqGobkQKZWeSqqze6DuPybeb05Rt6IB/Q3laKeTFNjUpnPqewoSPfcW6cMObBx/3AouEZKpudC3pLnJ4Zdl0wPIjAfzUNXtLgdEKaz1Dqh572DbNu6tCwN2q5OSi1cJrx50ogr2q1YGLc4vp6GW6Voo0bMnACIKXrOhpOhb04z6AAT4zGTvozmgOQsK3jJBgatASnprfmtYFSxgQ3iaRsomiAS2/bu2gK7LnjHvbi2/zs1ba7sfvkGPGA2hqk33NEdkaa/TweUbzTftO4HsGGvv+QcLWy4fq8tv0vOkC81p683OR5Ig25Sz81kj08vjz7/7eTiBUiZRSHmHXjxheHehouQmmlqntUFleFdM2JeuOgLkbF3ta/hs5Wpd+9uI7nd8bymRXSNoy9uSsu8sHFYHVjLg168CPdoW+cIC5mnH89PEENGrCejA8nfpqoelb1BHDP69dooUNeO8Sj+W5rxJAxr957LjH7ls3rmEssjduPkq54JGYKe86KwkiTNMlb1TQ6CblZR2DbZR8A8oCSXsJJCsXC1qroaoPl3x8rc+Tcw1C5kJFA0NQA9JH+D5xWZ0a7XIJsKgfFbORvzMuDudhQMRWpWRVkp8I51gQWHu4WTxKZWHo6r8dSEZnaAYXY69bOIuwfDWW3KAs/oAsvnseXU7S/0CL8egsjFjPKyLS5ukRZiMsfhUK4Esu6oDB1g4AaQTIkCLOVTobR5XZE7TlGGQphQoPxyoXRUjzWaa6mukddtizPFE7J8tDNxCp3ZQYpoUO1AQ9QtEEUab0ZLO0xODQnZHewFLyfXNugxOdNkBMWK2R5GgoAhRsNqdbDVWpC6pLMRn9RYJnWtEw6lRmhZjynUo0H3h6dh0XQ6Yg2/6wCzrY9sbRsx1vAyXgcYimEOYl4rLaF/rxJS8xqCIQF88oK3GI6YYfRq2NLFXcgICBIXH47Iq+9fvukNfjUXzvWMqrYsen/KQ5jEwFymgYumYHiZCC+xEn4P3rXOoNfptRiPFdPXimVbuwltG0KEbJc1Zhb2p8hk86y793YheOmNa9C16kgImfMS4sa+lNwcKlqQKzPm8y9XR31ithS13pLkBSYHALeMJzTyme0oiK905+l2ci0pBnZt28wONmw1lzOH4d3bd+Hj3dlsxt9KXW15NqkbqmdTeJPXf3Z13qW/e3Fsd499i1u3jDoiLkGq0bquQSe9tmS0vVN/DzWsbfwm1qR0cfLXLyeXV42W1qOVUQJzQXJMGSRJpCUNoR0obULtq2KBCIEN68XAiZ72gVolnEutaxG3Y2FLR3lkeFt2B2tBn16C2kByJ1otcu65E4223zhZbOvMJi4tjQbB7OpQQoB9NV+cHf7c1Kctg0BwkOKtx7EbMnS4TNTwwI9Pjj6enp00upLoAPKOCnD7TyNrrzXH5O6+gXCfNcwU4H55zNMRH2CkllIzeUcLvN68lwhsCrNUSEJdal5Eh0LSEh1KvsnBxcnZyS+nZz9C58c+xV6yEQer7/8dM/7h9Ox41ZRHQujrMS/YI6pG7txp0UjKFEY2AzfxT8/Mx2coIiWouzEk27LmPujJW3ThV6sQNO1BSxU6nc8uux7ns8vdjSoL5+XmbQgf1P/KSCXHZ5ekotmtkQEbbdl3q7HunUqKiaQzFJUnrGQSMwpadwGmsAHcABhXJBMVZ77xTyLCst97sQb+Te1Dq99T3ToQt7yEmmwYoGh3PVnFAkgMU/+4Cn23QvKJEYiF9F1n5MJaV2ByvMTpReBSZUu9cR2SCHu8z0Na66mQXFP94HZUh7BKkIJl71L0QVEdbEeOVnnvXy2Jw2DRMVO3jRFOFLExoEqna7fjxCTLaihPeu1lvseY3nzKwKBkh7tz0ak2hRHm6GVO3rV5BkaJNaaSM8Uf3EdlxT65W5dLlmkVuhWNqFFLVbNWUAbO2K9AsRiSi/7lcNbF3un6rMhr2+P8MWnSoWmnCNGQ0Cc95CARSI9e7wSyKctuzU2cc2X2/RvtF4yl+jzihtNSKCYMjcq8jdYHVPdOR8u6NLJZft1bXXlb84G8SYjE4lJp8ubgpc2S9snMRtCfM9lmf1g8dUlB6KX83nF4I2zUCth7kpOefT65uPh8key2hNyoJYisuFVC5ob+SrMTnOVDcjputEK0u9h+pYqUotytJC+7kZrZlEqaGaGYPB8xo229egmGtZG4Y+Tg5dsXYHwzXEgoFj5OJWsK6LYCq6kiTGW0Mve0UYsO9l3NXUWe/+P4+PjFkPxAs1uiCgolgM1t9WsttOvdjy+3LkA6UgOSUSk59DyDHVSYHG2kfTJmLMf3wcgvbWrhP/SA/EPCcxG8f5RR1mhy++bz+XACbd6HmUg1BPPb2PJjd7OSrcdZskzIXLU2LzX24eHh4ZIB28nbiSgTis7BjUY9PVsyJtNFfl0Vtbpu+vonx2aQXYdJC9UupmJY0n3Orj4evyAGChElw2wkaFwcb3fHZ2Le+88HIPjujIUYjqgcTkRBy8lQyMlwx9wUO+EXbfmJEV+ZJTd64CxoG3v18dhWB0ClpCRsNmLQ8jsTlUvMigAiMPP0VOvq/d4edI/LVD0e86+AQWp96Yz+ZnZPDOvbVKBaqeZrdUtaxi1LQqWkC3f+Mdks5xC2SY1sCP4pDHGF8YiyHfF8TenRoiNX9YocFudO8ZH7SP1hboIStcyYp13XfdkLdDd5qYZ28BtkeWESXwu9pYy2zVqdA8HXLQlRIRWTwFeTG2z/6OEXDpl12QUQWWvmXYySiHz6e//w6zMPc8k9AIkUO/FroIv1CSO2HAReASvVJLZpRhdkxEhGs2nrfhqxseE6PMyxyrnKqMzNTfofTAoXCDNjtGwkJ1iJRBRsKXQz1DB9BnrXoSWzrpIADArWS9XE8OPMhzYEjpa+nBBX7o2KxdHO3vXQeOPdpocw/e528bdqGGePzK+agHyv+DmGBfy3zZlxYZdj/DtxqwYBz7HaQHseBHIWGC9gyY2XWVGbK6qd7RuLeO0Yi3OwqowYTUZKNwP/QThmgNA34Jpnl8tR+H05p+/M+c1OXNML9J5HrkH5dzpyDQIrjlznwW915JqB/yBHLkDo9zpyAQp/lCP3JLAEa/GvKrSISg+73aw6JHViSMk+l6SVnf2dNPC82+h0ha0rbGEeJOuBC9qQ9OXJUc9E2Fd9LZeZqU6+albmrEmZswVE2mywmdYPh8d/O7m47JlcnVftwNnVTNw2TBbymSJfjs9JRReFoDkxgMhzXqLB7kXTM9Po04EP66erq/OOE8t8uZkXy0IlSTdWq25LqjWmGXFLXTE7M1m7c+aylApwcLeDFpYcTNJ0bJeLwD1urjyzAJajDNMPUck6fgq31btfLk47Q5kl07bgqWNWLg3Rvg5LAYVwvJc0bKDtHF9akJuvu/P5fNfA2q1lgQG0+U26veCylntbKVHZXddDMqNVKF7BXGiFsZBho2rlBapU/1P89wvG8OM0sKFh5br8eVljVDAwArvHmtZxqbhUiwIIEmYXovZU3gUJTGnh6xApZghAd+1DBISe2Yyq9A6YPU0u/6oQl3ZlpPCwrNfMMXXW1uz4uOywJaofdRCH0Xo8BCTkuq/3X/dkB00l7QRPLx0H3+gd6UxoMhZ12Zes8q9zVLrua/z3oLPSgQYNNB92VjowRwt/VuyFx7NZeOGdHn3qXni4cOanzdpCW9hko+iNNSSkVitPQCzVz7MSSvFRwa6R5bdP0+vW57epQ43HvRujtlaO5CGZ1jNaQhkquKvgAvICYz8rwV+SaZmrckJ96hgURe2FncyHXRc2cpReFvhIy9UbION/uueCuXLMfStmod9zyQLpt5EzZ2wGClBw9D7ZrzrHz/2Qp4XOnsMXjEA2OoDuJN0rK7irfzk8PFzCzX0wY6XGNCI80hBdlNESSkuOeEnlYieCNRaS4Pe7I6pYPiA75gbfwQBc9lW7r4UkO7ZMDv4ItbzgcwSwi9iKI1PwcgvrIekcebD3HQvpy/rYHxT5fPbx35eeXnhui7vjUEI3rU+bbS4a+xyUuE43Tw7cpmRHMY2FQSdMJ9yhuJPN0osqgwJC5o4DRRTL80L8WHrsltUL12358d3Cmp0EpVoNNkBzzTT8affRw+0cc5sNHwbQuyi6YOEJH3eWyAZ3bnpfPJwkwMzh0w+HYTigO7Bfzn4++/zL2c6A7HwUNN+J09Z3LrWQzPx4zAqm4a8jUZeaSfOn0XnNfy8LOjrSsgAoF1+OJJ0X5ok2LKoVPF5nGVPw5wfKzVtQh7bW052N74j/by5Se1IpigaZks2qQiwILZ0ypNjA6MmSQcXFmMIt3abgzDjWGI1ECcvxIJrquWIxsBu30EO/gaht3AQXgh8lVTDCvwfZBtdx2dZ77r6LCmxVb23zSs8NnqdW1kw4jTCeZmSJD0e2zUeaMiVgkCuhMnL/sv3eaISLgYL8RjLYxojAEKsX5PdFxS0K/XXrOCBQp3giB/Nh/9D5oIiuqgggKLPtS3DprfwvPQfchrtRnd2yh3oXLRTbDChU8u3suqUyOouJrPHhZ9Wwq5oWTZBlFIDr16apoB1BeN7Zjl5OF+GdrtG02SoGJi+77z6ujJcx9hugift8yxbdtQVn9vr4uXRQAyvaZGVuf3M5g39iHXl26+j4lWpKNjWokOd87KxPyxYJHO3W4PLAvWz87b4yUF12NZKmi2gidcVGJrtJ5IIpcEAqVuYgz+RU0wH6/Xwi/oxDmfzlysS3n2bMkR55npa1pSd4TyozAtDb17bkSO6mq7ARkfUY+ITdVeRmN+L3wNAxkPVOBLgYV9EIev3ek/bDqy6+hWbYNM4xZ4syqvGgSY+ZlEuzDf7IGOIS5qxI5NZsdswy1KUILzMJ1qe9nNm/CMBfKW7xkmtOi0fEw45gby7v8dz8qrpjciQU14uHCiWAiG8NYlnRjge/4zjOElwknV+3mqs8QCwJbC103jSY8XfWjpEAFBkOhzvg9t0pZE0yoyXjd0tvVkQY4ziu27E/9xJHMCTElYMyTP2ZKuiIGM1Z8Un5bI0FzJnSW8HGAAJDkygfiBKttZiJRA7mRnuKWDlYZAZ9yGyGMqDkfvIoEfa1ktioAVopYtnsticuYXtRmpb5aLHz/C/7LwZkRxVivvP8Lwfmb2gSpBS/YzvP//LyxcCZ6Ax92aTXcWsAz8bAKIe22yWrlS6dvNnedQxOADQSITe9OreKllNdU2htdl+yr5XmiVqzG9I61dRQC5cLFw/nw+Di+7yzsjGeIeDTcbz1//9X+ySnC2WzhMLRbIs52zxlpxRztL2xQjHCY43TphaPlChqzciXkn/t4Pz81cvdEV+6cKpgrLpO6H8b8iwDhiiGjYF5SWY8k8JXQrKn41kh6+sMrY/4ynK+EYprW7pBW/odFBtxv/n09yXrVYp2gd975IdeYlCQlsAniIXp2nq2jmYi/Ldj33QBwM6Zv1pI/7XmCevDQ2ahl9imuLI9dbRk4KMBRgw4LDVRWPWQquu65A83+RwdXpLnmZhVVLJdWua7ak6rF1GJBX+Klypy3w4hXDawQwHzOTq8RJ8lqauc6lbRn3W5OMg729J/DDCuNM+clO6TlckJzaaElVousCF0ELKfDGGxYTM7Bl0rigHMpc6ZbvDHfb2sjit47u6CRpzI4D3xopyIfBQ64s03x6OeMBj89YeeAFAzuGJu8tbfkRVCMctoMF8cI4osK7UQyZzLxhmNbvEpn0yZtA3FvLufkOfjMEX1BiIkb2CRb1wc8s0LQqsKW9K6EWwTVKrInBVFX9hOsyJko8CBdrvCJVtkL1KHV9OPy3Vqt2W8bI2oyHDhU8vbFBc7Y8J7IcwhbWM9roviSBQFppicbZQSj22n/cvWibHOU8BGMcY0o5qVkYHVyC6QvA5PekGlBSJ2+dW2sWMuNHk+fOGJKxpgSZqze96PPRbCx80GI4+oRGmnb1YuP7qz0GjnuhKXt4mmC+vz2ktm63w0drNcZFYL1IKIGddkFzu1Q0sdF7eHVRrcs4F0WhfwoJm5ubJ3o+FssVhDS0H2QF3oVjmKvtlewKsPvFuacAxX1bln8iMWlJdIo3Rhf9+a1bJBwC9pVSRWxO3IBylm643zy5RJrxFmtVRAo9x1feHKw+yOBtuy3jCH5L9ffj5rKAMyWLzrQ6V2mUTx6yCqWbYEhQUMGxKSEYZxTmpAaAENITFpalYrTWZUZ1OMT/JDR/BxO33WV0Sv2DI+fPrcxjr6Md2b5N8AyQH5NyFzJkfmrykv9YD8G/taFZTbJv//pkpaqanQ3bVEkvoAbP+SmQO/Lp9PLm3BZ9wuq70J/dwsy/Yk1V3xFC7NlZBefEgn9KvP4xKW1F4rrb6XDpeYyzqGaEuKWBXkIEHsGy7TD91liqt1IaUZckHQjhk17wgrRhIoMlEwzbpo4RNbQ8oOiJRaMTkWctaunWJumaBiOfGF+aCQjpV8B0QxLKf4BUF+dvqb8gjQKAXUyw6faFnTojtV5BenG8iMlsMEEnvbd/j5/Pri5Pzjv9vwHjjHthMkUoJvs9j40hy+7mK14q8/W1XWK2hF+H4us4vzo/4AbLJEMvvKe5fBwPTBakEGWLMKqR5EtCjukYx1fgRvYvYViDXOfpvUCapicb9B8HpYa5SOx7x3cSxQeD4BaGOTVQg7aM1t4PSAv1a6l58sgWaINxFX3ZPvfj0u6F0/3zLj+Jpi9kTCCykqkSwf1htWY3REwuQzhW3weT4wU8iMUGr4da2nu3XJv/aNOHnIiHD87jPkUhry4NGKZnRmN5DabCSl6Wwz6flQjriWZsjT47DyPaBEZjSb8pJB/q+rYdk3tn12VZ54GNrqJ27fDdTuhfq1CJXuxeVfP/ap3Oa3zTIuHXiyWeVQ1dZhN7elOdXW4OyLDUK9sLQ62yQWapl0P1IoaMXyaynmD7HtRog5W7cZHUNEx3XRq2Z7HCKAQT0EMfd50AVVGgvh9rBcXiomW11yV6N9enZ5cnHlipWuhzXPU8WzSjY3ygNgwXKDewJJaJ/b2FvWxfLy5OPJ0Wosgz33qlQEToydaLykWqDBsUUT3xRD2PUl+G2ggmFjgzkeW1dGVsGF1UhQgXjyTC1Jn8JA3y0EkzXxbZE1CU5Q77jd3Ke1RjEictJ25UazfLMchzWWzz50ayyffbgkd6/3Xm2Wq4dwyQNT9VavssHO+xScTRbpK7GkM14Kef3gcQDM6tE0XQru7jU5+vzp/POXs+OgvLKmKd9MJ2p6CREY2A08MO1h6Rdedr4PZAWHSwTLXLjJrq1L5dwYg5ak6yivmsQ39rlQeiJZ/7XdPLDZ3e0G2owY78FtYKCHcpttyAyOOZ9PtiU0JHlgR1RrdijgdeuWZFmL3SVH6XcTIkzF7piMg5dWA3UvbZAAjPVx4+8+HF4dfmx9d354dnr0SDLC+A8vIzwIw7aMYHmJZDkP77EL87mHjcBvm3EQB55sxEEQzU5ix1qexjhSDlBuZOyS0JQGnqzItbkTLR7sMV1oOJIvxmr3Uk8lH+tgM6/gi92L86OeHW0e2ExG8SNttq+d4jQrdhRtKXoqcjRXBZVnlmylV1SOY/Yx5gVrlaxBB1oAtra+PgXuJsPIPOuKjamf9ZTJOVcWxOlxUJfC7ZrrvNzTyptnGxB3qMuHu4ZwzKWJDTC9hfT0+CPOOGnRu8/56kb5tJAxe2QNslz50O1mpyKIaxzArxkDZDbjmUAp4cFr4PQrqM0z6+PrTl0R8s+rj109wJ61jxt2XNHF9hSBjcM2WpU9rj4GQjzSdPN8UzjMPHYwfJUoHDalZa6m9JZdZ8I8rB/a3uAX20HDYVeyidAc5WbfJae5L73DSDGl7DMRvKadEFaMZ2UmFxV4entrbNSzh87itFneADFE3g5gi5GTSrI7LmrlHuxDCca5Rrb5MDqwyPUhhpyvLnMmC3AhWVYN5EE+l4ZfRfB2eI51IMLpnh5DIrTm2S3Tzc/4mbCvmpU9s8UOFtcZk7YlL7v27vnt0Zbt8IG3uXP+66gbXWCGZ4RrxYp43i4ixL4RINw/qykrim4RwU2KU3VZwTIyWLEiZA22MFp0+7nOObR2SLRuM2JSXeKa5bVELypPUXc4KdsQhuXXGa+mfUWq2jF3a0zuo427s2DDOcR9/GrlOg2GyLbBXTIGtQbV+729+Xw+5LSkQyEne02HM7WnC7XbyB6tj8OvUz0r/hx/udtTIixYFjGDuPyGB2xticLwxGAYe+yjJbP4qHsujIG+a8Du8rz1CZclvQqeWaSn3D48nSlDnJ85dwEk1yXRyzuuyWYMJ3UQSUveYxIazl27nqOJpuHLz2cHYUe0vnH2n3oRoFVV2FGvC7pg8tpXFwpuzoci1CUaEpytAIddwMHzjuXnbdg/LXsAr/G6eCT0sW+/iG9DHHGAwc1IIq4bNZtVemHDW5MQzZ2R35lrQDHf4wqYCgBt96YkyZNuWXG3QelW98xweDdSp3nUsp1qUf6/4H0Gs3WNHFez/FPtApogVljpJtDZ7VkcAQtpwUbxYHmKUHSAXqcKlN0AWNW+qxKik/xNud11Q5hE1VwzP1Jrel4fhkvDHZIOuNHiXpPq3HNbnmDngltrmh1Y/vpYf47f5BprB2Hjv1XX2ANvkZTQ0UH/zAJieTgciS+M3/dG+GJbu9KyFHWZ2Wgz2robfOJQL+kTJP9gP8hhMacL1b5F/gAXQrAtwZ0QpNKspaGtvAEiTI6aF3vkMAzbjcKghon7ZOMLZVW/47+/2f/eWoKaCvE9HEtyWlwnjPIb8CjgSM1iQPySAdv1oIZDl0JfY9OA5LitANTOoMdm2W3TgUCxC/aEY2kL6Ay5BAc61j1TXwsFeL0HA8j0ZH3NvLEZ2/UtW1zTYiIk19PZdq8JD7YlJcSbhXiYUbpiA9pJyMXl4YAcXx4aEfLk6PjycPWUWhGZZG3iveS/eVtyiFqafl0D0G+6hB1yd1j0YEkLzWQJWb7XqAmlcFyp9F7WUMmaHDbgyBm4A1I729dSnc4fcs6hLGl4yEpyfvKpayaPNqlOleVeU15wkw4aeSKTbc82hrFKVoAEYJm67je6jY4QTLvScXs0ISe05L9tRYv9HMCymWRrjUuL67rkD5Y5vpQck+J5GYFfggVcjmXWLV2+4dDnFo7hQpJNzPwtInY3l+CQidlMlKme+hujcQbOLgl2DZvO5oLgm/t/5TnkStU9987KM3FSaq4XTgVUtRFGy5zYvvNPR+PpaPxLH40xLydMQjfotc9Hmqh9fGD+pmvBWDqxPkn/mSKfjt+EKHZW1l97U3qwvVEvfzrcPVh33Jdv3m535Jdv3rbG7jOlPYo65YwZT+rUkzr1pE5Fs3lSp57UKfKkTt17yCeZ8f8KmfFJnXo6Gk9H40mdWnfUP5o61Rm1o01dZ1PKu7GzSyu4HU0hOWxMtKyV9uKWVafWCvV7HAzWCjakBZPYt/GBRYFTzeRdQAUMAkCxpfwdZHnBl5JljN8lfYfB5nVxW6rmfgjebATNIOZ0bZX2n/TV/e67/374CgZ0Tspe/pAcnkRx02r6UCaXduga4djgGSAHo7UpSPGl12FcCuMR8EMPOGoz0MmzyOoCit1MGSA8/NP/GwAA//8GYEIB" } diff --git a/packetbeat/magefile.go b/packetbeat/magefile.go index 810dd74f778..ac8deeae9ff 100644 --- a/packetbeat/magefile.go +++ b/packetbeat/magefile.go @@ -136,8 +136,13 @@ func Update() error { return sh.Run("make", "update") } -// Fields generates a fields.yml for the Beat. -func Fields() error { +// Fields generates a fields.yml and fields.go for the Beat. +func Fields() { + mg.SerialDeps(fieldsYML, mage.GenerateAllInOneFieldsGo) +} + +// fieldsYML generates the fields.yml file. +func fieldsYML() error { return mage.GenerateFieldsYAML("protos") } diff --git a/packetbeat/procs/procs.go b/packetbeat/procs/procs.go index 0b33cae0e8f..b94cdaa5b82 100644 --- a/packetbeat/procs/procs.go +++ b/packetbeat/procs/procs.go @@ -19,13 +19,14 @@ package procs import ( "net" + "path/filepath" "strings" "time" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/packetbeat/protos/applayer" - "github.com/elastic/gosigar" + "github.com/elastic/go-sysinfo" ) // This controls how often process info for a running process is reloaded @@ -50,8 +51,11 @@ type portProcMapping struct { } type process struct { - name string - commandLine string + pid, ppid int + name, exe, cwd string + args []string + startTime time.Time + // To control cache expiration expiration time.Time } @@ -61,8 +65,8 @@ type processWatcherImpl interface { // GetLocalPortToPIDMapping returns the list of local port numbers and the PID // that owns them. GetLocalPortToPIDMapping(transport applayer.Transport) (ports map[endpoint]int, err error) - // GetProcessCommandLine returns the command line for a given process. - GetProcessCommandLine(pid int) string + // GetProcess returns the process metadata. + GetProcess(pid int) *process // GetLocalIPs returns the list of local addresses. GetLocalIPs() ([]net.IP, error) } @@ -116,20 +120,20 @@ func (proc *ProcessesWatcher) initWithImpl(config ProcsConfig, impl processWatch // FindProcessesTupleTCP looks up local process information for the source and // destination addresses of TCP tuple -func (proc *ProcessesWatcher) FindProcessesTupleTCP(tuple *common.IPPortTuple) (procTuple *common.CmdlineTuple) { +func (proc *ProcessesWatcher) FindProcessesTupleTCP(tuple *common.IPPortTuple) (procTuple *common.ProcessTuple) { return proc.FindProcessesTuple(tuple, applayer.TransportTCP) } // FindProcessesTupleUDP looks up local process information for the source and // destination addresses of UDP tuple -func (proc *ProcessesWatcher) FindProcessesTupleUDP(tuple *common.IPPortTuple) (procTuple *common.CmdlineTuple) { +func (proc *ProcessesWatcher) FindProcessesTupleUDP(tuple *common.IPPortTuple) (procTuple *common.ProcessTuple) { return proc.FindProcessesTuple(tuple, applayer.TransportUDP) } // FindProcessesTuple looks up local process information for the source and // destination addresses of a tuple for the given transport protocol -func (proc *ProcessesWatcher) FindProcessesTuple(tuple *common.IPPortTuple, transport applayer.Transport) (procTuple *common.CmdlineTuple) { - procTuple = &common.CmdlineTuple{} +func (proc *ProcessesWatcher) FindProcessesTuple(tuple *common.IPPortTuple, transport applayer.Transport) (procTuple *common.ProcessTuple) { + procTuple = &common.ProcessTuple{} if !proc.enabled { return @@ -137,20 +141,28 @@ func (proc *ProcessesWatcher) FindProcessesTuple(tuple *common.IPPortTuple, tran if proc.isLocalIP(tuple.SrcIP) { if p := proc.findProc(tuple.SrcIP, tuple.SrcPort, transport); p != nil { - procTuple.Src = []byte(p.name) - procTuple.SrcCommand = []byte(p.commandLine) + procTuple.Src.PID = p.pid + procTuple.Src.PPID = p.ppid + procTuple.Src.Name = p.name + procTuple.Src.Args = p.args + procTuple.Src.Exe = p.exe + procTuple.Src.StartTime = p.startTime if logp.IsDebug("procs") { - logp.Debug("procs", "Found process '%s' (%s) for %s:%d/%s", p.commandLine, p.name, tuple.SrcIP, tuple.SrcPort, transport) + logp.Debug("procs", "Found process '%s' (pid=%d) for %s:%d/%s", p.name, p.pid, tuple.SrcIP, tuple.SrcPort, transport) } } } if proc.isLocalIP(tuple.DstIP) { if p := proc.findProc(tuple.DstIP, tuple.DstPort, transport); p != nil { - procTuple.Dst = []byte(p.name) - procTuple.DstCommand = []byte(p.commandLine) + procTuple.Dst.PID = p.pid + procTuple.Dst.PPID = p.ppid + procTuple.Dst.Name = p.name + procTuple.Dst.Args = p.args + procTuple.Dst.Exe = p.exe + procTuple.Dst.StartTime = p.startTime if logp.IsDebug("procs") { - logp.Debug("procs", "Found process '%s' (%s) for %s:%d/%s", p.commandLine, p.name, tuple.DstIP, tuple.DstPort, transport) + logp.Debug("procs", "Found process '%s' (pid=%d) for %s:%d/%s", p.name, p.pid, tuple.DstIP, tuple.DstPort, transport) } } } @@ -238,8 +250,8 @@ func (proc *ProcessesWatcher) updateMappingEntry(transport applayer.Transport, e proc.portProcMap[transport][e] = portProcMapping{endpoint: e, pid: pid, proc: p} if logp.IsDebug("procsdetailed") { - logp.Debug("procsdetailed", "updateMappingEntry(): local=%s:%d/%s pid=%d process='%s' name=%s", - e.address, e.port, transport, pid, p.commandLine, p.name) + logp.Debug("procsdetailed", "updateMappingEntry(): local=%s:%d/%s pid=%d process='%s'", + e.address, e.port, transport, pid, p.name) } } @@ -262,13 +274,15 @@ func (proc *ProcessesWatcher) getProcessInfo(pid int) *process { return p } // Not in cache, resolve process info - p := &process{ - commandLine: proc.impl.GetProcessCommandLine(pid), - expiration: time.Now().Add(processCacheExpiration), + p := proc.impl.GetProcess(pid) + if p == nil { + return nil } - // see if the command-line matches any 'grep' pattern + + // The packetbeat.procs.monitored*.cmdline_grep allows you to overwrite + // the process name with an alias. for _, match := range proc.procConfig { - if strings.Contains(p.commandLine, match.CmdlineGrep) { + if strings.Contains(strings.Join(p.args, " "), match.CmdlineGrep) { p.name = match.Process break } @@ -286,16 +300,39 @@ func (proc *ProcessesWatcher) expireProcessCache() { } } -// GetProcessCommandLine returns the command line for a given process. -func (proc *ProcessesWatcher) GetProcessCommandLine(pid int) (cmdLine string) { - var procArgs gosigar.ProcArgs - if err := procArgs.Get(pid); err == nil { - cmdLine = strings.Join(procArgs.List, " ") - } else { - // Save PID without command-line to avoid continued errors for this process - logp.Err("Unable to get command-line for pid %d: %v", pid, err) +// GetProcess returns the process metadata. +func (proc *ProcessesWatcher) GetProcess(pid int) *process { + if pid <= 0 { + return nil + } + + p, err := sysinfo.Process(pid) + if err != nil { + logp.Err("Unable to get command-line for PID %d: %v", pid, err) + return nil + } + + info, err := p.Info() + if err != nil { + logp.Err("Unable to get command-line for PID %d: %v", pid, err) + return nil + } + + name := info.Name + if len(info.Args) > 0 { + // Workaround the 20 char limit on comm values on Linux. + name = filepath.Base(info.Args[0]) + } + return &process{ + pid: info.PID, + ppid: info.PPID, + name: name, + exe: info.Exe, + cwd: info.CWD, + args: info.Args, + startTime: info.StartTime, + expiration: time.Now().Add(processCacheExpiration), } - return cmdLine } // GetLocalIPs returns the list of local addresses. diff --git a/packetbeat/procs/procs_test.go b/packetbeat/procs/procs_test.go index 9079843ec34..ca71152c823 100644 --- a/packetbeat/procs/procs_test.go +++ b/packetbeat/procs/procs_test.go @@ -21,6 +21,7 @@ package procs import ( "net" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -33,14 +34,13 @@ import ( type testingImpl struct { localIPs []net.IP portToPID map[applayer.Transport]map[endpoint]int - pidToCmdline map[int]string + pidToProcess map[int]*process } type runningProcess struct { - cmdline string - pid int - ports []endpoint - proto applayer.Transport + process + ports []endpoint + proto applayer.Transport } func newTestingImpl(localIPs []net.IP, processes []runningProcess) *testingImpl { @@ -50,13 +50,14 @@ func newTestingImpl(localIPs []net.IP, processes []runningProcess) *testingImpl applayer.TransportTCP: make(map[endpoint]int), applayer.TransportUDP: make(map[endpoint]int), }, - pidToCmdline: make(map[int]string), + pidToProcess: make(map[int]*process), } - for _, proc := range processes { + for i, proc := range processes { for _, port := range proc.ports { impl.portToPID[proc.proto][port] = proc.pid } - impl.pidToCmdline[proc.pid] = proc.cmdline + + impl.pidToProcess[proc.pid] = &processes[i].process } return impl } @@ -65,11 +66,11 @@ func (impl *testingImpl) GetLocalPortToPIDMapping(transport applayer.Transport) return impl.portToPID[transport], nil } -func (impl *testingImpl) GetProcessCommandLine(pid int) string { - if cmdline, ok := impl.pidToCmdline[pid]; ok { +func (impl *testingImpl) GetProcess(pid int) *process { + if cmdline, ok := impl.pidToProcess[pid]; ok { return cmdline } - return "" + return nil } func (impl *testingImpl) GetLocalIPs() ([]net.IP, error) { @@ -94,40 +95,55 @@ func TestFindProcessTuple(t *testing.T) { }, []runningProcess{ { - cmdline: "/usr/bin/mylocal_service", - pid: 9997, + process: process{ + name: "mylocal_service", + args: strings.Fields("/usr/bin/mylocal_service"), + pid: 9997, + }, ports: []endpoint{ {address: "127.0.0.1", port: 38842}, }, proto: applayer.TransportTCP, }, { - cmdline: "/usr/local/bin/myexternal_service", - pid: 9998, + process: process{ + name: "myexternal_service", + args: strings.Fields("/usr/local/bin/myexternal_service"), + pid: 9998, + }, ports: []endpoint{ {address: "192.168.1.1", port: 38842}, }, proto: applayer.TransportTCP, }, { - cmdline: "/opt/someapp/ipv6_only_app", - pid: 9999, + process: process{ + name: "ipv6_only_app", + args: strings.Fields("/opt/someapp/ipv6_only_app"), + pid: 9999, + }, ports: []endpoint{ {address: anyIPv6, port: 38842}, }, proto: applayer.TransportTCP, }, { - cmdline: "curl -o /dev/null http://example.net/", - pid: 101, + process: process{ + name: "curl", + args: strings.Fields("curl -o /dev/null http://example.net/"), + pid: 101, + }, ports: []endpoint{ {address: anyIPv4, port: 65535}, }, proto: applayer.TransportTCP, }, { - cmdline: "/usr/X11/bin/webbrowser", - pid: 102, + process: process{ + name: "webbrowser", + args: strings.Fields("/usr/X11/bin/webbrowser"), + pid: 102, + }, ports: []endpoint{ {anyIPv4, 3201}, {anyIPv6, 3201}, @@ -137,16 +153,22 @@ func TestFindProcessTuple(t *testing.T) { proto: applayer.TransportTCP, }, { - cmdline: "nc -v -l -p 80", - pid: 105, + process: process{ + name: "nc", + args: strings.Fields("nc -v -l -p 80"), + pid: 105, + }, ports: []endpoint{ {anyIPv4, 80}, }, proto: applayer.TransportTCP, }, { - cmdline: "bind", - pid: 333, + process: process{ + name: "bind", + args: strings.Fields("bind"), + pid: 333, + }, ports: []endpoint{ {anyIPv6, 53}, }, @@ -158,104 +180,105 @@ func TestFindProcessTuple(t *testing.T) { assert.NoError(t, err) for _, testCase := range []struct { - name string - srcIP, dstIP, src, dst, srcCmd, dstCmd string - srcPort, dstPort int - proto applayer.Transport - preAction func() + name string + srcIP, dstIP, src, dst string + srcArgs, dstArgs []string + srcPort, dstPort int + proto applayer.Transport + preAction func() }{ { name: "Unrelated local HTTP client", proto: applayer.TransportTCP, srcIP: "127.0.0.1", srcPort: 12345, dstIP: "1.2.3.4", dstPort: 80, - src: "", srcCmd: "", - dst: "", dstCmd: "", + src: "", srcArgs: nil, + dst: "", dstArgs: nil, }, { name: "Web browser (IPv6)", proto: applayer.TransportTCP, srcIP: "7777::0:33", srcPort: 3201, dstIP: "1234:1234::AAAA", dstPort: 443, - src: "", srcCmd: "/usr/X11/bin/webbrowser", - dst: "", dstCmd: "", + src: "webbrowser", srcArgs: strings.Fields("/usr/X11/bin/webbrowser"), + dst: "", dstArgs: nil, }, { name: "Curl request", proto: applayer.TransportTCP, srcIP: "192.168.1.1", srcPort: 65535, dstIP: "1.1.1.1", dstPort: 80, - src: "Curl", srcCmd: "curl -o /dev/null http://example.net/", - dst: "", dstCmd: "", + src: "Curl", srcArgs: strings.Fields("curl -o /dev/null http://example.net/"), + dst: "", dstArgs: nil, }, { name: "Unrelated UDP using same port as TCP", proto: applayer.TransportUDP, srcIP: "192.168.1.1", srcPort: 65535, dstIP: "1.1.1.1", dstPort: 80, - src: "", srcCmd: "", - dst: "", dstCmd: "", + src: "", srcArgs: nil, + dst: "", dstArgs: nil, }, { name: "Local web browser to netcat server", proto: applayer.TransportTCP, srcIP: "127.0.0.1", srcPort: 3202, dstIP: "127.0.0.1", dstPort: 80, - src: "", srcCmd: "/usr/X11/bin/webbrowser", - dst: "NetCat", dstCmd: "nc -v -l -p 80", + src: "webbrowser", srcArgs: strings.Fields("/usr/X11/bin/webbrowser"), + dst: "NetCat", dstArgs: strings.Fields("nc -v -l -p 80"), }, { name: "External to netcat server", proto: applayer.TransportTCP, srcIP: "192.168.1.2", srcPort: 3203, dstIP: "192.168.1.1", dstPort: 80, - src: "", srcCmd: "", - dst: "NetCat", dstCmd: "nc -v -l -p 80", + src: "", srcArgs: nil, + dst: "NetCat", dstArgs: strings.Fields("nc -v -l -p 80"), }, { name: "New client", preAction: func() { // add a new running process - impl.pidToCmdline[555] = "/usr/bin/nmap -sT -P443 10.0.0.0/8" + impl.pidToProcess[555] = &process{args: strings.Fields("/usr/bin/nmap -sT -P443 10.0.0.0/8")} impl.portToPID[applayer.TransportTCP][endpoint{anyIPv6, 55555}] = 555 }, proto: applayer.TransportTCP, srcIP: "7777::33", srcPort: 55555, dstIP: "10.1.2.3", dstPort: 443, - src: "NMap", srcCmd: "/usr/bin/nmap -sT -P443 10.0.0.0/8", - dst: "", dstCmd: "", + src: "NMap", srcArgs: strings.Fields("/usr/bin/nmap -sT -P443 10.0.0.0/8"), + dst: "", dstArgs: nil, }, { name: "DNS request (UDP)", proto: applayer.TransportUDP, srcIP: "1234:5678::55", srcPort: 533, dstIP: "7777::33", dstPort: 53, - src: "", srcCmd: "", - dst: "", dstCmd: "bind", + src: "", srcArgs: nil, + dst: "bind", dstArgs: strings.Fields("bind"), }, { name: "Local bound port", proto: applayer.TransportTCP, srcIP: "127.0.0.1", srcPort: 38841, dstIP: "127.0.0.1", dstPort: 38842, - src: "", srcCmd: "", - dst: "", dstCmd: "/usr/bin/mylocal_service", + src: "", srcArgs: nil, + dst: "mylocal_service", dstArgs: strings.Fields("/usr/bin/mylocal_service"), }, { name: "Network bound port", proto: applayer.TransportTCP, srcIP: "192.168.255.37", srcPort: 65535, dstIP: "192.168.1.1", dstPort: 38842, - src: "", srcCmd: "", - dst: "", dstCmd: "/usr/local/bin/myexternal_service", + src: "", srcArgs: nil, + dst: "myexternal_service", dstArgs: strings.Fields("/usr/local/bin/myexternal_service"), }, { name: "IPv6 bound port", proto: applayer.TransportTCP, srcIP: "7fff::11", srcPort: 38842, dstIP: "7777::33", dstPort: 38842, - src: "", srcCmd: "", - dst: "", dstCmd: "/opt/someapp/ipv6_only_app", + src: "", srcArgs: nil, + dst: "ipv6_only_app", dstArgs: strings.Fields("/opt/someapp/ipv6_only_app"), }, } { t.Run(testCase.name, func(t *testing.T) { @@ -274,11 +297,10 @@ func TestFindProcessTuple(t *testing.T) { // nil result is not valid assert.NotNil(t, result) - assert.Equal(t, testCase.src, string(result.Src)) - assert.Equal(t, testCase.dst, string(result.Dst)) - assert.Equal(t, testCase.srcCmd, string(result.SrcCommand)) - assert.Equal(t, testCase.dstCmd, string(result.DstCommand)) + assert.Equal(t, testCase.src, result.Src.Name) + assert.Equal(t, testCase.dst, result.Dst.Name) + assert.Equal(t, testCase.srcArgs, result.Src.Args) + assert.Equal(t, testCase.dstArgs, result.Dst.Args) }) } - } diff --git a/packetbeat/protos/amqp/amqp_structs.go b/packetbeat/protos/amqp/amqp_structs.go index fbffd397767..9db4a090736 100644 --- a/packetbeat/protos/amqp/amqp_structs.go +++ b/packetbeat/protos/amqp/amqp_structs.go @@ -196,7 +196,7 @@ type amqpFrame struct { type amqpMessage struct { ts time.Time tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple method string isRequest bool request string diff --git a/packetbeat/protos/applayer/applayer.go b/packetbeat/protos/applayer/applayer.go index 4e36a5aaf06..9334943c38c 100644 --- a/packetbeat/protos/applayer/applayer.go +++ b/packetbeat/protos/applayer/applayer.go @@ -122,7 +122,7 @@ type Message struct { Ts time.Time Tuple common.IPPortTuple Transport Transport - CmdlineTuple *common.CmdlineTuple + CmdlineTuple *common.ProcessTuple Direction NetDirection IsRequest bool Size uint64 @@ -182,7 +182,7 @@ func (t *Transaction) Init( transport Transport, direction NetDirection, time time.Time, - cmdline *common.CmdlineTuple, + cmdline *common.ProcessTuple, notes []string, ) { t.Type = typ diff --git a/packetbeat/protos/cassandra/pub.go b/packetbeat/protos/cassandra/pub.go index 6aaa33f0e35..911670c79fa 100644 --- a/packetbeat/protos/cassandra/pub.go +++ b/packetbeat/protos/cassandra/pub.go @@ -107,9 +107,9 @@ func (pub *transPub) createEvent(requ, resp *message) beat.Event { timestamp = resp.Ts dst := &common.Endpoint{ - IP: resp.Tuple.DstIP.String(), - Port: resp.Tuple.DstPort, - Proc: string(resp.CmdlineTuple.Dst), + IP: resp.Tuple.DstIP.String(), + Port: resp.Tuple.DstPort, + Process: resp.CmdlineTuple.Dst, } fields["dst"] = dst } diff --git a/packetbeat/protos/dhcpv4/dhcpv4.go b/packetbeat/protos/dhcpv4/dhcpv4.go index 6dfea91876a..0905a27ea44 100644 --- a/packetbeat/protos/dhcpv4/dhcpv4.go +++ b/packetbeat/protos/dhcpv4/dhcpv4.go @@ -133,17 +133,15 @@ func (p *dhcpv4Plugin) parseDHCPv4(pkt *protos.Packet) *beat.Event { }, } + src, dst := common.MakeEndpointPair(pkt.Tuple.BaseTuple, nil) if v4.Opcode() == dhcpv4.OpcodeBootReply { - event.PutValue("ip", pkt.Tuple.SrcIP.String()) - event.PutValue("port", pkt.Tuple.SrcPort) - event.PutValue("client_ip", pkt.Tuple.DstIP.String()) - event.PutValue("client_port", pkt.Tuple.DstPort) + // Reverse + event.PutValue("src", &dst) + event.PutValue("dst", &src) event.PutValue("bytes_out", len(pkt.Payload)) } else { - event.PutValue("ip", pkt.Tuple.DstIP.String()) - event.PutValue("port", pkt.Tuple.DstPort) - event.PutValue("client_ip", pkt.Tuple.SrcIP.String()) - event.PutValue("client_port", pkt.Tuple.SrcPort) + event.PutValue("src", &src) + event.PutValue("dst", &dst) event.PutValue("bytes_in", len(pkt.Payload)) } diff --git a/packetbeat/protos/dhcpv4/dhcpv4_test.go b/packetbeat/protos/dhcpv4/dhcpv4_test.go index ddec7cf8a16..72e91025ff3 100644 --- a/packetbeat/protos/dhcpv4/dhcpv4_test.go +++ b/packetbeat/protos/dhcpv4/dhcpv4_test.go @@ -95,14 +95,18 @@ func TestParseDHCPRequest(t *testing.T) { expected := beat.Event{ Timestamp: pkt.Ts, Fields: common.MapStr{ - "type": "dhcpv4", - "transport": "udp", - "status": "OK", - "client_ip": "0.0.0.0", - "client_port": 68, - "ip": "255.255.255.255", - "port": 67, - "bytes_in": 272, + "type": "dhcpv4", + "transport": "udp", + "status": "OK", + "src": &common.Endpoint{ + IP: "0.0.0.0", + Port: 68, + }, + "dst": &common.Endpoint{ + IP: "255.255.255.255", + Port: 67, + }, + "bytes_in": 272, "dhcpv4": common.MapStr{ "client_mac": "00:0b:82:01:fc:42", "flags": "unicast", @@ -149,14 +153,18 @@ func TestParseDHCPACK(t *testing.T) { expected := beat.Event{ Timestamp: pkt.Ts, Fields: common.MapStr{ - "type": "dhcpv4", - "transport": "udp", - "status": "OK", - "client_ip": "192.168.0.10", - "client_port": 68, - "ip": "192.168.0.1", - "port": 67, - "bytes_out": 300, + "type": "dhcpv4", + "transport": "udp", + "status": "OK", + "src": &common.Endpoint{ + IP: "192.168.0.10", + Port: 68, + }, + "dst": &common.Endpoint{ + IP: "192.168.0.1", + Port: 67, + }, + "bytes_out": 300, "dhcpv4": common.MapStr{ "assigned_ip": "192.168.0.10", "client_mac": "00:0b:82:01:fc:42", diff --git a/packetbeat/protos/dns/dns.go b/packetbeat/protos/dns/dns.go index 22cd258e401..14d4eb3d238 100644 --- a/packetbeat/protos/dns/dns.go +++ b/packetbeat/protos/dns/dns.go @@ -101,7 +101,7 @@ type hashableDNSTuple [maxDNSTupleRawSize]byte type dnsMessage struct { ts time.Time // Time when the message was received. tuple common.IPPortTuple // Source and destination addresses of packet. - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple data *mkdns.Msg // Parsed DNS packet data. length int // Length of the DNS message in bytes (without DecodeOffset). } @@ -267,7 +267,7 @@ func (dns *dnsPlugin) setFromConfig(config *dnsConfig) error { return nil } -func newTransaction(ts time.Time, tuple dnsTuple, cmd common.CmdlineTuple) *dnsTransaction { +func newTransaction(ts time.Time, tuple dnsTuple, cmd common.ProcessTuple) *dnsTransaction { trans := &dnsTransaction{ transport: tuple.transport, ts: ts, diff --git a/packetbeat/protos/dns/dns_udp_test.go b/packetbeat/protos/dns/dns_udp_test.go index f549100ad1e..d4f07fc4d57 100644 --- a/packetbeat/protos/dns/dns_udp_test.go +++ b/packetbeat/protos/dns/dns_udp_test.go @@ -350,7 +350,7 @@ func TestExpireTransaction(t *testing.T) { results := &eventStore{} dns := newDNS(results, testing.Verbose()) - trans := newTransaction(time.Now(), dnsTuple{}, common.CmdlineTuple{}) + trans := newTransaction(time.Now(), dnsTuple{}, common.ProcessTuple{}) trans.request = &dnsMessage{ data: &mkdns.Msg{ Question: []mkdns.Question{{}}, @@ -370,7 +370,7 @@ func TestPublishTransaction_emptyDnsRequest(t *testing.T) { results := &eventStore{} dns := newDNS(results, testing.Verbose()) - trans := newTransaction(time.Now(), dnsTuple{}, common.CmdlineTuple{}) + trans := newTransaction(time.Now(), dnsTuple{}, common.ProcessTuple{}) trans.request = &dnsMessage{ data: &mkdns.Msg{}, } @@ -385,7 +385,7 @@ func TestPublishTransaction_emptyDnsResponse(t *testing.T) { results := &eventStore{} dns := newDNS(results, testing.Verbose()) - trans := newTransaction(time.Now(), dnsTuple{}, common.CmdlineTuple{}) + trans := newTransaction(time.Now(), dnsTuple{}, common.ProcessTuple{}) trans.response = &dnsMessage{ data: &mkdns.Msg{}, } diff --git a/packetbeat/protos/http/http_parser.go b/packetbeat/protos/http/http_parser.go index 510ef4cfc79..e629039c828 100644 --- a/packetbeat/protos/http/http_parser.go +++ b/packetbeat/protos/http/http_parser.go @@ -42,7 +42,7 @@ type message struct { isRequest bool tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple direction uint8 //Request Info diff --git a/packetbeat/protos/icmp/icmp.go b/packetbeat/protos/icmp/icmp.go index b64de3852fa..68e313ddcc8 100644 --- a/packetbeat/protos/icmp/icmp.go +++ b/packetbeat/protos/icmp/icmp.go @@ -283,11 +283,15 @@ func (icmp *icmpPlugin) publishTransaction(trans *icmpTransaction) { logp.Debug("icmp", "Publishing transaction. %s", &trans.tuple) - fields := common.MapStr{} - // common fields - group "env" - fields["client_ip"] = trans.tuple.srcIP - fields["ip"] = trans.tuple.dstIP + fields := common.MapStr{ + "client": common.MapStr{ + "ip": trans.tuple.srcIP, + }, + "server": common.MapStr{ + "ip": trans.tuple.dstIP, + }, + } // common fields - group "event" fields["type"] = "icmp" // protocol name diff --git a/packetbeat/protos/memcache/memcache_test.go b/packetbeat/protos/memcache/memcache_test.go index c9339fce117..63cfadc463e 100644 --- a/packetbeat/protos/memcache/memcache_test.go +++ b/packetbeat/protos/memcache/memcache_test.go @@ -48,10 +48,10 @@ func (mct *memcacheTest) onTransaction(t *transaction) { func (mct *memcacheTest) genTransaction(requ, resp *message) *transaction { if requ != nil { - requ.CmdlineTuple = &common.CmdlineTuple{} + requ.CmdlineTuple = &common.ProcessTuple{} } if resp != nil { - resp.CmdlineTuple = &common.CmdlineTuple{} + resp.CmdlineTuple = &common.ProcessTuple{} } t := newTransaction(requ, resp) diff --git a/packetbeat/protos/mongodb/mongodb_structs.go b/packetbeat/protos/mongodb/mongodb_structs.go index 759117c33e6..d9ce4bbe2bf 100644 --- a/packetbeat/protos/mongodb/mongodb_structs.go +++ b/packetbeat/protos/mongodb/mongodb_structs.go @@ -29,7 +29,7 @@ type mongodbMessage struct { ts time.Time tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple direction uint8 isResponse bool @@ -79,7 +79,7 @@ type mongodbConnectionData struct { // Represent a full mongodb transaction (request/reply) // These transactions are the end product of this parser type transaction struct { - cmdline *common.CmdlineTuple + cmdline *common.ProcessTuple src common.Endpoint dst common.Endpoint responseTime int32 diff --git a/packetbeat/protos/mysql/mysql.go b/packetbeat/protos/mysql/mysql.go index 38e6959e940..5109176835a 100644 --- a/packetbeat/protos/mysql/mysql.go +++ b/packetbeat/protos/mysql/mysql.go @@ -72,7 +72,7 @@ type mysqlMessage struct { direction uint8 isTruncated bool tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple raw []byte notes []string } diff --git a/packetbeat/protos/pgsql/pgsql.go b/packetbeat/protos/pgsql/pgsql.go index 3159bea89ca..8c620d4f965 100644 --- a/packetbeat/protos/pgsql/pgsql.go +++ b/packetbeat/protos/pgsql/pgsql.go @@ -76,7 +76,7 @@ type pgsqlMessage struct { direction uint8 tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple } type pgsqlTransaction struct { diff --git a/packetbeat/protos/redis/redis_parse.go b/packetbeat/protos/redis/redis_parse.go index e023bcee38e..27fbba6c181 100644 --- a/packetbeat/protos/redis/redis_parse.go +++ b/packetbeat/protos/redis/redis_parse.go @@ -35,7 +35,7 @@ type redisMessage struct { ts time.Time tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple direction uint8 isRequest bool diff --git a/packetbeat/protos/thrift/thrift.go b/packetbeat/protos/thrift/thrift.go index 25088cc87ef..bb1260e9718 100644 --- a/packetbeat/protos/thrift/thrift.go +++ b/packetbeat/protos/thrift/thrift.go @@ -64,7 +64,7 @@ type thriftMessage struct { ts time.Time tcpTuple common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple direction uint8 start int diff --git a/packetbeat/protos/tls/tls.go b/packetbeat/protos/tls/tls.go index fd836385c3e..d442c1bee76 100644 --- a/packetbeat/protos/tls/tls.go +++ b/packetbeat/protos/tls/tls.go @@ -35,7 +35,7 @@ type stream struct { applayer.Stream parser parser tcptuple *common.TCPTuple - cmdlineTuple *common.CmdlineTuple + cmdlineTuple *common.ProcessTuple } type tlsConnectionData struct { @@ -376,6 +376,13 @@ func (plugin *tlsPlugin) createEvent(conn *tlsConnectionData) beat.Event { tls["version"] = version } + // set "server.domain" to SNI, if provided + if value, ok := clientHello.extensions.Parsed["server_name_indication"]; ok { + if list, ok := value.([]string); ok && len(list) > 0 { + dst.Domain = list[0] + } + } + fields := common.MapStr{ "type": "tls", "status": status, @@ -383,12 +390,6 @@ func (plugin *tlsPlugin) createEvent(conn *tlsConnectionData) beat.Event { "src": src, "dst": dst, } - // set "server" to SNI, if provided - if value, ok := clientHello.extensions.Parsed["server_name_indication"]; ok { - if list, ok := value.([]string); ok && len(list) > 0 { - fields["server"] = list[0] - } - } // set "responsetime" if handshake completed responseTime := int32(conn.endTime.Sub(conn.startTime) / time.Millisecond) diff --git a/packetbeat/protos/tls/tls_test.go b/packetbeat/protos/tls/tls_test.go index 7d6b52d7cc7..4d428a4c042 100644 --- a/packetbeat/protos/tls/tls_test.go +++ b/packetbeat/protos/tls/tls_test.go @@ -38,7 +38,7 @@ type eventStore struct { } const ( - expectedClientHello = `{"dst":{"IP":"192.168.0.2","Port":27017,"Name":"","Cmdline":"","Proc":""},"server":"example.org","src":{"IP":"192.168.0.1","Port":6512,"Name":"","Cmdline":"","Proc":""},"status":"Error","tls":{"client_certificate_requested":false,"client_hello":{"extensions":{"_unparsed_":["renegotiation_info","23","status_request","18","30032"],"application_layer_protocol_negotiation":["h2","http/1.1"],"ec_points_formats":["uncompressed"],"server_name_indication":["example.org"],"session_ticket":"","signature_algorithms":["ecdsa_secp256r1_sha256","rsa_pss_sha256","rsa_pkcs1_sha256","ecdsa_secp384r1_sha384","rsa_pss_sha384","rsa_pkcs1_sha384","rsa_pss_sha512","rsa_pkcs1_sha512","rsa_pkcs1_sha1"],"supported_groups":["x25519","secp256r1","secp384r1"]},"supported_ciphers":["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_3DES_EDE_CBC_SHA"],"supported_compression_methods":["NULL"],"version":"3.3"},"fingerprints":{"ja3":{"hash":"94c485bca29d5392be53f2b8cf7f4304","str":"771,49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53-10,65281-0-23-35-13-5-18-16-30032-11-10,29-23-24,0"}},"handshake_completed":false,"resumed":false},"type":"tls"}` + expectedClientHello = `{"dst":{"IP":"192.168.0.2","Port":27017,"Domain":"example.org","PID":0,"PPID":0,"Name":"","Args":null,"Exe":"","CWD":"","StartTime":"0001-01-01T00:00:00Z"},"src":{"IP":"192.168.0.1","Port":6512,"Domain":"","PID":0,"PPID":0,"Name":"","Args":null,"Exe":"","CWD":"","StartTime":"0001-01-01T00:00:00Z"},"status":"Error","tls":{"client_certificate_requested":false,"client_hello":{"extensions":{"_unparsed_":["renegotiation_info","23","status_request","18","30032"],"application_layer_protocol_negotiation":["h2","http/1.1"],"ec_points_formats":["uncompressed"],"server_name_indication":["example.org"],"session_ticket":"","signature_algorithms":["ecdsa_secp256r1_sha256","rsa_pss_sha256","rsa_pkcs1_sha256","ecdsa_secp384r1_sha384","rsa_pss_sha384","rsa_pkcs1_sha384","rsa_pss_sha512","rsa_pkcs1_sha512","rsa_pkcs1_sha1"],"supported_groups":["x25519","secp256r1","secp384r1"]},"supported_ciphers":["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_3DES_EDE_CBC_SHA"],"supported_compression_methods":["NULL"],"version":"3.3"},"fingerprints":{"ja3":{"hash":"94c485bca29d5392be53f2b8cf7f4304","str":"771,49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53-10,65281-0-23-35-13-5-18-16-30032-11-10,29-23-24,0"}},"handshake_completed":false,"resumed":false},"type":"tls"}` expectedServerHello = `{"extensions":{"_unparsed_":["renegotiation_info","status_request"],"application_layer_protocol_negotiation":["h2"],"ec_points_formats":["uncompressed","ansiX962_compressed_prime","ansiX962_compressed_char2"],"session_ticket":""},"selected_cipher":"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","selected_compression_method":"NULL","version":"3.3"}` rawClientHello = "16030100c2010000be03033367dfae0d46ec0651e49cca2ae47317e8989df710" + "ee7570a88b9a7d5d56b3af00001c3a3ac02bc02fc02cc030cca9cca8c013c014" + diff --git a/packetbeat/publish/publish.go b/packetbeat/publish/publish.go index 3107f7d060d..b11d8fa5e64 100644 --- a/packetbeat/publish/publish.go +++ b/packetbeat/publish/publish.go @@ -171,54 +171,45 @@ func (p *transProcessor) normalizeTransAddr(event common.MapStr) bool { debugf("normalize address for: %v", event) var srcServer, dstServer string + var process common.MapStr src, ok := event["src"].(*common.Endpoint) debugf("has src: %v", ok) if ok { - // check if it's outgoing transaction (as client) - isOutgoing := p.IsPublisherIP(src.IP) - if isOutgoing { + delete(event, "src") + + // Check if it's outgoing transaction (as client). + if p.IsPublisherIP(src.IP) { if p.ignoreOutgoing { - // duplicated transaction -> ignore it + // Duplicated transaction -> ignore it. debugf("Ignore duplicated transaction on: %s -> %s", srcServer, dstServer) return false } - //outgoing transaction - event["direction"] = "out" + event.Put("network.direction", "outgoing") } - event["client_ip"] = src.IP - event["client_port"] = src.Port - event["client_proc"] = src.Proc - if len(src.Cmdline) > 0 { - event["client_cmdline"] = src.Cmdline - } - if _, exists := event["client_server"]; !exists { - event["client_server"] = p.GetServerName(src.IP) - } - delete(event, "src") + var client common.MapStr + client, process = makeEndpoint(p.name, src) + event.DeepUpdate(common.MapStr{"client": client}) } dst, ok := event["dst"].(*common.Endpoint) debugf("has dst: %v", ok) if ok { - event["ip"] = dst.IP - event["port"] = dst.Port - event["proc"] = dst.Proc - if len(dst.Cmdline) > 0 { - event["cmdline"] = dst.Cmdline - } - if _, exists := event["server"]; !exists { - event["server"] = p.GetServerName(dst.IP) - } delete(event, "dst") - //check if it's incoming transaction (as server) + var server common.MapStr + server, process = makeEndpoint(p.name, dst) + event.DeepUpdate(common.MapStr{"server": server}) + + // Check if it's incoming transaction (as server). if p.IsPublisherIP(dst.IP) { - // incoming transaction - event["direction"] = "in" + event.Put("network.direction", "incoming") } + } + if len(process) > 0 { + event.Put("process", process) } return true @@ -233,17 +224,38 @@ func (p *transProcessor) IsPublisherIP(ip string) bool { return false } -func (p *transProcessor) GetServerName(ip string) string { - // in case the IP is localhost, return current shipper name - islocal, err := common.IsLoopback(ip) - if err != nil { - logp.Err("Parsing IP %s fails with: %s", ip, err) - return "" +// makeEndpoint builds a map containing the endpoint information. As a +// convenience it returns a reference to the process map that is contained in +// the endpoint map (for use in populating the top-level process field). +func makeEndpoint(shipperName string, endpoint *common.Endpoint) (m common.MapStr, process common.MapStr) { + // address + m = common.MapStr{ + "ip": endpoint.IP, + "port": endpoint.Port, + } + if endpoint.Domain != "" { + m["domain"] = endpoint.Domain + } else if shipperName != "" { + if isLocal, err := common.IsLoopback(endpoint.IP); err == nil && isLocal { + m["domain"] = shipperName + } } - if islocal { - return p.name + // process + if endpoint.PID > 0 { + process := common.MapStr{ + "pid": endpoint.PID, + "ppid": endpoint.PPID, + "name": endpoint.Name, + "args": endpoint.Args, + "executable": endpoint.Exe, + "start": endpoint.StartTime, + } + if endpoint.CWD != "" { + process["working_directory"] = endpoint.CWD + } + m["process"] = process } - return "" + return m, process } diff --git a/packetbeat/publish/publish_test.go b/packetbeat/publish/publish_test.go index 5480d7184a9..029116ff913 100644 --- a/packetbeat/publish/publish_test.go +++ b/packetbeat/publish/publish_test.go @@ -100,18 +100,22 @@ func TestDirectionOut(t *testing.T) { Fields: common.MapStr{ "type": "test", "src": &common.Endpoint{ - IP: "192.145.2.4", - Port: 3267, - Name: "server1", - Cmdline: "proc1 start", - Proc: "proc1", + IP: "192.145.2.4", + Port: 3267, + Domain: "server1", + Process: common.Process{ + Args: []string{"proc1", "start"}, + Name: "proc1", + }, }, "dst": &common.Endpoint{ - IP: "192.145.2.5", - Port: 32232, - Name: "server2", - Cmdline: "proc2 start", - Proc: "proc2", + IP: "192.145.2.5", + Port: 32232, + Domain: "server2", + Process: common.Process{ + Args: []string{"proc2", "start"}, + Name: "proc2", + }, }, }, } @@ -119,8 +123,10 @@ func TestDirectionOut(t *testing.T) { if res, _ := processor.Run(&event); res == nil { t.Fatalf("event has been filtered out") } - assert.True(t, event.Fields["client_ip"] == "192.145.2.4") - assert.True(t, event.Fields["direction"] == "out") + clientIP, _ := event.GetValue("client.ip") + assert.Equal(t, "192.145.2.4", clientIP) + dir, _ := event.GetValue("network.direction") + assert.Equal(t, "outgoing", dir) } func TestDirectionIn(t *testing.T) { @@ -135,18 +141,22 @@ func TestDirectionIn(t *testing.T) { Fields: common.MapStr{ "type": "test", "src": &common.Endpoint{ - IP: "192.145.2.4", - Port: 3267, - Name: "server1", - Cmdline: "proc1 start", - Proc: "proc1", + IP: "192.145.2.4", + Port: 3267, + Domain: "server1", + Process: common.Process{ + Args: []string{"proc1", "start"}, + Name: "proc1", + }, }, "dst": &common.Endpoint{ - IP: "192.145.2.5", - Port: 32232, - Name: "server2", - Cmdline: "proc2 start", - Proc: "proc2", + IP: "192.145.2.5", + Port: 32232, + Domain: "server2", + Process: common.Process{ + Args: []string{"proc2", "start"}, + Name: "proc2", + }, }, }, } @@ -154,8 +164,10 @@ func TestDirectionIn(t *testing.T) { if res, _ := processor.Run(&event); res == nil { t.Fatalf("event has been filtered out") } - assert.True(t, event.Fields["client_ip"] == "192.145.2.4") - assert.True(t, event.Fields["direction"] == "in") + clientIP, _ := event.GetValue("client.ip") + assert.Equal(t, "192.145.2.4", clientIP) + dir, _ := event.GetValue("network.direction") + assert.Equal(t, "incoming", dir) } func TestNoDirection(t *testing.T) { @@ -170,18 +182,22 @@ func TestNoDirection(t *testing.T) { Fields: common.MapStr{ "type": "test", "src": &common.Endpoint{ - IP: "192.145.2.4", - Port: 3267, - Name: "server1", - Cmdline: "proc1 start", - Proc: "proc1", + IP: "192.145.2.4", + Port: 3267, + Domain: "server1", + Process: common.Process{ + Args: []string{"proc1", "start"}, + Name: "proc1", + }, }, "dst": &common.Endpoint{ - IP: "192.145.2.5", - Port: 32232, - Name: "server2", - Cmdline: "proc2 start", - Proc: "proc2", + IP: "192.145.2.5", + Port: 32232, + Domain: "server2", + Process: common.Process{ + Args: []string{"proc2", "start"}, + Name: "proc2", + }, }, }, } @@ -189,7 +205,8 @@ func TestNoDirection(t *testing.T) { if res, _ := processor.Run(&event); res == nil { t.Fatalf("event has been filtered out") } - assert.True(t, event.Fields["client_ip"] == "192.145.2.4") - _, ok := event.Fields["direction"] - assert.False(t, ok) + clientIP, _ := event.GetValue("client.ip") + assert.Equal(t, "192.145.2.4", clientIP) + dir, _ := event.GetValue("network.direction") + assert.Nil(t, dir) } diff --git a/packetbeat/tests/system/test_0001_mysql_spaces.py b/packetbeat/tests/system/test_0001_mysql_spaces.py index e763980fb24..ebdee96100f 100644 --- a/packetbeat/tests/system/test_0001_mysql_spaces.py +++ b/packetbeat/tests/system/test_0001_mysql_spaces.py @@ -13,7 +13,7 @@ def test_mysql_with_spaces(self): objs = self.read_output() assert all([o["type"] == "mysql" for o in objs]) assert len(objs) == 7 - assert all([o["port"] == 3306 for o in objs]) + assert all([o["server.port"] == 3306 for o in objs]) assert objs[0]["method"] == "SET" assert objs[0]["path"] == "" diff --git a/packetbeat/tests/system/test_0002_thrift_basics.py b/packetbeat/tests/system/test_0002_thrift_basics.py index 9058e86dd2a..0950f211c28 100644 --- a/packetbeat/tests/system/test_0002_thrift_basics.py +++ b/packetbeat/tests/system/test_0002_thrift_basics.py @@ -62,7 +62,7 @@ def test_thrift_tutorial_socket(self): assert objs[0]["request"] == "ping()" assert objs[11]["response"] == "Exceptions: (1: (1: 4, 2: " + \ "\"Cannot divide by 0\"))" - assert all([o["port"] == 9090 for o in objs]) + assert all([o["server.port"] == 9090 for o in objs]) def test_send_options_default(self): """ diff --git a/packetbeat/tests/system/test_0003_http_simple.py b/packetbeat/tests/system/test_0003_http_simple.py index 3546b593a57..fda9842bf52 100644 --- a/packetbeat/tests/system/test_0003_http_simple.py +++ b/packetbeat/tests/system/test_0003_http_simple.py @@ -11,10 +11,10 @@ def test_http_sample(self): assert len(objs) == 3 assert all([o["type"] == "http" for o in objs]) - assert all([o["client_ip"] == "192.168.1.104" for o in objs]) - assert all([o["client_port"] == 54742 for o in objs]) - assert all([o["ip"] == "192.168.1.110" for o in objs]) - assert all([o["port"] == 80 for o in objs]) + assert all([o["client.ip"] == "192.168.1.104" for o in objs]) + assert all([o["client.port"] == 54742 for o in objs]) + assert all([o["server.ip"] == "192.168.1.110" for o in objs]) + assert all([o["server.port"] == 80 for o in objs]) assert objs[0]["status"] == "OK" assert objs[1]["status"] == "OK" diff --git a/packetbeat/tests/system/test_0004_ipv6.py b/packetbeat/tests/system/test_0004_ipv6.py index 3c1743e4ce8..e5b71a1a5ab 100644 --- a/packetbeat/tests/system/test_0004_ipv6.py +++ b/packetbeat/tests/system/test_0004_ipv6.py @@ -15,5 +15,5 @@ def test_ipv6_thrift_framed(self): assert len(objs) == 17 assert all([o["type"] == "thrift" for o in objs]) - assert all([o["client_ip"] == "::1" for o in objs]) - assert all([o["ip"] == "::1" for o in objs]) + assert all([o["client.ip"] == "::1" for o in objs]) + assert all([o["server.ip"] == "::1" for o in objs]) diff --git a/packetbeat/tests/system/test_0005_mysql_integration.py b/packetbeat/tests/system/test_0005_mysql_integration.py index c87c29953a7..c919fd15f66 100644 --- a/packetbeat/tests/system/test_0005_mysql_integration.py +++ b/packetbeat/tests/system/test_0005_mysql_integration.py @@ -16,7 +16,7 @@ def test_string_operations(self): objs = self.read_output() assert all([o["type"] == "mysql" for o in objs]) assert len(objs) == 157 - assert all([o["port"] == 13001 for o in objs]) + assert all([o["server.port"] == 13001 for o in objs]) assert len([o for o in objs if o["method"] == "SELECT"]) == 134 diff --git a/packetbeat/tests/system/test_0006_wsgi.py b/packetbeat/tests/system/test_0006_wsgi.py index 818253c1515..85d7019c1fb 100644 --- a/packetbeat/tests/system/test_0006_wsgi.py +++ b/packetbeat/tests/system/test_0006_wsgi.py @@ -20,8 +20,8 @@ def test_long_answer(self): o = objs[0] assert o["type"] == "http" - assert o["client_port"] == 46249 - assert o["port"] == 8888 + assert o["client.port"] == 46249 + assert o["server.port"] == 8888 assert o["status"] == "OK" assert o["method"] == "GET" assert o["path"] == "/" @@ -41,7 +41,7 @@ def test_drum_interaction(self): assert len(objs) == 16 assert all([o["type"] == "http" for o in objs]) - assert all([o["port"] == 8888 for o in objs]) + assert all([o["server.port"] == 8888 for o in objs]) assert all([o["status"] == "OK" for i, o in enumerate(objs) if i != 13]) diff --git a/packetbeat/tests/system/test_0010_http_10_connection_close.py b/packetbeat/tests/system/test_0010_http_10_connection_close.py index 47408d03486..c1d2747a6ef 100644 --- a/packetbeat/tests/system/test_0010_http_10_connection_close.py +++ b/packetbeat/tests/system/test_0010_http_10_connection_close.py @@ -16,7 +16,7 @@ def test_http_sample(self): assert obj["http.response.headers"]["content-length"] == 11422 assert obj["http.response.code"] == 200 assert obj["type"] == "http" - assert obj["client_ip"] == "127.0.0.1" - assert obj["client_port"] == 37885 - assert obj["ip"] == "127.0.0.1" - assert obj["port"] == 8000 + assert obj["client.ip"] == "127.0.0.1" + assert obj["client.port"] == 37885 + assert obj["server.ip"] == "127.0.0.1" + assert obj["server.port"] == 8000 diff --git a/packetbeat/tests/system/test_0027_mysql_affected_rows.py b/packetbeat/tests/system/test_0027_mysql_affected_rows.py index 87d4c3d7dc3..321dbdad28b 100644 --- a/packetbeat/tests/system/test_0027_mysql_affected_rows.py +++ b/packetbeat/tests/system/test_0027_mysql_affected_rows.py @@ -13,7 +13,7 @@ def test_mysql_affected_rows(self): objs = self.read_output() assert all([o["type"] == "mysql" for o in objs]) assert len(objs) == 1 - assert all([o["port"] == 3306 for o in objs]) + assert all([o["server.port"] == 3306 for o in objs]) assert objs[0]["method"] == "UPDATE" assert objs[0]["mysql.affected_rows"] == 316 diff --git a/packetbeat/tests/system/test_0028_mysql_error.py b/packetbeat/tests/system/test_0028_mysql_error.py index 03b014eea49..fbffc430773 100644 --- a/packetbeat/tests/system/test_0028_mysql_error.py +++ b/packetbeat/tests/system/test_0028_mysql_error.py @@ -13,7 +13,7 @@ def test_mysql_error(self): objs = self.read_output() assert all([o["type"] == "mysql" for o in objs]) assert len(objs) == 1 - assert all([o["port"] == 3306 for o in objs]) + assert all([o["server.port"] == 3306 for o in objs]) assert objs[0]["method"] == "SELECT" assert objs[0]["status"] == "Error" diff --git a/packetbeat/tests/system/test_0032_dns.py b/packetbeat/tests/system/test_0032_dns.py index ef01af80a5f..6d22ecb6047 100644 --- a/packetbeat/tests/system/test_0032_dns.py +++ b/packetbeat/tests/system/test_0032_dns.py @@ -109,7 +109,7 @@ def test_TXT(self): assert o["type"] == "dns" assert o["transport"] == "udp" assert o["method"] == "QUERY" - assert o["ip"] == "8.8.8.8" + assert o["server.ip"] == "8.8.8.8" assert o["query"] == "class IN, type TXT, elastic.co." assert o["dns.question.type"] == "TXT" assert o["status"] == "OK" diff --git a/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py b/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py index c4926597fc2..ac901a93f81 100644 --- a/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py +++ b/packetbeat/tests/system/test_0040_memcache_tcp_bin_basic.py @@ -23,9 +23,9 @@ def _run(self, pcap): def assert_common(self, objs): # check client ip are not mixed up - assert all(o['client_ip'] == '192.168.188.37' for o in objs) - assert all(o['ip'] == '192.168.188.38' for o in objs) - assert all(o['port'] == 11211 for o in objs) + assert all(o['client.ip'] == '192.168.188.37' for o in objs) + assert all(o['server.ip'] == '192.168.188.38' for o in objs) + assert all(o['server.port'] == 11211 for o in objs) # check transport layer always tcp assert all(o['type'] == 'memcache' for o in objs) diff --git a/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py b/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py index f28dc7e1a9b..ec4004bc178 100644 --- a/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py +++ b/packetbeat/tests/system/test_0040_memcache_tcp_text_basic.py @@ -23,9 +23,9 @@ def _run(self, pcap): def assert_common(self, objs): # check client ip are not mixed up - assert all(o['client_ip'] == '192.168.188.37' for o in objs) - assert all(o['ip'] == '192.168.188.38' for o in objs) - assert all(o['port'] == 11211 for o in objs) + assert all(o['client.ip'] == '192.168.188.37' for o in objs) + assert all(o['server.ip'] == '192.168.188.38' for o in objs) + assert all(o['server.port'] == 11211 for o in objs) # check transport layer always tcp assert all(o['type'] == 'memcache' for o in objs) diff --git a/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py b/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py index b9c63e7dc64..c065fe64965 100644 --- a/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py +++ b/packetbeat/tests/system/test_0041_memcache_udp_bin_basic.py @@ -24,9 +24,9 @@ def _run(self, pcap): def assert_common(self, objs): # check client ip are not mixed up - assert all(o['client_ip'] == '192.168.188.37' for o in objs) - assert all(o['ip'] == '192.168.188.38' for o in objs) - assert all(o['port'] == 11211 for o in objs) + assert all(o['client.ip'] == '192.168.188.37' for o in objs) + assert all(o['server.ip'] == '192.168.188.38' for o in objs) + assert all(o['server.port'] == 11211 for o in objs) # check transport layer always udp assert all(o['type'] == 'memcache' for o in objs) diff --git a/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py b/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py index 24aee797307..7f4dda9fae9 100644 --- a/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py +++ b/packetbeat/tests/system/test_0041_memcache_udp_text_basic.py @@ -25,9 +25,9 @@ def _run(self, pcap): def assert_common(self, objs): # check client ip are not mixed up - assert all(o['client_ip'] == '192.168.188.37' for o in objs) - assert all(o['ip'] == '192.168.188.38' for o in objs) - assert all(o['port'] == 11211 for o in objs) + assert all(o['client.ip'] == '192.168.188.37' for o in objs) + assert all(o['server.ip'] == '192.168.188.38' for o in objs) + assert all(o['server.port'] == 11211 for o in objs) # check transport layer always tcp assert all(o['type'] == 'memcache' for o in objs) diff --git a/packetbeat/tests/system/test_0050_icmp.py b/packetbeat/tests/system/test_0050_icmp.py index 73a1912cfb6..1d82cc8541f 100644 --- a/packetbeat/tests/system/test_0050_icmp.py +++ b/packetbeat/tests/system/test_0050_icmp.py @@ -70,12 +70,12 @@ def assert_common_fields(self, objs): assert all([o["type"] == "icmp" for o in objs]) assert all([o["bytes_in"] == 4 for o in objs]) assert all([o["bytes_out"] == 4 for o in objs]) - assert all([("port" in o) == False for o in objs]) + assert all([("server.port" in o) == False for o in objs]) assert all([("transport" in o) == False for o in objs]) def assert_common_icmp4_fields(self, obj): - assert obj["ip"] == "10.0.0.2" - assert obj["client_ip"] == "10.0.0.1" + assert obj["server.ip"] == "10.0.0.2" + assert obj["client.ip"] == "10.0.0.1" assert obj["path"] == "10.0.0.2" assert obj["status"] == "OK" assert obj["icmp.request.message"] == "EchoRequest(0)" @@ -86,8 +86,8 @@ def assert_common_icmp4_fields(self, obj): assert obj["icmp.response.code"] == 0 def assert_common_icmp6_fields(self, obj): - assert obj["ip"] == "::2" - assert obj["client_ip"] == "::1" + assert obj["server.ip"] == "::2" + assert obj["client.ip"] == "::1" assert obj["path"] == "::2" assert obj["status"] == "OK" assert obj["icmp.request.message"] == "EchoRequest(0)" diff --git a/packetbeat/tests/system/test_0051_amqp_publish.py b/packetbeat/tests/system/test_0051_amqp_publish.py index 414dd6a6eb3..cf7eeee717a 100644 --- a/packetbeat/tests/system/test_0051_amqp_publish.py +++ b/packetbeat/tests/system/test_0051_amqp_publish.py @@ -14,7 +14,7 @@ def test_amqp_publish(self): objs = self.read_output() assert all([o["type"] == "amqp" for o in objs]) assert len(objs) == 2 - assert all([o["port"] == 5672 for o in objs]) + assert all([o["server.port"] == 5672 for o in objs]) assert objs[0]["method"] == "queue.declare" assert objs[0]["status"] == "OK" diff --git a/packetbeat/tests/system/test_0052_amqp_emit_receive.py b/packetbeat/tests/system/test_0052_amqp_emit_receive.py index dfa6c4f4785..d0649798558 100644 --- a/packetbeat/tests/system/test_0052_amqp_emit_receive.py +++ b/packetbeat/tests/system/test_0052_amqp_emit_receive.py @@ -13,7 +13,7 @@ def test_amqp_emit_receive(self): objs = self.read_output() assert all([o["type"] == "amqp" for o in objs]) assert len(objs) == 7 - assert all([o["port"] == 5672 for o in objs]) + assert all([o["server.port"] == 5672 for o in objs]) assert objs[0]["method"] == "exchange.declare" assert objs[0]["status"] == "OK" diff --git a/packetbeat/tests/system/test_0062_cassandra.py b/packetbeat/tests/system/test_0062_cassandra.py index c6ca2b27b06..12c8b957d67 100644 --- a/packetbeat/tests/system/test_0062_cassandra.py +++ b/packetbeat/tests/system/test_0062_cassandra.py @@ -23,7 +23,7 @@ def test_create_keyspace(self): o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o[ "cassandra.request.query"] == "CREATE KEYSPACE mykeyspace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };" @@ -59,7 +59,7 @@ def test_create_table(self): objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o[ "cassandra.request.query"] == "CREATE TABLE users (\n user_id int PRIMARY KEY,\n fname text,\n lname text\n);" @@ -93,7 +93,7 @@ def test_insert_data(self): o = objs[0] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o[ "cassandra.request.query"] == "INSERT INTO users (user_id, fname, lname)\n VALUES (1745, 'john', 'smith');" @@ -125,7 +125,7 @@ def test_select_data(self): objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["cassandra.request.query"] == "SELECT * FROM users;" assert o["cassandra.request.headers.version"] == "4" @@ -156,7 +156,7 @@ def test_create_index(self): objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["cassandra.request.query"] == "CREATE INDEX ON users (lname);" assert o["cassandra.request.headers.version"] == "4" @@ -188,7 +188,7 @@ def test_trace_error(self): objs = self.read_output() o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 55 assert o["bytes_out"] == 62 @@ -227,7 +227,7 @@ def test_select_use_index(self): o = objs[0] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["cassandra.request.query"] == "SELECT * FROM users WHERE lname = 'smith';" @@ -262,7 +262,7 @@ def test_ops_mixed(self): o = objs[0] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 9 assert o["bytes_out"] == 61 @@ -281,7 +281,7 @@ def test_ops_mixed(self): o = objs[1] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 31 assert o["bytes_out"] == 9 @@ -300,7 +300,7 @@ def test_ops_mixed(self): o = objs[2] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 58 assert o["bytes_out"] == 9 @@ -334,7 +334,7 @@ def test_ops_ignored(self): o = objs[0] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 31 assert o["bytes_out"] == 9 @@ -352,7 +352,7 @@ def test_ops_ignored(self): o = objs[1] assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 101 assert o["bytes_out"] == 116 @@ -387,7 +387,7 @@ def test_compressed_frame(self): o = objs[0] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 52 assert o["bytes_out"] == 10 @@ -406,7 +406,7 @@ def test_compressed_frame(self): o = objs[1] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 53 assert o["bytes_out"] == 10 @@ -425,7 +425,7 @@ def test_compressed_frame(self): o = objs[2] print(o) assert o["type"] == "cassandra" - assert o["port"] == 9042 + assert o["server.port"] == 9042 assert o["bytes_in"] == 62 assert o["bytes_out"] == 165 diff --git a/packetbeat/tests/system/test_0066_dhcp.py b/packetbeat/tests/system/test_0066_dhcp.py index 984d906c183..09668440896 100644 --- a/packetbeat/tests/system/test_0066_dhcp.py +++ b/packetbeat/tests/system/test_0066_dhcp.py @@ -14,8 +14,8 @@ def test_dhcp(self): objs = self.read_output(types=['dhcpv4']) assert len(objs) == 4 - assert objs[0]["client_ip"] == "0.0.0.0" - assert objs[0]["client_port"] == 68 + assert objs[0]["client.ip"] == "0.0.0.0" + assert objs[0]["client.port"] == 68 assert objs[0]["dhcpv4.client_mac"] == "00:0b:82:01:fc:42" assert objs[0]["dhcpv4.flags"] == "unicast" assert objs[0]["dhcpv4.hardware_type"] == "Ethernet" @@ -31,14 +31,14 @@ def test_dhcp(self): assert objs[0]["dhcpv4.option.requested_ip_address"] == "0.0.0.0" assert objs[0]["dhcpv4.seconds"] == 0 assert objs[0]["dhcpv4.transaction_id"] == "0x00003d1d" - assert objs[0]["ip"] == "255.255.255.255" - assert objs[0]["port"] == 67 + assert objs[0]["server.ip"] == "255.255.255.255" + assert objs[0]["server.port"] == 67 assert objs[0]["status"] == "OK" assert objs[0]["transport"] == "udp" assert objs[0]["type"] == "dhcpv4" - assert objs[1]["client_ip"] == "192.168.0.10" - assert objs[1]["client_port"] == 68 + assert objs[1]["client.ip"] == "192.168.0.10" + assert objs[1]["client.port"] == 68 assert objs[1]["dhcpv4.assigned_ip"] == "192.168.0.10" assert objs[1]["dhcpv4.client_mac"] == "00:0b:82:01:fc:42" assert objs[1]["dhcpv4.flags"] == "unicast" @@ -53,14 +53,14 @@ def test_dhcp(self): assert objs[1]["dhcpv4.option.subnet_mask"] == "255.255.255.0" assert objs[1]["dhcpv4.seconds"] == 0 assert objs[1]["dhcpv4.transaction_id"] == "0x00003d1d" - assert objs[1]["ip"] == "192.168.0.1" - assert objs[1]["port"] == 67 + assert objs[1]["server.ip"] == "192.168.0.1" + assert objs[1]["server.port"] == 67 assert objs[1]["status"] == "OK" assert objs[1]["transport"] == "udp" assert objs[1]["type"] == "dhcpv4" - assert objs[2]["client_ip"] == "0.0.0.0" - assert objs[2]["client_port"] == 68 + assert objs[2]["client.ip"] == "0.0.0.0" + assert objs[2]["client.port"] == 68 assert objs[2]["dhcpv4.client_mac"] == "00:0b:82:01:fc:42" assert objs[2]["dhcpv4.flags"] == "unicast" assert objs[2]["dhcpv4.hardware_type"] == "Ethernet" @@ -77,14 +77,14 @@ def test_dhcp(self): assert objs[2]["dhcpv4.option.server_identifier"] == "192.168.0.1" assert objs[2]["dhcpv4.seconds"] == 0 assert objs[2]["dhcpv4.transaction_id"] == "0x00003d1e" - assert objs[2]["ip"] == "255.255.255.255" - assert objs[2]["port"] == 67 + assert objs[2]["server.ip"] == "255.255.255.255" + assert objs[2]["server.port"] == 67 assert objs[2]["status"] == "OK" assert objs[2]["transport"] == "udp" assert objs[2]["type"] == "dhcpv4" - assert objs[3]["client_ip"] == "192.168.0.10" - assert objs[3]["client_port"] == 68 + assert objs[3]["client.ip"] == "192.168.0.10" + assert objs[3]["client.port"] == 68 assert objs[3]["dhcpv4.assigned_ip"] == "192.168.0.10" assert objs[3]["dhcpv4.client_mac"] == "00:0b:82:01:fc:42" assert objs[3]["dhcpv4.flags"] == "unicast" @@ -99,8 +99,8 @@ def test_dhcp(self): assert objs[3]["dhcpv4.option.subnet_mask"] == "255.255.255.0" assert objs[3]["dhcpv4.seconds"] == 0 assert objs[3]["dhcpv4.transaction_id"] == "0x00003d1e" - assert objs[3]["ip"] == "192.168.0.1" - assert objs[3]["port"] == 67 + assert objs[3]["server.ip"] == "192.168.0.1" + assert objs[3]["server.port"] == 67 assert objs[3]["status"] == "OK" assert objs[3]["transport"] == "udp" assert objs[3]["type"] == "dhcpv4"