Skip to content

Commit a394120

Browse files
committed
Allow for custom MSDNS time format
- Add inputs->msdns_time_format field to config - Fix parsing of default timestamp with 12h clock (AM/PM)
1 parent 870a5fe commit a394120

File tree

5 files changed

+83
-16
lines changed

5 files changed

+83
-16
lines changed

config.yml

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ inputs:
7171
# Default: (none)
7272
file:
7373

74+
# Time format used for parsing MSDNS log files.
75+
# Format layout documented at https://golang.org/pkg/time/#Parse
76+
msdns_time_format:
77+
7478
################################################################################
7579
# The outputs section describes where NFR should send the alerts generated by
7680
# the Analytics Engine (e.g. Graylog, a local file, or terminal)

config/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ type Config struct {
7676

7777
// Monitors keeps list of log files to monitor.
7878
Monitors []Monitor `yaml:"monitor"`
79+
80+
// MSDNS time format
81+
MSDNSTimeFormat string `yaml:"msdns_time_format"`
7982
} `yaml:"inputs"`
8083

8184
// Outputs describes where should send the alerts generated by the Analytics Engine.

executor/executor.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,12 @@ func (e *Executor) openFileParser(file, fileFomrat string) (err error) {
567567
case "suricata":
568568
e.lr, err = suricata.NewFileParser(file)
569569
case "msdns":
570-
e.lr, err = msdns.NewFileParser(file)
570+
var p *msdns.Parser
571+
p, err = msdns.NewFileParser(file)
572+
if p != nil {
573+
p.TimeFormat = e.cfg.Inputs.MSDNSTimeFormat
574+
}
575+
e.lr = p
571576
case "syslog-named":
572577
e.lr, err = syslognamed.NewFileParser(file)
573578
default:

logs/msdns/parser.go

+64-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ import (
1616

1717
// A Parser parses and reads network events from msdns logs.
1818
type Parser struct {
19+
// TimeFormat used for parsing timestamp.
20+
// If not set, will accept the following format:
21+
// 2006-01-02 3:04:05 PM
22+
// 2006/01/02 3:04:05 PM
23+
// 2006-01-02 15:04:05
24+
// 2006/01/02 15:04:05
25+
TimeFormat string
26+
1927
r io.ReadCloser
2028
}
2129

@@ -66,34 +74,81 @@ func (*Parser) ReadIP() ([]*packet.IPPacket, error) {
6674

6775
var numToDotRe = regexp.MustCompile(`\(\d+\)`)
6876

77+
func guessTimeFormat(s string) string {
78+
if len(s) < 10 {
79+
return ""
80+
}
81+
82+
sep := s[4] // date separator
83+
ampm := (s[len(s)-1] == 'M') // use 12h clock (AM/PM)
84+
85+
switch true {
86+
case sep == '-' && ampm:
87+
return "2006-01-02 3:04:05 PM"
88+
case sep == '-' && !ampm:
89+
return "2006-01-02 15:04:05"
90+
case sep == '/' && ampm:
91+
return "2006/01/02 3:04:05 PM"
92+
case sep == '/' && !ampm:
93+
return "2006/01/02 15:04:05"
94+
}
95+
96+
return ""
97+
}
98+
6999
// ParseLineDNS parse single log line with dns data.
70-
func (*Parser) ParseLineDNS(line string) (*packet.DNSPacket, error) {
100+
func (p *Parser) ParseLineDNS(line string) (*packet.DNSPacket, error) {
71101
s := strings.Fields(line)
72-
if len(s) == 16 && (s[2] == "AM" || s[2] == "PM") {
73-
s = append(s[:3], s[4:]...)
102+
103+
if len(s) < 15 {
104+
return nil, nil
105+
}
106+
107+
// find Context field, which can be 4th or 5th field,
108+
// depends if timestamp consists of 2 or 3 fields.
109+
contextIdx := 0
110+
switch "PACKET" {
111+
case s[3]:
112+
contextIdx = 3
113+
case s[4]:
114+
contextIdx = 4
115+
default:
116+
return nil, nil
74117
}
75118

76-
if len(s) != 15 || s[3] != "PACKET" || s[6] != "Rcv" || s[9] != "Q" {
119+
ts := strings.Join(s[:contextIdx-1], " ")
120+
s = s[contextIdx:]
121+
122+
// fields are now starting with Context, expect 12 fields
123+
if len(s) != 12 || s[3] != "Rcv" || s[6] != "Q" {
77124
return nil, nil
78125
}
79126

80-
timestamp, err := time.Parse("2006-01-02 15:04:05", s[0]+" "+s[1])
127+
timeFormat := p.TimeFormat
128+
if timeFormat == "" {
129+
timeFormat = guessTimeFormat(ts)
130+
}
131+
if timeFormat == "" {
132+
return nil, fmt.Errorf("Unknown time format for timestamp: %s", ts)
133+
}
134+
135+
timestamp, err := time.Parse(timeFormat, ts)
81136
if err != nil {
82137
return nil, err
83138
}
84139

85-
srcIP := net.ParseIP(s[7])
140+
srcIP := net.ParseIP(s[4])
86141
if err != nil {
87142
return nil, fmt.Errorf("invalid source ip at line %s", line)
88143
}
89144

90145
return &packet.DNSPacket{
91146
DstPort: 0,
92-
Protocol: strings.ToLower(s[5]),
147+
Protocol: strings.ToLower(s[2]),
93148
Timestamp: timestamp,
94149
SrcIP: srcIP,
95-
RecordType: s[13],
96-
FQDN: strings.Trim(numToDotRe.ReplaceAllString(s[14], "."), "."),
150+
RecordType: s[10],
151+
FQDN: strings.Trim(numToDotRe.ReplaceAllString(s[11], "."), "."),
97152
}, nil
98153
}
99154

logs/msdns/parser_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ Message logging key (for packets - other items use a subset of these fields):
4545
14 ResponseCode ]
4646
15 Question Type
4747
16 Question Name
48-
2017-01-01 00:00:00 01A0 EVENT The DNS server did not detect any zones of either primary or secondary type during initialization. It will not be authoritative for any zones, and it will run as a caching-only server until a zone is loaded manually or by Active Directory replication. For more information, see the online Help.
49-
2017-01-01 00:00:00 01A0 EVENT The DNS server has started.
50-
2017-01-01 00:00:00 0DB8 PACKET 0000000001962BB0 UDP Rcv 10.0.0.1 0030 Q [0001 D NOERROR] A (8)alphasoc(3)com(0)
51-
2017-01-01 00:00:00 0DB8 PACKET 0000000001962BB0 UDP Snd 127.0.0.1 0030 Q [0001 D NOERROR] A (8)alphasoc(3)com(0)
52-
2017-01-01 00:00:00 AM 0DB8 PACKET 0000000001962BB0 TCP Rcv 10.0.0.2 0030 Q [0001 D NOERROR] AAAA (8)alphasoc(3)net(0)
48+
2017-01-02 00:00:00 01A0 EVENT The DNS server did not detect any zones of either primary or secondary type during initialization. It will not be authoritative for any zones, and it will run as a caching-only server until a zone is loaded manually or by Active Directory replication. For more information, see the online Help.
49+
2017-01-02 00:00:00 01A0 EVENT The DNS server has started.
50+
2017-01-02 21:00:00 0DB8 PACKET 0000000001962BB0 UDP Rcv 10.0.0.1 0030 Q [0001 D NOERROR] A (8)alphasoc(3)com(0)
51+
2017-01-02 00:00:00 0DB8 PACKET 0000000001962BB0 UDP Snd 127.0.0.1 0030 Q [0001 D NOERROR] A (8)alphasoc(3)com(0)
52+
2017/01/02 09:00:00 PM 0DB8 PACKET 0000000001962BB0 TCP Rcv 10.0.0.2 0030 Q [0001 D NOERROR] AAAA (8)alphasoc(3)net(0)
5353
`
5454
)
5555

@@ -73,7 +73,7 @@ Message logging key (for packets - other items use a subset of these fields):
7373
t.Fatalf("reading msdns dns package failed - want: 2, got: %d", len(packets))
7474
}
7575

76-
tc := time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC)
76+
tc := time.Date(2017, 1, 2, 21, 0, 0, 0, time.UTC)
7777
if !(packets[0].DstPort == 0 &&
7878
packets[0].Protocol == "udp" &&
7979
packets[0].Timestamp.Equal(tc) &&

0 commit comments

Comments
 (0)