New upstream version 0.0~git20190601.0b64a38
Sascha Steinbiss
3 years ago
0 | *.swp |
0 | Copyright (c) 2013-2014 by Farsight Security, Inc. | |
1 | ||
2 | Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | you may not use this file except in compliance with the License. | |
4 | You may obtain a copy of the License at | |
5 | ||
6 | http://www.apache.org/licenses/LICENSE-2.0 | |
7 | ||
8 | Unless required by applicable law or agreed to in writing, software | |
9 | distributed under the License is distributed on an "AS IS" BASIS, | |
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | See the License for the specific language governing permissions and | |
12 | limitations under the License. | |
13 |
0 | /* | |
1 | * Copyright (c) 2013-2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "io" | |
20 | "log" | |
21 | "os" | |
22 | "time" | |
23 | ||
24 | "github.com/farsightsec/golang-framestream" | |
25 | ) | |
26 | ||
27 | // MaxPayloadSize sets the upper limit on input Dnstap payload sizes. If an Input | |
28 | // receives a Dnstap payload over this size limit, ReadInto will log an error and | |
29 | // return. | |
30 | // | |
31 | // EDNS0 and DNS over TCP use 2 octets for DNS message size, imposing a maximum | |
32 | // size of 65535 octets for the DNS message, which is the bulk of the data carried | |
33 | // in a Dnstap message. Protobuf encoding overhead and metadata with some size | |
34 | // guidance (e.g., identity and version being DNS strings, which have a maximum | |
35 | // length of 255) add up to less than 1KB. The default 96KiB size of the buffer | |
36 | // allows a bit over 30KB space for "extra" metadata. | |
37 | // | |
38 | var MaxPayloadSize uint32 = 96 * 1024 | |
39 | ||
40 | // A FrameStreamInput reads dnstap data from an io.ReadWriter. | |
41 | type FrameStreamInput struct { | |
42 | wait chan bool | |
43 | decoder *framestream.Decoder | |
44 | timeout time.Duration | |
45 | } | |
46 | ||
47 | // NewFrameStreamInput creates a FrameStreamInput reading data from the given | |
48 | // io.ReadWriter. If bi is true, the input will use the bidirectional | |
49 | // framestream protocol suitable for TCP and unix domain socket connections. | |
50 | func NewFrameStreamInput(r io.ReadWriter, bi bool) (input *FrameStreamInput, err error) { | |
51 | return NewFrameStreamInputTimeout(r, bi, 0) | |
52 | } | |
53 | ||
54 | // NewFrameStreamInputTimeout creates a FramestreamInput reading data from the | |
55 | // given io.ReadWriter with a timeout applied to reading and (for bidirectional | |
56 | // inputs) writing control messages. | |
57 | func NewFrameStreamInputTimeout(r io.ReadWriter, bi bool, timeout time.Duration) (input *FrameStreamInput, err error) { | |
58 | input = new(FrameStreamInput) | |
59 | decoderOptions := framestream.DecoderOptions{ | |
60 | MaxPayloadSize: MaxPayloadSize, | |
61 | ContentType: FSContentType, | |
62 | Bidirectional: bi, | |
63 | Timeout: timeout, | |
64 | } | |
65 | input.decoder, err = framestream.NewDecoder(r, &decoderOptions) | |
66 | if err != nil { | |
67 | return | |
68 | } | |
69 | input.wait = make(chan bool) | |
70 | return | |
71 | } | |
72 | ||
73 | // NewFrameStreamInputFromFilename creates a FrameStreamInput reading from | |
74 | // the named file. | |
75 | func NewFrameStreamInputFromFilename(fname string) (input *FrameStreamInput, err error) { | |
76 | file, err := os.Open(fname) | |
77 | if err != nil { | |
78 | return nil, err | |
79 | } | |
80 | input, err = NewFrameStreamInput(file, false) | |
81 | return | |
82 | } | |
83 | ||
84 | // ReadInto reads data from the FrameStreamInput into the output channel. | |
85 | // | |
86 | // ReadInto satisfies the dnstap Input interface. | |
87 | func (input *FrameStreamInput) ReadInto(output chan []byte) { | |
88 | for { | |
89 | buf, err := input.decoder.Decode() | |
90 | if err != nil { | |
91 | if err != io.EOF { | |
92 | log.Printf("framestream.Decoder.Decode() failed: %s\n", err) | |
93 | } | |
94 | break | |
95 | } | |
96 | newbuf := make([]byte, len(buf)) | |
97 | copy(newbuf, buf) | |
98 | output <- newbuf | |
99 | } | |
100 | close(input.wait) | |
101 | } | |
102 | ||
103 | // Wait reeturns when ReadInto has finished. | |
104 | // | |
105 | // Wait satisfies the dnstap Input interface. | |
106 | func (input *FrameStreamInput) Wait() { | |
107 | <-input.wait | |
108 | } |
0 | /* | |
1 | * Copyright (c) 2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "io" | |
20 | "log" | |
21 | "os" | |
22 | ||
23 | "github.com/farsightsec/golang-framestream" | |
24 | ) | |
25 | ||
26 | // FrameStreamOutput implements a dnstap Output to an io.Writer. | |
27 | type FrameStreamOutput struct { | |
28 | outputChannel chan []byte | |
29 | wait chan bool | |
30 | enc *framestream.Encoder | |
31 | } | |
32 | ||
33 | // NewFrameStreamOutput creates a FrameStreamOutput writing dnstap data to | |
34 | // the given io.Writer. | |
35 | func NewFrameStreamOutput(w io.Writer) (o *FrameStreamOutput, err error) { | |
36 | o = new(FrameStreamOutput) | |
37 | o.outputChannel = make(chan []byte, outputChannelSize) | |
38 | o.enc, err = framestream.NewEncoder(w, &framestream.EncoderOptions{ContentType: FSContentType}) | |
39 | if err != nil { | |
40 | return | |
41 | } | |
42 | o.wait = make(chan bool) | |
43 | return | |
44 | } | |
45 | ||
46 | // NewFrameStreamOutputFromFilename creates a file with the namee fname, | |
47 | // truncates it if it exists, and returns a FrameStreamOutput writing to | |
48 | // the newly created or truncated file. | |
49 | func NewFrameStreamOutputFromFilename(fname string) (o *FrameStreamOutput, err error) { | |
50 | if fname == "" || fname == "-" { | |
51 | return NewFrameStreamOutput(os.Stdout) | |
52 | } | |
53 | w, err := os.Create(fname) | |
54 | if err != nil { | |
55 | return | |
56 | } | |
57 | return NewFrameStreamOutput(w) | |
58 | } | |
59 | ||
60 | // GetOutputChannel returns the channel on which the FrameStreamOutput accepts | |
61 | // data. | |
62 | // | |
63 | // GetOutputData satisfies the dnstap Output interface. | |
64 | func (o *FrameStreamOutput) GetOutputChannel() chan []byte { | |
65 | return o.outputChannel | |
66 | } | |
67 | ||
68 | // RunOutputLoop processes data received on the channel returned by | |
69 | // GetOutputChannel, returning after the CLose method is called. | |
70 | // If there is an error writing to the Output's writer, RunOutputLoop() | |
71 | // logs a fatal error exits the program. | |
72 | // | |
73 | // RunOutputLoop satisfies the dnstap Output interface. | |
74 | func (o *FrameStreamOutput) RunOutputLoop() { | |
75 | for frame := range o.outputChannel { | |
76 | if _, err := o.enc.Write(frame); err != nil { | |
77 | log.Fatalf("framestream.Encoder.Write() failed: %s\n", err) | |
78 | break | |
79 | } | |
80 | } | |
81 | close(o.wait) | |
82 | } | |
83 | ||
84 | // Close closes the channel returned from GetOutputChannel, and flushes | |
85 | // all pending output. | |
86 | // | |
87 | // Close satisifies the dnstap Output interface. | |
88 | func (o *FrameStreamOutput) Close() { | |
89 | close(o.outputChannel) | |
90 | <-o.wait | |
91 | o.enc.Flush() | |
92 | o.enc.Close() | |
93 | } |
0 | /* | |
1 | * Copyright (c) 2013-2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "log" | |
20 | "net" | |
21 | "os" | |
22 | "time" | |
23 | ) | |
24 | ||
25 | // A FrameStreamSockInput collects dnstap data from one or more clients of | |
26 | // a listening socket. | |
27 | type FrameStreamSockInput struct { | |
28 | wait chan bool | |
29 | listener net.Listener | |
30 | timeout time.Duration | |
31 | } | |
32 | ||
33 | // NewFrameStreamSockInput creates a FrameStreamSockInput collecting dnstap | |
34 | // data from clients which connect to the given listener. | |
35 | func NewFrameStreamSockInput(listener net.Listener) (input *FrameStreamSockInput) { | |
36 | input = new(FrameStreamSockInput) | |
37 | input.listener = listener | |
38 | return | |
39 | } | |
40 | ||
41 | // SetTimeout sets the timeout for reading the initial handshake and writing | |
42 | // response control messages to clients of the FrameStreamSockInput's listener. | |
43 | // | |
44 | // The timeout is effective only for connections accepted after the call to | |
45 | // FrameStreamSockInput. | |
46 | func (input *FrameStreamSockInput) SetTimeout(timeout time.Duration) { | |
47 | input.timeout = timeout | |
48 | } | |
49 | ||
50 | // NewFrameStreamSockInputFromPath creates a unix domain socket at the | |
51 | // given socketPath and returns a FrameStreamSockInput collecting dnstap | |
52 | // data from clients connecting to this socket. | |
53 | // | |
54 | // If a socket or other file already exists at socketPath, | |
55 | // NewFrameStreamSockInputFromPath removes it before creating the socket. | |
56 | func NewFrameStreamSockInputFromPath(socketPath string) (input *FrameStreamSockInput, err error) { | |
57 | os.Remove(socketPath) | |
58 | listener, err := net.Listen("unix", socketPath) | |
59 | if err != nil { | |
60 | return | |
61 | } | |
62 | return NewFrameStreamSockInput(listener), nil | |
63 | } | |
64 | ||
65 | // ReadInto accepts connections to the FrameStreamSockInput's listening | |
66 | // socket and sends all dnstap data read from these connections to the | |
67 | // output channel. | |
68 | // | |
69 | // ReadInto satisfies the dnstap Input interface. | |
70 | func (input *FrameStreamSockInput) ReadInto(output chan []byte) { | |
71 | for { | |
72 | conn, err := input.listener.Accept() | |
73 | if err != nil { | |
74 | log.Printf("net.Listener.Accept() failed: %s\n", err) | |
75 | continue | |
76 | } | |
77 | i, err := NewFrameStreamInputTimeout(conn, true, input.timeout) | |
78 | if err != nil { | |
79 | log.Printf("dnstap.NewFrameStreamInput() failed: %s\n", err) | |
80 | continue | |
81 | } | |
82 | log.Printf("dnstap.FrameStreamSockInput: accepted a socket connection\n") | |
83 | go i.ReadInto(output) | |
84 | } | |
85 | } | |
86 | ||
87 | // Wait satisfies the dnstap Input interface. | |
88 | // | |
89 | // The FrameSTreamSocketInput Wait method never returns, because the | |
90 | // corresponding Readinto method also never returns. | |
91 | func (input *FrameStreamSockInput) Wait() { | |
92 | select {} | |
93 | } |
0 | /* | |
1 | * Copyright (c) 2019 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "log" | |
20 | "net" | |
21 | "time" | |
22 | ||
23 | "github.com/farsightsec/golang-framestream" | |
24 | ) | |
25 | ||
26 | // A FrameStreamSockOutput manages a socket connection and sends dnstap | |
27 | // data over a framestream connection on that socket. | |
28 | type FrameStreamSockOutput struct { | |
29 | outputChannel chan []byte | |
30 | address net.Addr | |
31 | wait chan bool | |
32 | dialer *net.Dialer | |
33 | timeout time.Duration | |
34 | retry time.Duration | |
35 | flushTimeout time.Duration | |
36 | } | |
37 | ||
38 | // NewFrameStreamSockOutput creates a FrameStreamSockOutput manaaging a | |
39 | // connection to the given address. | |
40 | func NewFrameStreamSockOutput(address net.Addr) (*FrameStreamSockOutput, error) { | |
41 | return &FrameStreamSockOutput{ | |
42 | outputChannel: make(chan []byte, outputChannelSize), | |
43 | address: address, | |
44 | wait: make(chan bool), | |
45 | retry: 10 * time.Second, | |
46 | flushTimeout: 5 * time.Second, | |
47 | dialer: &net.Dialer{ | |
48 | Timeout: 30 * time.Second, | |
49 | }, | |
50 | }, nil | |
51 | } | |
52 | ||
53 | // SetTimeout sets the write timeout for data and control messages and the | |
54 | // read timeout for handshake responses on the FrameStreamSockOutput's | |
55 | // connection. The default timeout is zero, for no timeout. | |
56 | func (o *FrameStreamSockOutput) SetTimeout(timeout time.Duration) { | |
57 | o.timeout = timeout | |
58 | } | |
59 | ||
60 | // SetFlushTimeout sets the maximum time data will be kept in the output | |
61 | // buffer. | |
62 | // | |
63 | // The default flush timeout is five seconds. | |
64 | func (o *FrameStreamSockOutput) SetFlushTimeout(timeout time.Duration) { | |
65 | o.flushTimeout = timeout | |
66 | } | |
67 | ||
68 | // SetRetryInterval specifies how long the FrameStreamSockOutput will wait | |
69 | // before re-establishing a failed connection. The default retry interval | |
70 | // is 10 seconds. | |
71 | func (o *FrameStreamSockOutput) SetRetryInterval(retry time.Duration) { | |
72 | o.retry = retry | |
73 | } | |
74 | ||
75 | // SetDialer replaces the default net.Dialer for re-establishing the | |
76 | // the FrameStreamSockOutput connection. This can be used to set the | |
77 | // timeout for connection establishment and enable keepalives | |
78 | // new connections. | |
79 | // | |
80 | // FrameStreamSockOutput uses a default dialer with a 30 second | |
81 | // timeout. | |
82 | func (o *FrameStreamSockOutput) SetDialer(dialer *net.Dialer) { | |
83 | o.dialer = dialer | |
84 | } | |
85 | ||
86 | // GetOutputChannel returns the channel on which the | |
87 | // FrameStreamSockOutput accepts data. | |
88 | // | |
89 | // GetOutputChannel satisifes the dnstap Output interface. | |
90 | func (o *FrameStreamSockOutput) GetOutputChannel() chan []byte { | |
91 | return o.outputChannel | |
92 | } | |
93 | ||
94 | // A timedConn resets an associated timer on each Write to the underlying | |
95 | // connection, and is used to implement the output's flush timeout. | |
96 | type timedConn struct { | |
97 | net.Conn | |
98 | timer *time.Timer | |
99 | timeout time.Duration | |
100 | ||
101 | // idle is true if the timer has fired and we have consumed | |
102 | // the time from its channel. We use this to prevent deadlocking | |
103 | // when resetting or stopping an already fired timer. | |
104 | idle bool | |
105 | } | |
106 | ||
107 | // SetIdle informs the timedConn that the associated timer is idle, i.e. | |
108 | // it has fired and has not been reset. | |
109 | func (t *timedConn) SetIdle() { | |
110 | t.idle = true | |
111 | } | |
112 | ||
113 | // Stop stops the underlying timer, consuming any time value if the timer | |
114 | // had fired before Stop was called. | |
115 | func (t *timedConn) StopTimer() { | |
116 | if !t.timer.Stop() && !t.idle { | |
117 | <-t.timer.C | |
118 | } | |
119 | t.idle = true | |
120 | } | |
121 | ||
122 | func (t *timedConn) Write(b []byte) (int, error) { | |
123 | t.StopTimer() | |
124 | t.timer.Reset(t.timeout) | |
125 | t.idle = false | |
126 | return t.Conn.Write(b) | |
127 | } | |
128 | ||
129 | func (t *timedConn) Close() error { | |
130 | t.StopTimer() | |
131 | return t.Conn.Close() | |
132 | } | |
133 | ||
134 | // RunOutputLoop reads data from the output channel and sends it over | |
135 | // a connections to the FrameStreamSockOutput's address, establishing | |
136 | // the connection as needed. | |
137 | // | |
138 | // RunOutputLoop satisifes the dnstap Output interface. | |
139 | func (o *FrameStreamSockOutput) RunOutputLoop() { | |
140 | var enc *framestream.Encoder | |
141 | var err error | |
142 | ||
143 | // Start with the connection flush timer in a stopped state. | |
144 | // It will be reset by the first Write call on a new connection. | |
145 | conn := &timedConn{ | |
146 | timer: time.NewTimer(0), | |
147 | timeout: o.flushTimeout, | |
148 | } | |
149 | conn.StopTimer() | |
150 | ||
151 | defer func() { | |
152 | if enc != nil { | |
153 | enc.Flush() | |
154 | enc.Close() | |
155 | } | |
156 | if conn != nil { | |
157 | conn.Close() | |
158 | } | |
159 | close(o.wait) | |
160 | }() | |
161 | ||
162 | for { | |
163 | select { | |
164 | case frame, ok := <-o.outputChannel: | |
165 | if !ok { | |
166 | return | |
167 | } | |
168 | ||
169 | // the retry loop | |
170 | for ;; time.Sleep(o.retry) { | |
171 | if enc == nil { | |
172 | // connect the socket | |
173 | conn.Conn, err = o.dialer.Dial(o.address.Network(), o.address.String()) | |
174 | if err != nil { | |
175 | log.Printf("Dial() failed: %v", err) | |
176 | continue // = retry | |
177 | } | |
178 | ||
179 | // create the encoder | |
180 | eopt := framestream.EncoderOptions{ | |
181 | ContentType: FSContentType, | |
182 | Bidirectional: true, | |
183 | Timeout: o.timeout, | |
184 | } | |
185 | enc, err = framestream.NewEncoder(conn, &eopt) | |
186 | if err != nil { | |
187 | log.Printf("framestream.NewEncoder() failed: %v", err) | |
188 | conn.Close() | |
189 | enc = nil | |
190 | continue // = retry | |
191 | } | |
192 | } | |
193 | ||
194 | // try writing | |
195 | if _, err = enc.Write(frame); err != nil { | |
196 | log.Printf("framestream.Encoder.Write() failed: %v", err) | |
197 | enc.Close() | |
198 | enc = nil | |
199 | conn.Close() | |
200 | continue // = retry | |
201 | } | |
202 | ||
203 | break // success! | |
204 | } | |
205 | ||
206 | case <-conn.timer.C: | |
207 | conn.SetIdle() | |
208 | if enc == nil { | |
209 | continue | |
210 | } | |
211 | if err := enc.Flush(); err != nil { | |
212 | log.Printf("framestream.Encoder.Flush() failed: %s", err) | |
213 | enc.Close() | |
214 | enc = nil | |
215 | conn.Close() | |
216 | time.Sleep(o.retry) | |
217 | } | |
218 | } | |
219 | } | |
220 | } | |
221 | ||
222 | // Close shuts down the FrameStreamSockOutput's output channel and returns | |
223 | // after all pending data has been flushed and the connection has been closed. | |
224 | // | |
225 | // Close satisifes the dnstap Output interface | |
226 | func (o *FrameStreamSockOutput) Close() { | |
227 | close(o.outputChannel) | |
228 | <-o.wait | |
229 | } |
0 | /* | |
1 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | * you may not use this file except in compliance with the License. | |
3 | * You may obtain a copy of the License at | |
4 | * | |
5 | * http://www.apache.org/licenses/LICENSE-2.0 | |
6 | * | |
7 | * Unless required by applicable law or agreed to in writing, software | |
8 | * distributed under the License is distributed on an "AS IS" BASIS, | |
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | * See the License for the specific language governing permissions and | |
11 | * limitations under the License. | |
12 | */ | |
13 | ||
14 | package dnstap | |
15 | ||
16 | import ( | |
17 | "bytes" | |
18 | "encoding/json" | |
19 | "fmt" | |
20 | "net" | |
21 | "time" | |
22 | ||
23 | "github.com/miekg/dns" | |
24 | ) | |
25 | ||
26 | type jsonTime time.Time | |
27 | ||
28 | func (jt *jsonTime) MarshalJSON() ([]byte, error) { | |
29 | stamp := time.Time(*jt).Format(time.RFC3339Nano) | |
30 | return []byte(fmt.Sprintf("\"%s\"", stamp)), nil | |
31 | } | |
32 | ||
33 | type jsonDnstap struct { | |
34 | Type string `json:"type"` | |
35 | Identity string `json:"identity,omitempty"` | |
36 | Version string `json:"version,omitempty"` | |
37 | Message jsonMessage `json:"message"` | |
38 | } | |
39 | ||
40 | type jsonMessage struct { | |
41 | Type string `json:"type"` | |
42 | QueryTime *jsonTime `json:"query_time,omitempty"` | |
43 | ResponseTime *jsonTime `json:"response_time,omitempty"` | |
44 | SocketFamily string `json:"socket_family,omitempty"` | |
45 | SocketProtocol string `json:"socket_protocol,omitempty"` | |
46 | QueryAddress *net.IP `json:"query_address,omitempty"` | |
47 | ResponseAddress *net.IP `json:"response_address,omitempty"` | |
48 | QueryPort uint32 `json:"query_port,omitempty"` | |
49 | ResponsePort uint32 `json:"response_port,omitempty"` | |
50 | QueryZone string `json:"query_zone,omitempty"` | |
51 | QueryMessage string `json:"query_message,omitempty"` | |
52 | ResponseMessage string `json:"response_message,omitempty"` | |
53 | } | |
54 | ||
55 | func convertJSONMessage(m *Message) jsonMessage { | |
56 | jMsg := jsonMessage{ | |
57 | Type: fmt.Sprint(m.Type), | |
58 | SocketFamily: fmt.Sprint(m.SocketFamily), | |
59 | SocketProtocol: fmt.Sprint(m.SocketProtocol), | |
60 | } | |
61 | ||
62 | if m.QueryTimeSec != nil && m.QueryTimeNsec != nil { | |
63 | qt := jsonTime(time.Unix(int64(*m.QueryTimeSec), int64(*m.QueryTimeNsec)).UTC()) | |
64 | jMsg.QueryTime = &qt | |
65 | } | |
66 | ||
67 | if m.ResponseTimeSec != nil && m.ResponseTimeNsec != nil { | |
68 | rt := jsonTime(time.Unix(int64(*m.ResponseTimeSec), int64(*m.ResponseTimeNsec)).UTC()) | |
69 | jMsg.ResponseTime = &rt | |
70 | } | |
71 | ||
72 | if m.QueryAddress != nil { | |
73 | qa := net.IP(m.QueryAddress) | |
74 | jMsg.QueryAddress = &qa | |
75 | } | |
76 | ||
77 | if m.ResponseAddress != nil { | |
78 | ra := net.IP(m.ResponseAddress) | |
79 | jMsg.ResponseAddress = &ra | |
80 | } | |
81 | ||
82 | if m.QueryPort != nil { | |
83 | jMsg.QueryPort = *m.QueryPort | |
84 | } | |
85 | ||
86 | if m.ResponsePort != nil { | |
87 | jMsg.ResponsePort = *m.ResponsePort | |
88 | } | |
89 | ||
90 | if m.QueryZone != nil { | |
91 | name, _, err := dns.UnpackDomainName(m.QueryZone, 0) | |
92 | if err != nil { | |
93 | jMsg.QueryZone = fmt.Sprintf("parse failed: %v", err) | |
94 | } else { | |
95 | jMsg.QueryZone = string(name) | |
96 | } | |
97 | } | |
98 | ||
99 | if m.QueryMessage != nil { | |
100 | msg := new(dns.Msg) | |
101 | err := msg.Unpack(m.QueryMessage) | |
102 | if err != nil { | |
103 | jMsg.QueryMessage = fmt.Sprintf("parse failed: %v", err) | |
104 | } else { | |
105 | jMsg.QueryMessage = msg.String() | |
106 | } | |
107 | } | |
108 | ||
109 | if m.ResponseMessage != nil { | |
110 | msg := new(dns.Msg) | |
111 | err := msg.Unpack(m.ResponseMessage) | |
112 | if err != nil { | |
113 | jMsg.ResponseMessage = fmt.Sprintf("parse failed: %v", err) | |
114 | } else { | |
115 | jMsg.ResponseMessage = msg.String() | |
116 | } | |
117 | } | |
118 | return jMsg | |
119 | } | |
120 | ||
121 | // JSONFormat renders a Dnstap message in JSON format. Any encapsulated | |
122 | // DNS messages are rendered as strings in a format similar to 'dig' output. | |
123 | func JSONFormat(dt *Dnstap) (out []byte, ok bool) { | |
124 | var s bytes.Buffer | |
125 | ||
126 | j, err := json.Marshal(jsonDnstap{ | |
127 | Type: fmt.Sprint(dt.Type), | |
128 | Identity: string(dt.Identity), | |
129 | Version: string(dt.Version), | |
130 | Message: convertJSONMessage(dt.Message), | |
131 | }) | |
132 | if err != nil { | |
133 | return nil, false | |
134 | } | |
135 | ||
136 | s.WriteString(string(j) + "\n") | |
137 | ||
138 | return s.Bytes(), true | |
139 | } |
0 | ||
1 | Apache License | |
2 | Version 2.0, January 2004 | |
3 | http://www.apache.org/licenses/ | |
4 | ||
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
6 | ||
7 | 1. Definitions. | |
8 | ||
9 | "License" shall mean the terms and conditions for use, reproduction, | |
10 | and distribution as defined by Sections 1 through 9 of this document. | |
11 | ||
12 | "Licensor" shall mean the copyright owner or entity authorized by | |
13 | the copyright owner that is granting the License. | |
14 | ||
15 | "Legal Entity" shall mean the union of the acting entity and all | |
16 | other entities that control, are controlled by, or are under common | |
17 | control with that entity. For the purposes of this definition, | |
18 | "control" means (i) the power, direct or indirect, to cause the | |
19 | direction or management of such entity, whether by contract or | |
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
22 | ||
23 | "You" (or "Your") shall mean an individual or Legal Entity | |
24 | exercising permissions granted by this License. | |
25 | ||
26 | "Source" form shall mean the preferred form for making modifications, | |
27 | including but not limited to software source code, documentation | |
28 | source, and configuration files. | |
29 | ||
30 | "Object" form shall mean any form resulting from mechanical | |
31 | transformation or translation of a Source form, including but | |
32 | not limited to compiled object code, generated documentation, | |
33 | and conversions to other media types. | |
34 | ||
35 | "Work" shall mean the work of authorship, whether in Source or | |
36 | Object form, made available under the License, as indicated by a | |
37 | copyright notice that is included in or attached to the work | |
38 | (an example is provided in the Appendix below). | |
39 | ||
40 | "Derivative Works" shall mean any work, whether in Source or Object | |
41 | form, that is based on (or derived from) the Work and for which the | |
42 | editorial revisions, annotations, elaborations, or other modifications | |
43 | represent, as a whole, an original work of authorship. For the purposes | |
44 | of this License, Derivative Works shall not include works that remain | |
45 | separable from, or merely link (or bind by name) to the interfaces of, | |
46 | the Work and Derivative Works thereof. | |
47 | ||
48 | "Contribution" shall mean any work of authorship, including | |
49 | the original version of the Work and any modifications or additions | |
50 | to that Work or Derivative Works thereof, that is intentionally | |
51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
52 | or by an individual or Legal Entity authorized to submit on behalf of | |
53 | the copyright owner. For the purposes of this definition, "submitted" | |
54 | means any form of electronic, verbal, or written communication sent | |
55 | to the Licensor or its representatives, including but not limited to | |
56 | communication on electronic mailing lists, source code control systems, | |
57 | and issue tracking systems that are managed by, or on behalf of, the | |
58 | Licensor for the purpose of discussing and improving the Work, but | |
59 | excluding communication that is conspicuously marked or otherwise | |
60 | designated in writing by the copyright owner as "Not a Contribution." | |
61 | ||
62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
63 | on behalf of whom a Contribution has been received by Licensor and | |
64 | subsequently incorporated within the Work. | |
65 | ||
66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
67 | this License, each Contributor hereby grants to You a perpetual, | |
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
69 | copyright license to reproduce, prepare Derivative Works of, | |
70 | publicly display, publicly perform, sublicense, and distribute the | |
71 | Work and such Derivative Works in Source or Object form. | |
72 | ||
73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
74 | this License, each Contributor hereby grants to You a perpetual, | |
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
76 | (except as stated in this section) patent license to make, have made, | |
77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
78 | where such license applies only to those patent claims licensable | |
79 | by such Contributor that are necessarily infringed by their | |
80 | Contribution(s) alone or by combination of their Contribution(s) | |
81 | with the Work to which such Contribution(s) was submitted. If You | |
82 | institute patent litigation against any entity (including a | |
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
84 | or a Contribution incorporated within the Work constitutes direct | |
85 | or contributory patent infringement, then any patent licenses | |
86 | granted to You under this License for that Work shall terminate | |
87 | as of the date such litigation is filed. | |
88 | ||
89 | 4. Redistribution. You may reproduce and distribute copies of the | |
90 | Work or Derivative Works thereof in any medium, with or without | |
91 | modifications, and in Source or Object form, provided that You | |
92 | meet the following conditions: | |
93 | ||
94 | (a) You must give any other recipients of the Work or | |
95 | Derivative Works a copy of this License; and | |
96 | ||
97 | (b) You must cause any modified files to carry prominent notices | |
98 | stating that You changed the files; and | |
99 | ||
100 | (c) You must retain, in the Source form of any Derivative Works | |
101 | that You distribute, all copyright, patent, trademark, and | |
102 | attribution notices from the Source form of the Work, | |
103 | excluding those notices that do not pertain to any part of | |
104 | the Derivative Works; and | |
105 | ||
106 | (d) If the Work includes a "NOTICE" text file as part of its | |
107 | distribution, then any Derivative Works that You distribute must | |
108 | include a readable copy of the attribution notices contained | |
109 | within such NOTICE file, excluding those notices that do not | |
110 | pertain to any part of the Derivative Works, in at least one | |
111 | of the following places: within a NOTICE text file distributed | |
112 | as part of the Derivative Works; within the Source form or | |
113 | documentation, if provided along with the Derivative Works; or, | |
114 | within a display generated by the Derivative Works, if and | |
115 | wherever such third-party notices normally appear. The contents | |
116 | of the NOTICE file are for informational purposes only and | |
117 | do not modify the License. You may add Your own attribution | |
118 | notices within Derivative Works that You distribute, alongside | |
119 | or as an addendum to the NOTICE text from the Work, provided | |
120 | that such additional attribution notices cannot be construed | |
121 | as modifying the License. | |
122 | ||
123 | You may add Your own copyright statement to Your modifications and | |
124 | may provide additional or different license terms and conditions | |
125 | for use, reproduction, or distribution of Your modifications, or | |
126 | for any such Derivative Works as a whole, provided Your use, | |
127 | reproduction, and distribution of the Work otherwise complies with | |
128 | the conditions stated in this License. | |
129 | ||
130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
131 | any Contribution intentionally submitted for inclusion in the Work | |
132 | by You to the Licensor shall be under the terms and conditions of | |
133 | this License, without any additional terms or conditions. | |
134 | Notwithstanding the above, nothing herein shall supersede or modify | |
135 | the terms of any separate license agreement you may have executed | |
136 | with Licensor regarding such Contributions. | |
137 | ||
138 | 6. Trademarks. This License does not grant permission to use the trade | |
139 | names, trademarks, service marks, or product names of the Licensor, | |
140 | except as required for reasonable and customary use in describing the | |
141 | origin of the Work and reproducing the content of the NOTICE file. | |
142 | ||
143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
144 | agreed to in writing, Licensor provides the Work (and each | |
145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
147 | implied, including, without limitation, any warranties or conditions | |
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
150 | appropriateness of using or redistributing the Work and assume any | |
151 | risks associated with Your exercise of permissions under this License. | |
152 | ||
153 | 8. Limitation of Liability. In no event and under no legal theory, | |
154 | whether in tort (including negligence), contract, or otherwise, | |
155 | unless required by applicable law (such as deliberate and grossly | |
156 | negligent acts) or agreed to in writing, shall any Contributor be | |
157 | liable to You for damages, including any direct, indirect, special, | |
158 | incidental, or consequential damages of any character arising as a | |
159 | result of this License or out of the use or inability to use the | |
160 | Work (including but not limited to damages for loss of goodwill, | |
161 | work stoppage, computer failure or malfunction, or any and all | |
162 | other commercial damages or losses), even if such Contributor | |
163 | has been advised of the possibility of such damages. | |
164 | ||
165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
166 | the Work or Derivative Works thereof, You may choose to offer, | |
167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
168 | or other liability obligations and/or rights consistent with this | |
169 | License. However, in accepting such obligations, You may act only | |
170 | on Your own behalf and on Your sole responsibility, not on behalf | |
171 | of any other Contributor, and only if You agree to indemnify, | |
172 | defend, and hold each Contributor harmless for any liability | |
173 | incurred by, or claims asserted against, such Contributor by reason | |
174 | of your accepting any such warranty or additional liability. | |
175 | ||
176 | END OF TERMS AND CONDITIONS | |
177 | ||
178 | APPENDIX: How to apply the Apache License to your work. | |
179 | ||
180 | To apply the Apache License to your work, attach the following | |
181 | boilerplate notice, with the fields enclosed by brackets "[]" | |
182 | replaced with your own identifying information. (Don't include | |
183 | the brackets!) The text should be enclosed in the appropriate | |
184 | comment syntax for the file format. We also recommend that a | |
185 | file or class name and description of purpose be included on the | |
186 | same "printed page" as the copyright notice for easier | |
187 | identification within third-party archives. | |
188 | ||
189 | Copyright [yyyy] [name of copyright owner] | |
190 | ||
191 | Licensed under the Apache License, Version 2.0 (the "License"); | |
192 | you may not use this file except in compliance with the License. | |
193 | You may obtain a copy of the License at | |
194 | ||
195 | http://www.apache.org/licenses/LICENSE-2.0 | |
196 | ||
197 | Unless required by applicable law or agreed to in writing, software | |
198 | distributed under the License is distributed on an "AS IS" BASIS, | |
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
200 | See the License for the specific language governing permissions and | |
201 | limitations under the License. |
0 | /* | |
1 | * Copyright (c) 2013-2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "bytes" | |
20 | "fmt" | |
21 | "net" | |
22 | "strconv" | |
23 | "time" | |
24 | ||
25 | "github.com/miekg/dns" | |
26 | ) | |
27 | ||
28 | const quietTimeFormat = "15:04:05" | |
29 | ||
30 | func textConvertTime(s *bytes.Buffer, secs *uint64, nsecs *uint32) { | |
31 | if secs != nil { | |
32 | s.WriteString(time.Unix(int64(*secs), 0).Format(quietTimeFormat)) | |
33 | } else { | |
34 | s.WriteString("??:??:??") | |
35 | } | |
36 | if nsecs != nil { | |
37 | s.WriteString(fmt.Sprintf(".%06d", *nsecs/1000)) | |
38 | } else { | |
39 | s.WriteString(".??????") | |
40 | } | |
41 | } | |
42 | ||
43 | func textConvertIP(s *bytes.Buffer, ip []byte) { | |
44 | if ip != nil { | |
45 | s.WriteString(net.IP(ip).String()) | |
46 | } else { | |
47 | s.WriteString("MISSING_ADDRESS") | |
48 | } | |
49 | } | |
50 | ||
51 | func textConvertMessage(m *Message, s *bytes.Buffer) { | |
52 | isQuery := false | |
53 | printQueryAddress := false | |
54 | ||
55 | switch *m.Type { | |
56 | case Message_CLIENT_QUERY, | |
57 | Message_RESOLVER_QUERY, | |
58 | Message_AUTH_QUERY, | |
59 | Message_FORWARDER_QUERY, | |
60 | Message_TOOL_QUERY: | |
61 | isQuery = true | |
62 | case Message_CLIENT_RESPONSE, | |
63 | Message_RESOLVER_RESPONSE, | |
64 | Message_AUTH_RESPONSE, | |
65 | Message_FORWARDER_RESPONSE, | |
66 | Message_TOOL_RESPONSE: | |
67 | isQuery = false | |
68 | default: | |
69 | s.WriteString("[unhandled Message.Type]\n") | |
70 | return | |
71 | } | |
72 | ||
73 | if isQuery { | |
74 | textConvertTime(s, m.QueryTimeSec, m.QueryTimeNsec) | |
75 | } else { | |
76 | textConvertTime(s, m.ResponseTimeSec, m.ResponseTimeNsec) | |
77 | } | |
78 | s.WriteString(" ") | |
79 | ||
80 | switch *m.Type { | |
81 | case Message_CLIENT_QUERY, | |
82 | Message_CLIENT_RESPONSE: | |
83 | { | |
84 | s.WriteString("C") | |
85 | } | |
86 | case Message_RESOLVER_QUERY, | |
87 | Message_RESOLVER_RESPONSE: | |
88 | { | |
89 | s.WriteString("R") | |
90 | } | |
91 | case Message_AUTH_QUERY, | |
92 | Message_AUTH_RESPONSE: | |
93 | { | |
94 | s.WriteString("A") | |
95 | } | |
96 | case Message_FORWARDER_QUERY, | |
97 | Message_FORWARDER_RESPONSE: | |
98 | { | |
99 | s.WriteString("F") | |
100 | } | |
101 | case Message_STUB_QUERY, | |
102 | Message_STUB_RESPONSE: | |
103 | { | |
104 | s.WriteString("S") | |
105 | } | |
106 | case Message_TOOL_QUERY, | |
107 | Message_TOOL_RESPONSE: | |
108 | { | |
109 | s.WriteString("T") | |
110 | } | |
111 | } | |
112 | ||
113 | if isQuery { | |
114 | s.WriteString("Q ") | |
115 | } else { | |
116 | s.WriteString("R ") | |
117 | } | |
118 | ||
119 | switch *m.Type { | |
120 | case Message_CLIENT_QUERY, | |
121 | Message_CLIENT_RESPONSE, | |
122 | Message_AUTH_QUERY, | |
123 | Message_AUTH_RESPONSE: | |
124 | printQueryAddress = true | |
125 | } | |
126 | ||
127 | if printQueryAddress { | |
128 | textConvertIP(s, m.QueryAddress) | |
129 | } else { | |
130 | textConvertIP(s, m.ResponseAddress) | |
131 | } | |
132 | s.WriteString(" ") | |
133 | ||
134 | if m.SocketProtocol != nil { | |
135 | s.WriteString(m.SocketProtocol.String()) | |
136 | } | |
137 | s.WriteString(" ") | |
138 | ||
139 | var err error | |
140 | msg := new(dns.Msg) | |
141 | if isQuery { | |
142 | s.WriteString(strconv.Itoa(len(m.QueryMessage))) | |
143 | s.WriteString("b ") | |
144 | err = msg.Unpack(m.QueryMessage) | |
145 | } else { | |
146 | s.WriteString(strconv.Itoa(len(m.ResponseMessage))) | |
147 | s.WriteString("b ") | |
148 | err = msg.Unpack(m.ResponseMessage) | |
149 | } | |
150 | ||
151 | if err != nil || len(msg.Question) == 0 { | |
152 | s.WriteString("X ") | |
153 | } else { | |
154 | s.WriteString("\"" + msg.Question[0].Name + "\" ") | |
155 | s.WriteString(dns.Class(msg.Question[0].Qclass).String() + " ") | |
156 | s.WriteString(dns.Type(msg.Question[0].Qtype).String()) | |
157 | } | |
158 | ||
159 | s.WriteString("\n") | |
160 | } | |
161 | ||
162 | // TextFormat renders a dnstap message in a compact human-readable text | |
163 | // form. | |
164 | func TextFormat(dt *Dnstap) (out []byte, ok bool) { | |
165 | var s bytes.Buffer | |
166 | ||
167 | if *dt.Type == Dnstap_MESSAGE { | |
168 | textConvertMessage(dt.Message, &s) | |
169 | return s.Bytes(), true | |
170 | } | |
171 | ||
172 | return nil, false | |
173 | } |
0 | dnstap: flexible, structured event replication format for DNS servers | |
1 | --------------------------------------------------------------------- | |
2 | ||
3 | dnstap implements an encoding format for DNS server events. It uses a | |
4 | lightweight framing on top of event payloads encoded using Protocol Buffers and | |
5 | is transport neutral. | |
6 | ||
7 | dnstap can represent internal state inside a DNS server that is difficult to | |
8 | obtain using techniques based on traditional packet capture or unstructured | |
9 | textual format logging. | |
10 | ||
11 | This repository contains a command-line tool named "dnstap" developed in the | |
12 | Go programming language. It can be installed with the following command: | |
13 | ||
14 | go get -u github.com/dnstap/golang-dnstap/dnstap |
0 | /* | |
1 | * Copyright (c) 2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "bufio" | |
20 | "io" | |
21 | "log" | |
22 | "os" | |
23 | ||
24 | "github.com/golang/protobuf/proto" | |
25 | ) | |
26 | ||
27 | // A TextFormatFunc renders a dnstap message into a human readable format. | |
28 | type TextFormatFunc func(*Dnstap) ([]byte, bool) | |
29 | ||
30 | // TextOutput implements a dnstap Output rendering dnstap data as text. | |
31 | type TextOutput struct { | |
32 | format TextFormatFunc | |
33 | outputChannel chan []byte | |
34 | wait chan bool | |
35 | writer *bufio.Writer | |
36 | } | |
37 | ||
38 | // NewTextOutput creates a TextOutput writing dnstap data to the given io.Writer | |
39 | // in the text format given by the TextFormatFunc format. | |
40 | func NewTextOutput(writer io.Writer, format TextFormatFunc) (o *TextOutput) { | |
41 | o = new(TextOutput) | |
42 | o.format = format | |
43 | o.outputChannel = make(chan []byte, outputChannelSize) | |
44 | o.writer = bufio.NewWriter(writer) | |
45 | o.wait = make(chan bool) | |
46 | return | |
47 | } | |
48 | ||
49 | // NewTextOutputFromFilename creates a TextOutput writing dnstap data to a | |
50 | // file with the given filename in the format given by format. If doAppend | |
51 | // is false, the file is truncated if it already exists, otherwise the file | |
52 | // is opened for appending. | |
53 | func NewTextOutputFromFilename(fname string, format TextFormatFunc, doAppend bool) (o *TextOutput, err error) { | |
54 | if fname == "" || fname == "-" { | |
55 | return NewTextOutput(os.Stdout, format), nil | |
56 | } | |
57 | var writer io.Writer | |
58 | if doAppend { | |
59 | writer, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) | |
60 | } else { | |
61 | writer, err = os.Create(fname) | |
62 | } | |
63 | if err != nil { | |
64 | return | |
65 | } | |
66 | return NewTextOutput(writer, format), nil | |
67 | } | |
68 | ||
69 | // GetOutputChannel returns the channel on which the TextOutput accepts dnstap data. | |
70 | // | |
71 | // GetOutputChannel satisfies the dnstap Output interface. | |
72 | func (o *TextOutput) GetOutputChannel() chan []byte { | |
73 | return o.outputChannel | |
74 | } | |
75 | ||
76 | // RunOutputLoop receives dnstap data sent on the output channel, formats it | |
77 | // with the configured TextFormatFunc, and writes it to the file or io.Writer | |
78 | // of the TextOutput. | |
79 | // | |
80 | // RunOutputLoop satisfies the dnstap Output interface. | |
81 | func (o *TextOutput) RunOutputLoop() { | |
82 | dt := &Dnstap{} | |
83 | for frame := range o.outputChannel { | |
84 | if err := proto.Unmarshal(frame, dt); err != nil { | |
85 | log.Fatalf("dnstap.TextOutput: proto.Unmarshal() failed: %s\n", err) | |
86 | break | |
87 | } | |
88 | buf, ok := o.format(dt) | |
89 | if !ok { | |
90 | log.Fatalf("dnstap.TextOutput: text format function failed\n") | |
91 | break | |
92 | } | |
93 | if _, err := o.writer.Write(buf); err != nil { | |
94 | log.Fatalf("dnstap.TextOutput: write failed: %s\n", err) | |
95 | break | |
96 | } | |
97 | o.writer.Flush() | |
98 | } | |
99 | close(o.wait) | |
100 | } | |
101 | ||
102 | // Close closes the output channel and returns when all pending data has been | |
103 | // written. | |
104 | // | |
105 | // Close satisfies the dnstap Output interface. | |
106 | func (o *TextOutput) Close() { | |
107 | close(o.outputChannel) | |
108 | <-o.wait | |
109 | o.writer.Flush() | |
110 | } |
0 | /* | |
1 | * Copyright (c) 2013-2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | import ( | |
19 | "bytes" | |
20 | "fmt" | |
21 | "net" | |
22 | "strconv" | |
23 | "strings" | |
24 | "time" | |
25 | ||
26 | "github.com/miekg/dns" | |
27 | ) | |
28 | ||
29 | const yamlTimeFormat = "2006-01-02 15:04:05.999999999" | |
30 | ||
31 | func yamlConvertMessage(m *Message, s *bytes.Buffer) { | |
32 | s.WriteString(fmt.Sprint(" type: ", m.Type, "\n")) | |
33 | ||
34 | if m.QueryTimeSec != nil && m.QueryTimeNsec != nil { | |
35 | t := time.Unix(int64(*m.QueryTimeSec), int64(*m.QueryTimeNsec)).UTC() | |
36 | s.WriteString(fmt.Sprint(" query_time: !!timestamp ", t.Format(yamlTimeFormat), "\n")) | |
37 | } | |
38 | ||
39 | if m.ResponseTimeSec != nil && m.ResponseTimeNsec != nil { | |
40 | t := time.Unix(int64(*m.ResponseTimeSec), int64(*m.ResponseTimeNsec)).UTC() | |
41 | s.WriteString(fmt.Sprint(" response_time: !!timestamp ", t.Format(yamlTimeFormat), "\n")) | |
42 | } | |
43 | ||
44 | if m.SocketFamily != nil { | |
45 | s.WriteString(fmt.Sprint(" socket_family: ", m.SocketFamily, "\n")) | |
46 | } | |
47 | ||
48 | if m.SocketProtocol != nil { | |
49 | s.WriteString(fmt.Sprint(" socket_protocol: ", m.SocketProtocol, "\n")) | |
50 | } | |
51 | ||
52 | if m.QueryAddress != nil { | |
53 | s.WriteString(fmt.Sprint(" query_address: ", net.IP(m.QueryAddress), "\n")) | |
54 | } | |
55 | ||
56 | if m.ResponseAddress != nil { | |
57 | s.WriteString(fmt.Sprint(" response_address: ", net.IP(m.ResponseAddress), "\n")) | |
58 | } | |
59 | ||
60 | if m.QueryPort != nil { | |
61 | s.WriteString(fmt.Sprint(" query_port: ", *m.QueryPort, "\n")) | |
62 | } | |
63 | ||
64 | if m.ResponsePort != nil { | |
65 | s.WriteString(fmt.Sprint(" response_port: ", *m.ResponsePort, "\n")) | |
66 | } | |
67 | ||
68 | if m.QueryZone != nil { | |
69 | name, _, err := dns.UnpackDomainName(m.QueryZone, 0) | |
70 | if err != nil { | |
71 | fmt.Fprintf(s, " # query_zone: parse failed: %v\n", err) | |
72 | } else { | |
73 | s.WriteString(fmt.Sprint(" query_zone: ", strconv.Quote(name), "\n")) | |
74 | } | |
75 | } | |
76 | ||
77 | if m.QueryMessage != nil { | |
78 | msg := new(dns.Msg) | |
79 | err := msg.Unpack(m.QueryMessage) | |
80 | if err != nil { | |
81 | fmt.Fprintf(s, " # query_message: parse failed: %v\n", err) | |
82 | } else { | |
83 | s.WriteString(" query_message: |\n") | |
84 | s.WriteString(" " + strings.Replace(strings.TrimSpace(msg.String()), "\n", "\n ", -1) + "\n") | |
85 | } | |
86 | } | |
87 | if m.ResponseMessage != nil { | |
88 | msg := new(dns.Msg) | |
89 | err := msg.Unpack(m.ResponseMessage) | |
90 | if err != nil { | |
91 | fmt.Fprintf(s, " # response_message: parse failed: %v\n", err) | |
92 | } else { | |
93 | s.WriteString(" response_message: |\n") | |
94 | s.WriteString(" " + strings.Replace(strings.TrimSpace(msg.String()), "\n", "\n ", -1) + "\n") | |
95 | } | |
96 | } | |
97 | s.WriteString("---\n") | |
98 | } | |
99 | ||
100 | // YamlFormat renders a dnstap message in YAML format. Any encapsulated DNS | |
101 | // messages are rendered as strings in a format similar to 'dig' output. | |
102 | func YamlFormat(dt *Dnstap) (out []byte, ok bool) { | |
103 | var s bytes.Buffer | |
104 | ||
105 | s.WriteString(fmt.Sprint("type: ", dt.Type, "\n")) | |
106 | if dt.Identity != nil { | |
107 | s.WriteString(fmt.Sprint("identity: ", strconv.Quote(string(dt.Identity)), "\n")) | |
108 | } | |
109 | if dt.Version != nil { | |
110 | s.WriteString(fmt.Sprint("version: ", strconv.Quote(string(dt.Version)), "\n")) | |
111 | } | |
112 | if *dt.Type == Dnstap_MESSAGE { | |
113 | s.WriteString("message:\n") | |
114 | yamlConvertMessage(dt.Message, &s) | |
115 | } | |
116 | return s.Bytes(), true | |
117 | } |
0 | .TH dnstap 8 | |
1 | ||
2 | .SH NAME | |
3 | ||
4 | dnstap \- Capture, display, and relay Dnstap data. | |
5 | ||
6 | .SH SYNOPSIS | |
7 | ||
8 | .B dnstap [ -u \fIsocket-path\fB [ -u \fIsocket2-path\fB ... ] ] | |
9 | .br | |
10 | .B " [ -l \fIhost:port\fB [ -l \fIhost2:port2\fB ... ] ]" | |
11 | .br | |
12 | .B " [ -r \fIfile\fB [ -r \fIfile2\fB ... ] ]" | |
13 | .br | |
14 | .B " [ -U \fIsocket-path\fB [ -U \fIsocket2-path\fB ... ] ]" | |
15 | .br | |
16 | .B " [ -T \fIhost:port\fB [ -T \fIhost2:port2\fB ... ] ]" | |
17 | .br | |
18 | .B " [ -w \fIfile\fB ] [ -q | -y | -j ] [-a]" | |
19 | .br | |
20 | .B " [ -t \fItimeout\fB ]" | |
21 | .br | |
22 | ||
23 | .SH DESCRIPTION | |
24 | ||
25 | .B dnstap | |
26 | reads data in the Dnstap export format from Frame Streams files or | |
27 | receives data on Frame Streams connections to TCP/IP or unix domain | |
28 | socket addresses. | |
29 | .B dnstap | |
30 | can display this data in a compact text (the default), JSON, or YAML | |
31 | formats. It can also save data to a file in display or Frame Streams | |
32 | binary format, or relay the data to other Dnstap processes over unix | |
33 | domain socket or TCP/IP connections. | |
34 | ||
35 | .SH OPTIONS | |
36 | ||
37 | .TP | |
38 | .B -a | |
39 | When opening an file (\fB-w\fR) for text format output | |
40 | (\fB-j\fR, \fB-q\fR, or \fB-y\fR), append to the file rather | |
41 | truncating. | |
42 | ||
43 | .B -a | |
44 | does not apply when writing binary Frame Streams data to a file. | |
45 | ||
46 | .TP | |
47 | .B -j | |
48 | Write data in JSON format. Encapsulated DNS messages are | |
49 | rendered in text form similar to the output of \fBdig(1)\fR. | |
50 | ||
51 | At most one text format (\fB-j\fR, \fB-q\fR, or \fB-y\fR) option may be | |
52 | given. | |
53 | ||
54 | .TP | |
55 | .B -l \fIhost:port\fR | |
56 | Listen for Dnstap data on TCP/IP port \fBport\fR on address \fIhost\fR. | |
57 | ||
58 | The \fB-l\fR option may be given multiple times to listen on multiple | |
59 | addresses. | |
60 | ||
61 | At least one input (\fB-l\fR, \fB-r\fR, or \fB-u\fR) option must be given. | |
62 | ||
63 | .TP | |
64 | .B -q | |
65 | Write or display data in compact (quiet) text format. | |
66 | ||
67 | At most one text format (\fB-j\fR, \fB-q\fR, or \fB-y\fR) option may be given. | |
68 | ||
69 | .TP | |
70 | .B -r \fIfile\fR | |
71 | Read Dnstap data from the given \fIfile\fR. The \fB-r\fR option | |
72 | may be given multiple times to read from multiple files. | |
73 | ||
74 | At least one input (\fB-l\fR, \fB-r\fR, or \fB-u\fR) option must be given. | |
75 | ||
76 | .TP | |
77 | .B -T \fIhost:port\fR | |
78 | Relay Dnstap data over a TCP/IP connection to \fIhost:port\fR. | |
79 | \fBdnstap\fR will establish or re-establish this connection as needed. | |
80 | ||
81 | The \fB-T\fR option may be given multiple times to relay Dnstap data | |
82 | to multiple addresses. | |
83 | ||
84 | .TP | |
85 | .B -t \fItimeout\fR | |
86 | Apply i/o \fItimeout\fR to TCP/IP and unix domain socket | |
87 | connections. \fItimeout\fR is given as a number followed by a unit | |
88 | abbreviation (e.g., \fIms\fR for milliseconds, \fIs\fR for seconds, | |
89 | \fIm\fR for minutes). | |
90 | ||
91 | .TP | |
92 | .B -u \fIsocket-path\fR | |
93 | Listen for Dnstap data on the unix domain socket at | |
94 | \fIsocket-path\fR. \fBdnstap\fR will remove any file or socket | |
95 | \fIsocket-path\fR before listening. | |
96 | ||
97 | The \fB-u\fR option may be given multiple times to listen on multiple | |
98 | socket paths. | |
99 | ||
100 | At least one input (\fB-l\fR, \fB-r\fR, or \fB-u\fR) option must be given. | |
101 | ||
102 | .TP | |
103 | .B -U \fIsocket-path\fR | |
104 | Relay Dnstap data over a unix domain socket connection to | |
105 | \fIsocket-path\fR. \fBdnstap\fR will establish or re-establish this | |
106 | connection as needed. | |
107 | ||
108 | The \fB-U\fR option may be given multiple times to relay Dnstap data to | |
109 | multiple socket paths. | |
110 | ||
111 | ||
112 | .TP | |
113 | .B -w \fIfile\fR | |
114 | Write Dnstap data to \fIfile\fR. | |
115 | ||
116 | If \fIfile\fR is "-" or no \fB-w\fR, \fB-T\fR, or \fB-U\fR output | |
117 | options are present, data will be written to standard output in quiet | |
118 | text format (\fB-q\fR), unless the YAML or JSON format is specified | |
119 | with the \fB-y\fR or \fB-j\fR options, respectively. | |
120 | ||
121 | If \fIfile\fR is a filename other than "-", Dnstap data is written to the | |
122 | named file in Frame Streams binary format by default, unless quiet text, | |
123 | JSON, or YAML formats are specified. | |
124 | ||
125 | .B dnstap | |
126 | will reopen \fIfile\fR on \fBSIGHUP\fR, for file rotation purposes. | |
127 | ||
128 | ||
129 | .TP | |
130 | .B -y | |
131 | Write Dnstap output in YAML format. Encapsulated DNS messages are rendered in text | |
132 | form similar to the output of \fBdig(1)\fR. | |
133 | ||
134 | At most one text format (\fB-j\fR, \fB-q\fR, or \fB-y\fR) option may be given. | |
135 | ||
136 | ||
137 | .SH EXAMPLES | |
138 | ||
139 | Listen for Dnstap data from a local name server and print quiet text format | |
140 | to standard output. | |
141 | ||
142 | .nf | |
143 | dnstap -u /var/named/dnstap.sock | |
144 | .fi | |
145 | ||
146 | Listen for Dnstap data from a local name server, save a local binary copy, and | |
147 | relay it to a remote host over TCP. | |
148 | ||
149 | .nf | |
150 | dnstap -u /usr/local/unbound/dnstap.sock -w dnstap.fstrm \\ | |
151 | -T dns-admin.example.com:5353 | |
152 | .fi | |
153 | ||
154 | .SH SEE ALSO | |
155 | ||
156 | .B dig(1) |
0 | /* | |
1 | * Copyright (c) 2019 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package main | |
17 | ||
18 | import ( | |
19 | "errors" | |
20 | "fmt" | |
21 | "os" | |
22 | "os/signal" | |
23 | "syscall" | |
24 | ||
25 | dnstap "github.com/dnstap/golang-dnstap" | |
26 | ) | |
27 | ||
28 | // Output channel buffer size value from main dnstap package. | |
29 | const outputChannelSize = 32 | |
30 | ||
31 | // | |
32 | // A fileOutput implements a dnstap.Output which writes frames to a file | |
33 | // and closes and reopens the file on SIGHUP. | |
34 | // | |
35 | // Data frames are written in binary fstrm format unless a text formatting | |
36 | // function (dnstp.TextFormatFunc) is given or the filename is blank or "-". | |
37 | // In the latter case, data is written in compact (quiet) text format unless | |
38 | // an alternate text format is given on the assumption that stdout is a terminal. | |
39 | // | |
40 | type fileOutput struct { | |
41 | formatter dnstap.TextFormatFunc | |
42 | filename string | |
43 | doAppend bool | |
44 | output dnstap.Output | |
45 | data chan []byte | |
46 | done chan struct{} | |
47 | } | |
48 | ||
49 | func openOutputFile(filename string, formatter dnstap.TextFormatFunc, doAppend bool) (o dnstap.Output, err error) { | |
50 | if formatter == nil { | |
51 | if filename == "-" || filename == "" { | |
52 | o = dnstap.NewTextOutput(os.Stdout, dnstap.TextFormat) | |
53 | return | |
54 | } | |
55 | o, err = dnstap.NewFrameStreamOutputFromFilename(filename) | |
56 | } else { | |
57 | if filename == "-" || filename == "" { | |
58 | if doAppend { | |
59 | return nil, errors.New("cannot append to stdout (-)") | |
60 | } | |
61 | o = dnstap.NewTextOutput(os.Stdout, formatter) | |
62 | return | |
63 | } | |
64 | o, err = dnstap.NewTextOutputFromFilename(filename, formatter, doAppend) | |
65 | } | |
66 | return | |
67 | } | |
68 | ||
69 | func newFileOutput(filename string, formatter dnstap.TextFormatFunc, doAppend bool) (*fileOutput, error) { | |
70 | o, err := openOutputFile(filename, formatter, doAppend) | |
71 | if err != nil { | |
72 | return nil, err | |
73 | } | |
74 | return &fileOutput{ | |
75 | formatter: formatter, | |
76 | filename: filename, | |
77 | doAppend: doAppend, | |
78 | output: o, | |
79 | data: make(chan []byte, outputChannelSize), | |
80 | done: make(chan struct{}), | |
81 | }, nil | |
82 | } | |
83 | ||
84 | func (fo *fileOutput) GetOutputChannel() chan []byte { | |
85 | return fo.data | |
86 | } | |
87 | ||
88 | func (fo *fileOutput) Close() { | |
89 | close(fo.data) | |
90 | <-fo.done | |
91 | } | |
92 | ||
93 | func (fo *fileOutput) RunOutputLoop() { | |
94 | sigch := make(chan os.Signal, 1) | |
95 | signal.Notify(sigch, os.Interrupt, syscall.SIGHUP) | |
96 | o := fo.output | |
97 | go o.RunOutputLoop() | |
98 | defer func() { | |
99 | o.Close() | |
100 | close(fo.done) | |
101 | }() | |
102 | for { | |
103 | select { | |
104 | case b, ok := <-fo.data: | |
105 | if !ok { | |
106 | return | |
107 | } | |
108 | o.GetOutputChannel() <- b | |
109 | case sig := <-sigch: | |
110 | if sig == syscall.SIGHUP { | |
111 | o.Close() | |
112 | newo, err := openOutputFile(fo.filename, fo.formatter, fo.doAppend) | |
113 | if err != nil { | |
114 | fmt.Fprintf(os.Stderr, | |
115 | "dnstap: Error: failed to reopen %s: %v\n", | |
116 | fo.filename, err) | |
117 | os.Exit(1) | |
118 | } | |
119 | o = newo | |
120 | go o.RunOutputLoop() | |
121 | continue | |
122 | } | |
123 | os.Exit(0) | |
124 | } | |
125 | } | |
126 | } |
0 | /* | |
1 | * Copyright (c) 2013-2019 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package main | |
17 | ||
18 | import ( | |
19 | "flag" | |
20 | "fmt" | |
21 | "log" | |
22 | "net" | |
23 | "os" | |
24 | "runtime" | |
25 | "strings" | |
26 | "sync" | |
27 | ||
28 | "github.com/dnstap/golang-dnstap" | |
29 | ) | |
30 | ||
31 | type stringList []string | |
32 | ||
33 | func (sl *stringList) Set(s string) error { | |
34 | *sl = append(*sl, s) | |
35 | return nil | |
36 | } | |
37 | func (sl *stringList) String() string { | |
38 | return strings.Join(*sl, ", ") | |
39 | } | |
40 | ||
41 | var ( | |
42 | flagTimeout = flag.Duration("t", 0, "I/O timeout for tcp/ip and unix domain sockets") | |
43 | flagWriteFile = flag.String("w", "", "write output to file") | |
44 | flagAppendFile = flag.Bool("a", false, "append to the given file, do not overwrite. valid only when outputting a text or YAML file.") | |
45 | flagQuietText = flag.Bool("q", false, "use quiet text output") | |
46 | flagYamlText = flag.Bool("y", false, "use verbose YAML output") | |
47 | flagJSONText = flag.Bool("j", false, "use verbose JSON output") | |
48 | ) | |
49 | ||
50 | func usage() { | |
51 | fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]...\n", os.Args[0]) | |
52 | flag.PrintDefaults() | |
53 | fmt.Fprintf(os.Stderr, ` | |
54 | Quiet text output format mnemonics: | |
55 | AQ: AUTH_QUERY | |
56 | AR: AUTH_RESPONSE | |
57 | RQ: RESOLVER_QUERY | |
58 | RR: RESOLVER_RESPONSE | |
59 | CQ: CLIENT_QUERY | |
60 | CR: CLIENT_RESPONSE | |
61 | FQ: FORWARDER_QUERY | |
62 | FR: FORWARDER_RESPONSE | |
63 | SQ: STUB_QUERY | |
64 | SR: STUB_RESPONSE | |
65 | TQ: TOOL_QUERY | |
66 | TR: TOOL_RESPONSE | |
67 | `) | |
68 | } | |
69 | ||
70 | func main() { | |
71 | var tcpOutputs, unixOutputs stringList | |
72 | var fileInputs, tcpInputs, unixInputs stringList | |
73 | ||
74 | flag.Var(&tcpOutputs, "T", "write dnstap payloads to tcp/ip address") | |
75 | flag.Var(&unixOutputs, "U", "write dnstap payloads to unix socket") | |
76 | flag.Var(&fileInputs, "r", "read dnstap payloads from file") | |
77 | flag.Var(&tcpInputs, "l", "read dnstap payloads from tcp/ip") | |
78 | flag.Var(&unixInputs, "u", "read dnstap payloads from unix socket") | |
79 | ||
80 | runtime.GOMAXPROCS(runtime.NumCPU()) | |
81 | log.SetFlags(0) | |
82 | flag.Usage = usage | |
83 | ||
84 | // Handle command-line arguments. | |
85 | flag.Parse() | |
86 | ||
87 | if len(fileInputs)+len(unixInputs)+len(tcpInputs) == 0 { | |
88 | fmt.Fprintf(os.Stderr, "dnstap: Error: no inputs specified.\n") | |
89 | os.Exit(1) | |
90 | } | |
91 | ||
92 | haveFormat := false | |
93 | for _, f := range []bool{*flagQuietText, *flagYamlText, *flagJSONText} { | |
94 | if haveFormat && f { | |
95 | fmt.Fprintf(os.Stderr, "dnstap: Error: specify at most one of -q, -y, or -j.\n") | |
96 | os.Exit(1) | |
97 | } | |
98 | haveFormat = haveFormat || f | |
99 | } | |
100 | ||
101 | output := newMirrorOutput() | |
102 | if err := addSockOutputs(output, "tcp", tcpOutputs); err != nil { | |
103 | fmt.Fprintf(os.Stderr, "dnstap: TCP error: %v\n", err) | |
104 | os.Exit(1) | |
105 | } | |
106 | if err := addSockOutputs(output, "unix", unixOutputs); err != nil { | |
107 | fmt.Fprintf(os.Stderr, "dnstap: Unix socket error: %v\n", err) | |
108 | os.Exit(1) | |
109 | } | |
110 | if *flagWriteFile != "" || len(tcpOutputs)+len(unixOutputs) == 0 { | |
111 | var format dnstap.TextFormatFunc | |
112 | ||
113 | switch { | |
114 | case *flagYamlText: | |
115 | format = dnstap.YamlFormat | |
116 | case *flagQuietText: | |
117 | format = dnstap.TextFormat | |
118 | case *flagJSONText: | |
119 | format = dnstap.JSONFormat | |
120 | } | |
121 | ||
122 | o, err := newFileOutput(*flagWriteFile, format, *flagAppendFile) | |
123 | if err != nil { | |
124 | fmt.Fprintf(os.Stderr, "dnstap: File output error on '%s': %v\n", | |
125 | *flagWriteFile, err) | |
126 | os.Exit(1) | |
127 | } | |
128 | go o.RunOutputLoop() | |
129 | output.Add(o) | |
130 | } | |
131 | ||
132 | go output.RunOutputLoop() | |
133 | ||
134 | var iwg sync.WaitGroup | |
135 | // Open the input and start the input loop. | |
136 | for _, fname := range fileInputs { | |
137 | i, err := dnstap.NewFrameStreamInputFromFilename(fname) | |
138 | if err != nil { | |
139 | fmt.Fprintf(os.Stderr, "dnstap: Failed to open input file %s: %v\n", fname, err) | |
140 | os.Exit(1) | |
141 | } | |
142 | fmt.Fprintf(os.Stderr, "dnstap: opened input file %s\n", fname) | |
143 | iwg.Add(1) | |
144 | go runInput(i, output, &iwg) | |
145 | } | |
146 | for _, path := range unixInputs { | |
147 | i, err := dnstap.NewFrameStreamSockInputFromPath(path) | |
148 | if err != nil { | |
149 | fmt.Fprintf(os.Stderr, "dnstap: Failed to open input socket %s: %v\n", path, err) | |
150 | os.Exit(1) | |
151 | } | |
152 | i.SetTimeout(*flagTimeout) | |
153 | fmt.Fprintf(os.Stderr, "dnstap: opened input socket %s\n", path) | |
154 | iwg.Add(1) | |
155 | go runInput(i, output, &iwg) | |
156 | } | |
157 | for _, addr := range tcpInputs { | |
158 | l, err := net.Listen("tcp", addr) | |
159 | if err != nil { | |
160 | fmt.Fprintf(os.Stderr, "dnstap: Failed to listen on %s: %v\n", addr, err) | |
161 | os.Exit(1) | |
162 | } | |
163 | i := dnstap.NewFrameStreamSockInput(l) | |
164 | i.SetTimeout(*flagTimeout) | |
165 | iwg.Add(1) | |
166 | go runInput(i, output, &iwg) | |
167 | } | |
168 | iwg.Wait() | |
169 | ||
170 | output.Close() | |
171 | } | |
172 | ||
173 | func runInput(i dnstap.Input, o dnstap.Output, wg *sync.WaitGroup) { | |
174 | go i.ReadInto(o.GetOutputChannel()) | |
175 | i.Wait() | |
176 | wg.Done() | |
177 | } | |
178 | ||
179 | func addSockOutputs(mo *mirrorOutput, network string, addrs stringList) error { | |
180 | var naddr net.Addr | |
181 | var err error | |
182 | for _, addr := range addrs { | |
183 | switch network { | |
184 | case "tcp": | |
185 | naddr, err = net.ResolveTCPAddr(network, addr) | |
186 | case "unix": | |
187 | naddr, err = net.ResolveUnixAddr(network, addr) | |
188 | default: | |
189 | return fmt.Errorf("invalid network '%s'", network) | |
190 | } | |
191 | if err != nil { | |
192 | return err | |
193 | } | |
194 | ||
195 | o, err := dnstap.NewFrameStreamSockOutput(naddr) | |
196 | if err != nil { | |
197 | return err | |
198 | } | |
199 | o.SetTimeout(*flagTimeout) | |
200 | go o.RunOutputLoop() | |
201 | mo.Add(o) | |
202 | } | |
203 | return nil | |
204 | } |
0 | /* | |
1 | * Copyright (c) 2019 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package main | |
17 | ||
18 | ||
19 | import ( | |
20 | dnstap "github.com/dnstap/golang-dnstap" | |
21 | ) | |
22 | ||
23 | type mirrorOutput struct { | |
24 | outputs []dnstap.Output | |
25 | data chan []byte | |
26 | done chan struct{} | |
27 | } | |
28 | ||
29 | func newMirrorOutput() *mirrorOutput { | |
30 | return &mirrorOutput{ | |
31 | data: make(chan []byte, outputChannelSize), | |
32 | done: make(chan struct{}), | |
33 | } | |
34 | } | |
35 | ||
36 | func (mo *mirrorOutput) Add(o dnstap.Output) { | |
37 | mo.outputs = append(mo.outputs, o) | |
38 | } | |
39 | ||
40 | func (mo *mirrorOutput) RunOutputLoop() { | |
41 | for b := range mo.data { | |
42 | for _, o := range mo.outputs { | |
43 | select { | |
44 | case o.GetOutputChannel() <- b: | |
45 | default: | |
46 | } | |
47 | } | |
48 | } | |
49 | for _, o := range mo.outputs { | |
50 | o.Close() | |
51 | } | |
52 | close(mo.done) | |
53 | } | |
54 | ||
55 | func (mo *mirrorOutput) Close() { | |
56 | close(mo.data) | |
57 | <-mo.done | |
58 | } | |
59 | ||
60 | func (mo *mirrorOutput) GetOutputChannel() chan []byte { | |
61 | return mo.data | |
62 | } |
0 | /* | |
1 | * Copyright (c) 2014 by Farsight Security, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package dnstap | |
17 | ||
18 | const outputChannelSize = 32 | |
19 | ||
20 | // FSContentType is the FrameStream content type for dnstap protobuf data. | |
21 | var FSContentType = []byte("protobuf:dnstap.Dnstap") | |
22 | ||
23 | // An Input is a source of dnstap data. It provides validation of the | |
24 | // content type and will present any data read or received on the channel | |
25 | // provided to the ReadInto method. | |
26 | type Input interface { | |
27 | ReadInto(chan []byte) | |
28 | Wait() | |
29 | } | |
30 | ||
31 | // An Output is a desintaion for dnstap data. It accepts data on the channel | |
32 | // returned from the GetOutputChannel method. The RunOutputLoop() method | |
33 | // processes data received on this channel, and returns after the Close() | |
34 | // method is called. | |
35 | type Output interface { | |
36 | GetOutputChannel() chan []byte | |
37 | RunOutputLoop() | |
38 | Close() | |
39 | } |
0 | .deps/ | |
1 | .dirstamp | |
2 | .libs/ | |
3 | *.pb-c.c | |
4 | *.pb-c.h | |
5 | *.pb.cc | |
6 | *.pb.h | |
7 | *.pb.go | |
8 | *_pb2.py | |
9 | *_pb2.pyc |
0 | Creative Commons Legal Code | |
1 | ||
2 | CC0 1.0 Universal | |
3 | ||
4 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE | |
5 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN | |
6 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS | |
7 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES | |
8 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS | |
9 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM | |
10 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED | |
11 | HEREUNDER. | |
12 | ||
13 | Statement of Purpose | |
14 | ||
15 | The laws of most jurisdictions throughout the world automatically confer | |
16 | exclusive Copyright and Related Rights (defined below) upon the creator | |
17 | and subsequent owner(s) (each and all, an "owner") of an original work of | |
18 | authorship and/or a database (each, a "Work"). | |
19 | ||
20 | Certain owners wish to permanently relinquish those rights to a Work for | |
21 | the purpose of contributing to a commons of creative, cultural and | |
22 | scientific works ("Commons") that the public can reliably and without fear | |
23 | of later claims of infringement build upon, modify, incorporate in other | |
24 | works, reuse and redistribute as freely as possible in any form whatsoever | |
25 | and for any purposes, including without limitation commercial purposes. | |
26 | These owners may contribute to the Commons to promote the ideal of a free | |
27 | culture and the further production of creative, cultural and scientific | |
28 | works, or to gain reputation or greater distribution for their Work in | |
29 | part through the use and efforts of others. | |
30 | ||
31 | For these and/or other purposes and motivations, and without any | |
32 | expectation of additional consideration or compensation, the person | |
33 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she | |
34 | is an owner of Copyright and Related Rights in the Work, voluntarily | |
35 | elects to apply CC0 to the Work and publicly distribute the Work under its | |
36 | terms, with knowledge of his or her Copyright and Related Rights in the | |
37 | Work and the meaning and intended legal effect of CC0 on those rights. | |
38 | ||
39 | 1. Copyright and Related Rights. A Work made available under CC0 may be | |
40 | protected by copyright and related or neighboring rights ("Copyright and | |
41 | Related Rights"). Copyright and Related Rights include, but are not | |
42 | limited to, the following: | |
43 | ||
44 | i. the right to reproduce, adapt, distribute, perform, display, | |
45 | communicate, and translate a Work; | |
46 | ii. moral rights retained by the original author(s) and/or performer(s); | |
47 | iii. publicity and privacy rights pertaining to a person's image or | |
48 | likeness depicted in a Work; | |
49 | iv. rights protecting against unfair competition in regards to a Work, | |
50 | subject to the limitations in paragraph 4(a), below; | |
51 | v. rights protecting the extraction, dissemination, use and reuse of data | |
52 | in a Work; | |
53 | vi. database rights (such as those arising under Directive 96/9/EC of the | |
54 | European Parliament and of the Council of 11 March 1996 on the legal | |
55 | protection of databases, and under any national implementation | |
56 | thereof, including any amended or successor version of such | |
57 | directive); and | |
58 | vii. other similar, equivalent or corresponding rights throughout the | |
59 | world based on applicable law or treaty, and any national | |
60 | implementations thereof. | |
61 | ||
62 | 2. Waiver. To the greatest extent permitted by, but not in contravention | |
63 | of, applicable law, Affirmer hereby overtly, fully, permanently, | |
64 | irrevocably and unconditionally waives, abandons, and surrenders all of | |
65 | Affirmer's Copyright and Related Rights and associated claims and causes | |
66 | of action, whether now known or unknown (including existing as well as | |
67 | future claims and causes of action), in the Work (i) in all territories | |
68 | worldwide, (ii) for the maximum duration provided by applicable law or | |
69 | treaty (including future time extensions), (iii) in any current or future | |
70 | medium and for any number of copies, and (iv) for any purpose whatsoever, | |
71 | including without limitation commercial, advertising or promotional | |
72 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each | |
73 | member of the public at large and to the detriment of Affirmer's heirs and | |
74 | successors, fully intending that such Waiver shall not be subject to | |
75 | revocation, rescission, cancellation, termination, or any other legal or | |
76 | equitable action to disrupt the quiet enjoyment of the Work by the public | |
77 | as contemplated by Affirmer's express Statement of Purpose. | |
78 | ||
79 | 3. Public License Fallback. Should any part of the Waiver for any reason | |
80 | be judged legally invalid or ineffective under applicable law, then the | |
81 | Waiver shall be preserved to the maximum extent permitted taking into | |
82 | account Affirmer's express Statement of Purpose. In addition, to the | |
83 | extent the Waiver is so judged Affirmer hereby grants to each affected | |
84 | person a royalty-free, non transferable, non sublicensable, non exclusive, | |
85 | irrevocable and unconditional license to exercise Affirmer's Copyright and | |
86 | Related Rights in the Work (i) in all territories worldwide, (ii) for the | |
87 | maximum duration provided by applicable law or treaty (including future | |
88 | time extensions), (iii) in any current or future medium and for any number | |
89 | of copies, and (iv) for any purpose whatsoever, including without | |
90 | limitation commercial, advertising or promotional purposes (the | |
91 | "License"). The License shall be deemed effective as of the date CC0 was | |
92 | applied by Affirmer to the Work. Should any part of the License for any | |
93 | reason be judged legally invalid or ineffective under applicable law, such | |
94 | partial invalidity or ineffectiveness shall not invalidate the remainder | |
95 | of the License, and in such case Affirmer hereby affirms that he or she | |
96 | will not (i) exercise any of his or her remaining Copyright and Related | |
97 | Rights in the Work or (ii) assert any associated claims and causes of | |
98 | action with respect to the Work, in either case contrary to Affirmer's | |
99 | express Statement of Purpose. | |
100 | ||
101 | 4. Limitations and Disclaimers. | |
102 | ||
103 | a. No trademark or patent rights held by Affirmer are waived, abandoned, | |
104 | surrendered, licensed or otherwise affected by this document. | |
105 | b. Affirmer offers the Work as-is and makes no representations or | |
106 | warranties of any kind concerning the Work, express, implied, | |
107 | statutory or otherwise, including without limitation warranties of | |
108 | title, merchantability, fitness for a particular purpose, non | |
109 | infringement, or the absence of latent or other defects, accuracy, or | |
110 | the present or absence of errors, whether or not discoverable, all to | |
111 | the greatest extent permissible under applicable law. | |
112 | c. Affirmer disclaims responsibility for clearing rights of other persons | |
113 | that may apply to the Work or any use thereof, including without | |
114 | limitation any person's Copyright and Related Rights in the Work. | |
115 | Further, Affirmer disclaims responsibility for obtaining any necessary | |
116 | consents, permissions or other rights required for any use of the | |
117 | Work. | |
118 | d. Affirmer understands and acknowledges that Creative Commons is not a | |
119 | party to this document and has no duty or obligation with respect to | |
120 | this CC0 or use of the Work. |
0 | dnstap: flexible, structured event replication format for DNS software | |
1 | ---------------------------------------------------------------------- | |
2 | ||
3 | This directory contains only the protobuf schemas for dnstap, and is the root of | |
4 | a repository named "dnstap.pb". |
0 | // dnstap: flexible, structured event replication format for DNS software | |
1 | // | |
2 | // This file contains the protobuf schemas for the "dnstap" structured event | |
3 | // replication format for DNS software. | |
4 | ||
5 | // Written in 2013-2014 by Farsight Security, Inc. | |
6 | // | |
7 | // To the extent possible under law, the author(s) have dedicated all | |
8 | // copyright and related and neighboring rights to this file to the public | |
9 | // domain worldwide. This file is distributed without any warranty. | |
10 | // | |
11 | // You should have received a copy of the CC0 Public Domain Dedication along | |
12 | // with this file. If not, see: | |
13 | // | |
14 | // <http://creativecommons.org/publicdomain/zero/1.0/>. | |
15 | ||
16 | package dnstap; | |
17 | ||
18 | // "Dnstap": this is the top-level dnstap type, which is a "union" type that | |
19 | // contains other kinds of dnstap payloads, although currently only one type | |
20 | // of dnstap payload is defined. | |
21 | // See: https://developers.google.com/protocol-buffers/docs/techniques#union | |
22 | message Dnstap { | |
23 | // DNS server identity. | |
24 | // If enabled, this is the identity string of the DNS server which generated | |
25 | // this message. Typically this would be the same string as returned by an | |
26 | // "NSID" (RFC 5001) query. | |
27 | optional bytes identity = 1; | |
28 | ||
29 | // DNS server version. | |
30 | // If enabled, this is the version string of the DNS server which generated | |
31 | // this message. Typically this would be the same string as returned by a | |
32 | // "version.bind" query. | |
33 | optional bytes version = 2; | |
34 | ||
35 | // Extra data for this payload. | |
36 | // This field can be used for adding an arbitrary byte-string annotation to | |
37 | // the payload. No encoding or interpretation is applied or enforced. | |
38 | optional bytes extra = 3; | |
39 | ||
40 | // Identifies which field below is filled in. | |
41 | enum Type { | |
42 | MESSAGE = 1; | |
43 | } | |
44 | required Type type = 15; | |
45 | ||
46 | // One of the following will be filled in. | |
47 | optional Message message = 14; | |
48 | } | |
49 | ||
50 | // SocketFamily: the network protocol family of a socket. This specifies how | |
51 | // to interpret "network address" fields. | |
52 | enum SocketFamily { | |
53 | INET = 1; // IPv4 (RFC 791) | |
54 | INET6 = 2; // IPv6 (RFC 2460) | |
55 | } | |
56 | ||
57 | // SocketProtocol: the transport protocol of a socket. This specifies how to | |
58 | // interpret "transport port" fields. | |
59 | enum SocketProtocol { | |
60 | UDP = 1; // User Datagram Protocol (RFC 768) | |
61 | TCP = 2; // Transmission Control Protocol (RFC 793) | |
62 | } | |
63 | ||
64 | // Message: a wire-format (RFC 1035 section 4) DNS message and associated | |
65 | // metadata. Applications generating "Message" payloads should follow | |
66 | // certain requirements based on the MessageType, see below. | |
67 | message Message { | |
68 | ||
69 | // There are eight types of "Message" defined that correspond to the | |
70 | // four arrows in the following diagram, slightly modified from RFC 1035 | |
71 | // section 2: | |
72 | ||
73 | // +---------+ +----------+ +--------+ | |
74 | // | | query | | query | | | |
75 | // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. | | |
76 | // | Resolver| | Server | | Name | | |
77 | // | |<-SR--------CR-| |<-RR----AR-| Server | | |
78 | // +---------+ response | | response | | | |
79 | // +----------+ +--------+ | |
80 | ||
81 | // Each arrow has two Type values each, one for each "end" of each arrow, | |
82 | // because these are considered to be distinct events. Each end of each | |
83 | // arrow on the diagram above has been marked with a two-letter Type | |
84 | // mnemonic. Clockwise from upper left, these mnemonic values are: | |
85 | // | |
86 | // SQ: STUB_QUERY | |
87 | // CQ: CLIENT_QUERY | |
88 | // RQ: RESOLVER_QUERY | |
89 | // AQ: AUTH_QUERY | |
90 | // AR: AUTH_RESPONSE | |
91 | // RR: RESOLVER_RESPONSE | |
92 | // CR: CLIENT_RESPONSE | |
93 | // SR: STUB_RESPONSE | |
94 | ||
95 | // Two additional types of "Message" have been defined for the | |
96 | // "forwarding" case where an upstream DNS server is responsible for | |
97 | // further recursion. These are not shown on the diagram above, but have | |
98 | // the following mnemonic values: | |
99 | ||
100 | // FQ: FORWARDER_QUERY | |
101 | // FR: FORWARDER_RESPONSE | |
102 | ||
103 | // The "Message" Type values are defined below. | |
104 | ||
105 | enum Type { | |
106 | // AUTH_QUERY is a DNS query message received from a resolver by an | |
107 | // authoritative name server, from the perspective of the authorative | |
108 | // name server. | |
109 | AUTH_QUERY = 1; | |
110 | ||
111 | // AUTH_RESPONSE is a DNS response message sent from an authoritative | |
112 | // name server to a resolver, from the perspective of the authoritative | |
113 | // name server. | |
114 | AUTH_RESPONSE = 2; | |
115 | ||
116 | // RESOLVER_QUERY is a DNS query message sent from a resolver to an | |
117 | // authoritative name server, from the perspective of the resolver. | |
118 | // Resolvers typically clear the RD (recursion desired) bit when | |
119 | // sending queries. | |
120 | RESOLVER_QUERY = 3; | |
121 | ||
122 | // RESOLVER_RESPONSE is a DNS response message received from an | |
123 | // authoritative name server by a resolver, from the perspective of | |
124 | // the resolver. | |
125 | RESOLVER_RESPONSE = 4; | |
126 | ||
127 | // CLIENT_QUERY is a DNS query message sent from a client to a DNS | |
128 | // server which is expected to perform further recursion, from the | |
129 | // perspective of the DNS server. The client may be a stub resolver or | |
130 | // forwarder or some other type of software which typically sets the RD | |
131 | // (recursion desired) bit when querying the DNS server. The DNS server | |
132 | // may be a simple forwarding proxy or it may be a full recursive | |
133 | // resolver. | |
134 | CLIENT_QUERY = 5; | |
135 | ||
136 | // CLIENT_RESPONSE is a DNS response message sent from a DNS server to | |
137 | // a client, from the perspective of the DNS server. The DNS server | |
138 | // typically sets the RA (recursion available) bit when responding. | |
139 | CLIENT_RESPONSE = 6; | |
140 | ||
141 | // FORWARDER_QUERY is a DNS query message sent from a downstream DNS | |
142 | // server to an upstream DNS server which is expected to perform | |
143 | // further recursion, from the perspective of the downstream DNS | |
144 | // server. | |
145 | FORWARDER_QUERY = 7; | |
146 | ||
147 | // FORWARDER_RESPONSE is a DNS response message sent from an upstream | |
148 | // DNS server performing recursion to a downstream DNS server, from the | |
149 | // perspective of the downstream DNS server. | |
150 | FORWARDER_RESPONSE = 8; | |
151 | ||
152 | // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS | |
153 | // server, from the perspective of the stub resolver. | |
154 | STUB_QUERY = 9; | |
155 | ||
156 | // STUB_RESPONSE is a DNS response message sent from a DNS server to a | |
157 | // stub resolver, from the perspective of the stub resolver. | |
158 | STUB_RESPONSE = 10; | |
159 | ||
160 | // TOOL_QUERY is a DNS query message sent from a DNS software tool to a | |
161 | // DNS server, from the perspective of the tool. | |
162 | TOOL_QUERY = 11; | |
163 | ||
164 | // TOOL_RESPONSE is a DNS response message received by a DNS software | |
165 | // tool from a DNS server, from the perspective of the tool. | |
166 | TOOL_RESPONSE = 12; | |
167 | } | |
168 | ||
169 | // One of the Type values described above. | |
170 | required Type type = 1; | |
171 | ||
172 | // One of the SocketFamily values described above. | |
173 | optional SocketFamily socket_family = 2; | |
174 | ||
175 | // One of the SocketProtocol values described above. | |
176 | optional SocketProtocol socket_protocol = 3; | |
177 | ||
178 | // The network address of the message initiator. | |
179 | // For SocketFamily INET, this field is 4 octets (IPv4 address). | |
180 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). | |
181 | optional bytes query_address = 4; | |
182 | ||
183 | // The network address of the message responder. | |
184 | // For SocketFamily INET, this field is 4 octets (IPv4 address). | |
185 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). | |
186 | optional bytes response_address = 5; | |
187 | ||
188 | // The transport port of the message initiator. | |
189 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. | |
190 | optional uint32 query_port = 6; | |
191 | ||
192 | // The transport port of the message responder. | |
193 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. | |
194 | optional uint32 response_port = 7; | |
195 | ||
196 | // The time at which the DNS query message was sent or received, depending | |
197 | // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. | |
198 | // This is the number of seconds since the UNIX epoch. | |
199 | optional uint64 query_time_sec = 8; | |
200 | ||
201 | // The time at which the DNS query message was sent or received. | |
202 | // This is the seconds fraction, expressed as a count of nanoseconds. | |
203 | optional fixed32 query_time_nsec = 9; | |
204 | ||
205 | // The initiator's original wire-format DNS query message, verbatim. | |
206 | optional bytes query_message = 10; | |
207 | ||
208 | // The "zone" or "bailiwick" pertaining to the DNS query message. | |
209 | // This is a wire-format DNS domain name. | |
210 | optional bytes query_zone = 11; | |
211 | ||
212 | // The time at which the DNS response message was sent or received, | |
213 | // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or | |
214 | // CLIENT_RESPONSE. | |
215 | // This is the number of seconds since the UNIX epoch. | |
216 | optional uint64 response_time_sec = 12; | |
217 | ||
218 | // The time at which the DNS response message was sent or received. | |
219 | // This is the seconds fraction, expressed as a count of nanoseconds. | |
220 | optional fixed32 response_time_nsec = 13; | |
221 | ||
222 | // The responder's original wire-format DNS response message, verbatim. | |
223 | optional bytes response_message = 14; | |
224 | } | |
225 | ||
226 | // All fields except for 'type' in the Message schema are optional. | |
227 | // It is recommended that at least the following fields be filled in for | |
228 | // particular types of Messages. | |
229 | ||
230 | // AUTH_QUERY: | |
231 | // socket_family, socket_protocol | |
232 | // query_address, query_port | |
233 | // query_message | |
234 | // query_time_sec, query_time_nsec | |
235 | ||
236 | // AUTH_RESPONSE: | |
237 | // socket_family, socket_protocol | |
238 | // query_address, query_port | |
239 | // query_time_sec, query_time_nsec | |
240 | // response_message | |
241 | // response_time_sec, response_time_nsec | |
242 | ||
243 | // RESOLVER_QUERY: | |
244 | // socket_family, socket_protocol | |
245 | // query_message | |
246 | // query_time_sec, query_time_nsec | |
247 | // query_zone | |
248 | // response_address, response_port | |
249 | ||
250 | // RESOLVER_RESPONSE: | |
251 | // socket_family, socket_protocol | |
252 | // query_time_sec, query_time_nsec | |
253 | // query_zone | |
254 | // response_address, response_port | |
255 | // response_message | |
256 | // response_time_sec, response_time_nsec | |
257 | ||
258 | // CLIENT_QUERY: | |
259 | // socket_family, socket_protocol | |
260 | // query_message | |
261 | // query_time_sec, query_time_nsec | |
262 | ||
263 | // CLIENT_RESPONSE: | |
264 | // socket_family, socket_protocol | |
265 | // query_time_sec, query_time_nsec | |
266 | // response_message | |
267 | // response_time_sec, response_time_nsec |
0 | // Code generated by protoc-gen-go. | |
1 | // source: dnstap.proto | |
2 | // DO NOT EDIT! | |
3 | ||
4 | /* | |
5 | Package dnstap is a generated protocol buffer package. | |
6 | ||
7 | It is generated from these files: | |
8 | dnstap.proto | |
9 | ||
10 | It has these top-level messages: | |
11 | Dnstap | |
12 | Message | |
13 | */ | |
14 | package dnstap | |
15 | ||
16 | import proto "github.com/golang/protobuf/proto" | |
17 | import json "encoding/json" | |
18 | import math "math" | |
19 | ||
20 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. | |
21 | var _ = proto.Marshal | |
22 | var _ = &json.SyntaxError{} | |
23 | var _ = math.Inf | |
24 | ||
25 | // SocketFamily: the network protocol family of a socket. This specifies how | |
26 | // to interpret "network address" fields. | |
27 | type SocketFamily int32 | |
28 | ||
29 | const ( | |
30 | SocketFamily_INET SocketFamily = 1 | |
31 | SocketFamily_INET6 SocketFamily = 2 | |
32 | ) | |
33 | ||
34 | var SocketFamily_name = map[int32]string{ | |
35 | 1: "INET", | |
36 | 2: "INET6", | |
37 | } | |
38 | var SocketFamily_value = map[string]int32{ | |
39 | "INET": 1, | |
40 | "INET6": 2, | |
41 | } | |
42 | ||
43 | func (x SocketFamily) Enum() *SocketFamily { | |
44 | p := new(SocketFamily) | |
45 | *p = x | |
46 | return p | |
47 | } | |
48 | func (x SocketFamily) String() string { | |
49 | return proto.EnumName(SocketFamily_name, int32(x)) | |
50 | } | |
51 | func (x *SocketFamily) UnmarshalJSON(data []byte) error { | |
52 | value, err := proto.UnmarshalJSONEnum(SocketFamily_value, data, "SocketFamily") | |
53 | if err != nil { | |
54 | return err | |
55 | } | |
56 | *x = SocketFamily(value) | |
57 | return nil | |
58 | } | |
59 | ||
60 | // SocketProtocol: the transport protocol of a socket. This specifies how to | |
61 | // interpret "transport port" fields. | |
62 | type SocketProtocol int32 | |
63 | ||
64 | const ( | |
65 | SocketProtocol_UDP SocketProtocol = 1 | |
66 | SocketProtocol_TCP SocketProtocol = 2 | |
67 | ) | |
68 | ||
69 | var SocketProtocol_name = map[int32]string{ | |
70 | 1: "UDP", | |
71 | 2: "TCP", | |
72 | } | |
73 | var SocketProtocol_value = map[string]int32{ | |
74 | "UDP": 1, | |
75 | "TCP": 2, | |
76 | } | |
77 | ||
78 | func (x SocketProtocol) Enum() *SocketProtocol { | |
79 | p := new(SocketProtocol) | |
80 | *p = x | |
81 | return p | |
82 | } | |
83 | func (x SocketProtocol) String() string { | |
84 | return proto.EnumName(SocketProtocol_name, int32(x)) | |
85 | } | |
86 | func (x *SocketProtocol) UnmarshalJSON(data []byte) error { | |
87 | value, err := proto.UnmarshalJSONEnum(SocketProtocol_value, data, "SocketProtocol") | |
88 | if err != nil { | |
89 | return err | |
90 | } | |
91 | *x = SocketProtocol(value) | |
92 | return nil | |
93 | } | |
94 | ||
95 | // Identifies which field below is filled in. | |
96 | type Dnstap_Type int32 | |
97 | ||
98 | const ( | |
99 | Dnstap_MESSAGE Dnstap_Type = 1 | |
100 | ) | |
101 | ||
102 | var Dnstap_Type_name = map[int32]string{ | |
103 | 1: "MESSAGE", | |
104 | } | |
105 | var Dnstap_Type_value = map[string]int32{ | |
106 | "MESSAGE": 1, | |
107 | } | |
108 | ||
109 | func (x Dnstap_Type) Enum() *Dnstap_Type { | |
110 | p := new(Dnstap_Type) | |
111 | *p = x | |
112 | return p | |
113 | } | |
114 | func (x Dnstap_Type) String() string { | |
115 | return proto.EnumName(Dnstap_Type_name, int32(x)) | |
116 | } | |
117 | func (x *Dnstap_Type) UnmarshalJSON(data []byte) error { | |
118 | value, err := proto.UnmarshalJSONEnum(Dnstap_Type_value, data, "Dnstap_Type") | |
119 | if err != nil { | |
120 | return err | |
121 | } | |
122 | *x = Dnstap_Type(value) | |
123 | return nil | |
124 | } | |
125 | ||
126 | type Message_Type int32 | |
127 | ||
128 | const ( | |
129 | // AUTH_QUERY is a DNS query message received from a resolver by an | |
130 | // authoritative name server, from the perspective of the authorative | |
131 | // name server. | |
132 | Message_AUTH_QUERY Message_Type = 1 | |
133 | // AUTH_RESPONSE is a DNS response message sent from an authoritative | |
134 | // name server to a resolver, from the perspective of the authoritative | |
135 | // name server. | |
136 | Message_AUTH_RESPONSE Message_Type = 2 | |
137 | // RESOLVER_QUERY is a DNS query message sent from a resolver to an | |
138 | // authoritative name server, from the perspective of the resolver. | |
139 | // Resolvers typically clear the RD (recursion desired) bit when | |
140 | // sending queries. | |
141 | Message_RESOLVER_QUERY Message_Type = 3 | |
142 | // RESOLVER_RESPONSE is a DNS response message received from an | |
143 | // authoritative name server by a resolver, from the perspective of | |
144 | // the resolver. | |
145 | Message_RESOLVER_RESPONSE Message_Type = 4 | |
146 | // CLIENT_QUERY is a DNS query message sent from a client to a DNS | |
147 | // server which is expected to perform further recursion, from the | |
148 | // perspective of the DNS server. The client may be a stub resolver or | |
149 | // forwarder or some other type of software which typically sets the RD | |
150 | // (recursion desired) bit when querying the DNS server. The DNS server | |
151 | // may be a simple forwarding proxy or it may be a full recursive | |
152 | // resolver. | |
153 | Message_CLIENT_QUERY Message_Type = 5 | |
154 | // CLIENT_RESPONSE is a DNS response message sent from a DNS server to | |
155 | // a client, from the perspective of the DNS server. The DNS server | |
156 | // typically sets the RA (recursion available) bit when responding. | |
157 | Message_CLIENT_RESPONSE Message_Type = 6 | |
158 | // FORWARDER_QUERY is a DNS query message sent from a downstream DNS | |
159 | // server to an upstream DNS server which is expected to perform | |
160 | // further recursion, from the perspective of the downstream DNS | |
161 | // server. | |
162 | Message_FORWARDER_QUERY Message_Type = 7 | |
163 | // FORWARDER_RESPONSE is a DNS response message sent from an upstream | |
164 | // DNS server performing recursion to a downstream DNS server, from the | |
165 | // perspective of the downstream DNS server. | |
166 | Message_FORWARDER_RESPONSE Message_Type = 8 | |
167 | // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS | |
168 | // server, from the perspective of the stub resolver. | |
169 | Message_STUB_QUERY Message_Type = 9 | |
170 | // STUB_RESPONSE is a DNS response message sent from a DNS server to a | |
171 | // stub resolver, from the perspective of the stub resolver. | |
172 | Message_STUB_RESPONSE Message_Type = 10 | |
173 | // TOOL_QUERY is a DNS query message sent from a DNS software tool to a | |
174 | // DNS server, from the perspective of the tool. | |
175 | Message_TOOL_QUERY Message_Type = 11 | |
176 | // TOOL_RESPONSE is a DNS response message received by a DNS software | |
177 | // tool from a DNS server, from the perspective of the tool. | |
178 | Message_TOOL_RESPONSE Message_Type = 12 | |
179 | ) | |
180 | ||
181 | var Message_Type_name = map[int32]string{ | |
182 | 1: "AUTH_QUERY", | |
183 | 2: "AUTH_RESPONSE", | |
184 | 3: "RESOLVER_QUERY", | |
185 | 4: "RESOLVER_RESPONSE", | |
186 | 5: "CLIENT_QUERY", | |
187 | 6: "CLIENT_RESPONSE", | |
188 | 7: "FORWARDER_QUERY", | |
189 | 8: "FORWARDER_RESPONSE", | |
190 | 9: "STUB_QUERY", | |
191 | 10: "STUB_RESPONSE", | |
192 | 11: "TOOL_QUERY", | |
193 | 12: "TOOL_RESPONSE", | |
194 | } | |
195 | var Message_Type_value = map[string]int32{ | |
196 | "AUTH_QUERY": 1, | |
197 | "AUTH_RESPONSE": 2, | |
198 | "RESOLVER_QUERY": 3, | |
199 | "RESOLVER_RESPONSE": 4, | |
200 | "CLIENT_QUERY": 5, | |
201 | "CLIENT_RESPONSE": 6, | |
202 | "FORWARDER_QUERY": 7, | |
203 | "FORWARDER_RESPONSE": 8, | |
204 | "STUB_QUERY": 9, | |
205 | "STUB_RESPONSE": 10, | |
206 | "TOOL_QUERY": 11, | |
207 | "TOOL_RESPONSE": 12, | |
208 | } | |
209 | ||
210 | func (x Message_Type) Enum() *Message_Type { | |
211 | p := new(Message_Type) | |
212 | *p = x | |
213 | return p | |
214 | } | |
215 | func (x Message_Type) String() string { | |
216 | return proto.EnumName(Message_Type_name, int32(x)) | |
217 | } | |
218 | func (x *Message_Type) UnmarshalJSON(data []byte) error { | |
219 | value, err := proto.UnmarshalJSONEnum(Message_Type_value, data, "Message_Type") | |
220 | if err != nil { | |
221 | return err | |
222 | } | |
223 | *x = Message_Type(value) | |
224 | return nil | |
225 | } | |
226 | ||
227 | // "Dnstap": this is the top-level dnstap type, which is a "union" type that | |
228 | // contains other kinds of dnstap payloads, although currently only one type | |
229 | // of dnstap payload is defined. | |
230 | // See: https://developers.google.com/protocol-buffers/docs/techniques#union | |
231 | type Dnstap struct { | |
232 | // DNS server identity. | |
233 | // If enabled, this is the identity string of the DNS server which generated | |
234 | // this message. Typically this would be the same string as returned by an | |
235 | // "NSID" (RFC 5001) query. | |
236 | Identity []byte `protobuf:"bytes,1,opt,name=identity" json:"identity,omitempty"` | |
237 | // DNS server version. | |
238 | // If enabled, this is the version string of the DNS server which generated | |
239 | // this message. Typically this would be the same string as returned by a | |
240 | // "version.bind" query. | |
241 | Version []byte `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` | |
242 | // Extra data for this payload. | |
243 | // This field can be used for adding an arbitrary byte-string annotation to | |
244 | // the payload. No encoding or interpretation is applied or enforced. | |
245 | Extra []byte `protobuf:"bytes,3,opt,name=extra" json:"extra,omitempty"` | |
246 | Type *Dnstap_Type `protobuf:"varint,15,req,name=type,enum=dnstap.Dnstap_Type" json:"type,omitempty"` | |
247 | // One of the following will be filled in. | |
248 | Message *Message `protobuf:"bytes,14,opt,name=message" json:"message,omitempty"` | |
249 | XXX_unrecognized []byte `json:"-"` | |
250 | } | |
251 | ||
252 | func (m *Dnstap) Reset() { *m = Dnstap{} } | |
253 | func (m *Dnstap) String() string { return proto.CompactTextString(m) } | |
254 | func (*Dnstap) ProtoMessage() {} | |
255 | ||
256 | func (m *Dnstap) GetIdentity() []byte { | |
257 | if m != nil { | |
258 | return m.Identity | |
259 | } | |
260 | return nil | |
261 | } | |
262 | ||
263 | func (m *Dnstap) GetVersion() []byte { | |
264 | if m != nil { | |
265 | return m.Version | |
266 | } | |
267 | return nil | |
268 | } | |
269 | ||
270 | func (m *Dnstap) GetExtra() []byte { | |
271 | if m != nil { | |
272 | return m.Extra | |
273 | } | |
274 | return nil | |
275 | } | |
276 | ||
277 | func (m *Dnstap) GetType() Dnstap_Type { | |
278 | if m != nil && m.Type != nil { | |
279 | return *m.Type | |
280 | } | |
281 | return Dnstap_MESSAGE | |
282 | } | |
283 | ||
284 | func (m *Dnstap) GetMessage() *Message { | |
285 | if m != nil { | |
286 | return m.Message | |
287 | } | |
288 | return nil | |
289 | } | |
290 | ||
291 | // Message: a wire-format (RFC 1035 section 4) DNS message and associated | |
292 | // metadata. Applications generating "Message" payloads should follow | |
293 | // certain requirements based on the MessageType, see below. | |
294 | type Message struct { | |
295 | // One of the Type values described above. | |
296 | Type *Message_Type `protobuf:"varint,1,req,name=type,enum=dnstap.Message_Type" json:"type,omitempty"` | |
297 | // One of the SocketFamily values described above. | |
298 | SocketFamily *SocketFamily `protobuf:"varint,2,opt,name=socket_family,enum=dnstap.SocketFamily" json:"socket_family,omitempty"` | |
299 | // One of the SocketProtocol values described above. | |
300 | SocketProtocol *SocketProtocol `protobuf:"varint,3,opt,name=socket_protocol,enum=dnstap.SocketProtocol" json:"socket_protocol,omitempty"` | |
301 | // The network address of the message initiator. | |
302 | // For SocketFamily INET, this field is 4 octets (IPv4 address). | |
303 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). | |
304 | QueryAddress []byte `protobuf:"bytes,4,opt,name=query_address" json:"query_address,omitempty"` | |
305 | // The network address of the message responder. | |
306 | // For SocketFamily INET, this field is 4 octets (IPv4 address). | |
307 | // For SocketFamily INET6, this field is 16 octets (IPv6 address). | |
308 | ResponseAddress []byte `protobuf:"bytes,5,opt,name=response_address" json:"response_address,omitempty"` | |
309 | // The transport port of the message initiator. | |
310 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. | |
311 | QueryPort *uint32 `protobuf:"varint,6,opt,name=query_port" json:"query_port,omitempty"` | |
312 | // The transport port of the message responder. | |
313 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. | |
314 | ResponsePort *uint32 `protobuf:"varint,7,opt,name=response_port" json:"response_port,omitempty"` | |
315 | // The time at which the DNS query message was sent or received, depending | |
316 | // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. | |
317 | // This is the number of seconds since the UNIX epoch. | |
318 | QueryTimeSec *uint64 `protobuf:"varint,8,opt,name=query_time_sec" json:"query_time_sec,omitempty"` | |
319 | // The time at which the DNS query message was sent or received. | |
320 | // This is the seconds fraction, expressed as a count of nanoseconds. | |
321 | QueryTimeNsec *uint32 `protobuf:"fixed32,9,opt,name=query_time_nsec" json:"query_time_nsec,omitempty"` | |
322 | // The initiator's original wire-format DNS query message, verbatim. | |
323 | QueryMessage []byte `protobuf:"bytes,10,opt,name=query_message" json:"query_message,omitempty"` | |
324 | // The "zone" or "bailiwick" pertaining to the DNS query message. | |
325 | // This is a wire-format DNS domain name. | |
326 | QueryZone []byte `protobuf:"bytes,11,opt,name=query_zone" json:"query_zone,omitempty"` | |
327 | // The time at which the DNS response message was sent or received, | |
328 | // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or | |
329 | // CLIENT_RESPONSE. | |
330 | // This is the number of seconds since the UNIX epoch. | |
331 | ResponseTimeSec *uint64 `protobuf:"varint,12,opt,name=response_time_sec" json:"response_time_sec,omitempty"` | |
332 | // The time at which the DNS response message was sent or received. | |
333 | // This is the seconds fraction, expressed as a count of nanoseconds. | |
334 | ResponseTimeNsec *uint32 `protobuf:"fixed32,13,opt,name=response_time_nsec" json:"response_time_nsec,omitempty"` | |
335 | // The responder's original wire-format DNS response message, verbatim. | |
336 | ResponseMessage []byte `protobuf:"bytes,14,opt,name=response_message" json:"response_message,omitempty"` | |
337 | XXX_unrecognized []byte `json:"-"` | |
338 | } | |
339 | ||
340 | func (m *Message) Reset() { *m = Message{} } | |
341 | func (m *Message) String() string { return proto.CompactTextString(m) } | |
342 | func (*Message) ProtoMessage() {} | |
343 | ||
344 | func (m *Message) GetType() Message_Type { | |
345 | if m != nil && m.Type != nil { | |
346 | return *m.Type | |
347 | } | |
348 | return Message_AUTH_QUERY | |
349 | } | |
350 | ||
351 | func (m *Message) GetSocketFamily() SocketFamily { | |
352 | if m != nil && m.SocketFamily != nil { | |
353 | return *m.SocketFamily | |
354 | } | |
355 | return SocketFamily_INET | |
356 | } | |
357 | ||
358 | func (m *Message) GetSocketProtocol() SocketProtocol { | |
359 | if m != nil && m.SocketProtocol != nil { | |
360 | return *m.SocketProtocol | |
361 | } | |
362 | return SocketProtocol_UDP | |
363 | } | |
364 | ||
365 | func (m *Message) GetQueryAddress() []byte { | |
366 | if m != nil { | |
367 | return m.QueryAddress | |
368 | } | |
369 | return nil | |
370 | } | |
371 | ||
372 | func (m *Message) GetResponseAddress() []byte { | |
373 | if m != nil { | |
374 | return m.ResponseAddress | |
375 | } | |
376 | return nil | |
377 | } | |
378 | ||
379 | func (m *Message) GetQueryPort() uint32 { | |
380 | if m != nil && m.QueryPort != nil { | |
381 | return *m.QueryPort | |
382 | } | |
383 | return 0 | |
384 | } | |
385 | ||
386 | func (m *Message) GetResponsePort() uint32 { | |
387 | if m != nil && m.ResponsePort != nil { | |
388 | return *m.ResponsePort | |
389 | } | |
390 | return 0 | |
391 | } | |
392 | ||
393 | func (m *Message) GetQueryTimeSec() uint64 { | |
394 | if m != nil && m.QueryTimeSec != nil { | |
395 | return *m.QueryTimeSec | |
396 | } | |
397 | return 0 | |
398 | } | |
399 | ||
400 | func (m *Message) GetQueryTimeNsec() uint32 { | |
401 | if m != nil && m.QueryTimeNsec != nil { | |
402 | return *m.QueryTimeNsec | |
403 | } | |
404 | return 0 | |
405 | } | |
406 | ||
407 | func (m *Message) GetQueryMessage() []byte { | |
408 | if m != nil { | |
409 | return m.QueryMessage | |
410 | } | |
411 | return nil | |
412 | } | |
413 | ||
414 | func (m *Message) GetQueryZone() []byte { | |
415 | if m != nil { | |
416 | return m.QueryZone | |
417 | } | |
418 | return nil | |
419 | } | |
420 | ||
421 | func (m *Message) GetResponseTimeSec() uint64 { | |
422 | if m != nil && m.ResponseTimeSec != nil { | |
423 | return *m.ResponseTimeSec | |
424 | } | |
425 | return 0 | |
426 | } | |
427 | ||
428 | func (m *Message) GetResponseTimeNsec() uint32 { | |
429 | if m != nil && m.ResponseTimeNsec != nil { | |
430 | return *m.ResponseTimeNsec | |
431 | } | |
432 | return 0 | |
433 | } | |
434 | ||
435 | func (m *Message) GetResponseMessage() []byte { | |
436 | if m != nil { | |
437 | return m.ResponseMessage | |
438 | } | |
439 | return nil | |
440 | } | |
441 | ||
442 | func init() { | |
443 | proto.RegisterEnum("dnstap.SocketFamily", SocketFamily_name, SocketFamily_value) | |
444 | proto.RegisterEnum("dnstap.SocketProtocol", SocketProtocol_name, SocketProtocol_value) | |
445 | proto.RegisterEnum("dnstap.Dnstap_Type", Dnstap_Type_name, Dnstap_Type_value) | |
446 | proto.RegisterEnum("dnstap.Message_Type", Message_Type_name, Message_Type_value) | |
447 | } |
0 | package dnstap | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net" | |
5 | "testing" | |
6 | "time" | |
7 | ) | |
8 | ||
9 | func dialAndSend(t *testing.T, network, address string) *FrameStreamSockOutput { | |
10 | var addr net.Addr | |
11 | var err error | |
12 | switch network { | |
13 | case "unix": | |
14 | addr, err = net.ResolveUnixAddr(network, address) | |
15 | case "tcp", "tcp4", "tcp6": | |
16 | addr, err = net.ResolveTCPAddr(network, address) | |
17 | default: | |
18 | err = fmt.Errorf("invalid network %s", network) | |
19 | } | |
20 | if err != nil { | |
21 | t.Fatal(err) | |
22 | } | |
23 | ||
24 | out, err := NewFrameStreamSockOutput(addr) | |
25 | if err != nil { | |
26 | t.Fatal(err) | |
27 | } | |
28 | ||
29 | out.SetDialer(&net.Dialer{Timeout: time.Second}) | |
30 | out.SetTimeout(time.Second) | |
31 | out.SetFlushTimeout(100 * time.Millisecond) | |
32 | out.SetRetryInterval(time.Second) | |
33 | ||
34 | go out.RunOutputLoop() | |
35 | <-time.After(500 * time.Millisecond) | |
36 | out.GetOutputChannel() <- []byte("frame") | |
37 | return out | |
38 | } | |
39 | ||
40 | func readOne(t *testing.T, out chan []byte) { | |
41 | select { | |
42 | case <-out: | |
43 | case <-time.After(time.Second): | |
44 | t.Fatal("timed out waiting for frame") | |
45 | } | |
46 | } | |
47 | ||
48 | // Test if dnstap can accept multiple connections on the socket | |
49 | func TestMultiConn(t *testing.T) { | |
50 | in, err := NewFrameStreamSockInputFromPath("dnstap.sock") | |
51 | if err != nil { | |
52 | t.Fatal(err) | |
53 | } | |
54 | ||
55 | out := make(chan []byte) | |
56 | go in.ReadInto(out) | |
57 | ||
58 | // send two framestream messages on different connections | |
59 | defer dialAndSend(t, "unix", "dnstap.sock").Close() | |
60 | defer dialAndSend(t, "unix", "dnstap.sock").Close() | |
61 | ||
62 | readOne(t, out) | |
63 | readOne(t, out) | |
64 | } | |
65 | ||
66 | func TestReconnect(t *testing.T) { | |
67 | // Find an open port on localhost by opening a listener on an | |
68 | // unspecified port, querying its address, then closing it. | |
69 | l, err := net.Listen("tcp", "localhost:0") | |
70 | if err != nil { | |
71 | t.Fatal(err) | |
72 | } | |
73 | laddr := l.Addr() | |
74 | l.Close() | |
75 | ||
76 | defer dialAndSend(t, laddr.Network(), laddr.String()).Close() | |
77 | defer dialAndSend(t, laddr.Network(), laddr.String()).Close() | |
78 | time.Sleep(1500 * time.Millisecond) | |
79 | l, err = net.Listen(laddr.Network(), laddr.String()) | |
80 | if err != nil { | |
81 | t.Fatal(err) | |
82 | } | |
83 | ||
84 | in := NewFrameStreamSockInput(l) | |
85 | out := make(chan []byte) | |
86 | go in.ReadInto(out) | |
87 | readOne(t, out) | |
88 | readOne(t, out) | |
89 | } | |
90 | ||
91 | func BenchmarkConnectUnidirectional(b *testing.B) { | |
92 | b.StopTimer() | |
93 | l, err := net.Listen("tcp", "localhost:0") | |
94 | if err != nil { | |
95 | b.Fatal(err) | |
96 | } | |
97 | ||
98 | // read from tcp socket into outch | |
99 | outch := make(chan []byte, 32) | |
100 | go func() { | |
101 | // wait for connection | |
102 | s, err := l.Accept() | |
103 | if err != nil { | |
104 | b.Error(err) | |
105 | close(outch) | |
106 | return | |
107 | } | |
108 | ||
109 | // start rewriter | |
110 | in, err := NewFrameStreamInput(s, false) | |
111 | if err != nil { | |
112 | b.Error(err) | |
113 | close(outch) | |
114 | return | |
115 | } | |
116 | ||
117 | // read ASAP into outch | |
118 | in.ReadInto(outch) | |
119 | close(outch) | |
120 | }() | |
121 | ||
122 | // read from outch exactly b.N frames | |
123 | // this is separate from the above, because the process of rewriting tcp into outch | |
124 | // must run in parallel with reading b.N frames from outch | |
125 | readDone := make(chan struct{}) | |
126 | go func() { | |
127 | // wait for the first frame before starting the timer | |
128 | <-outch | |
129 | i := 1 | |
130 | ||
131 | b.StartTimer() | |
132 | for _ = range outch { i++ } | |
133 | if i != b.N { | |
134 | b.Error("invalid frame count") | |
135 | } | |
136 | close(readDone) | |
137 | }() | |
138 | ||
139 | // connect to tcp socket and start the output loop | |
140 | c, err := net.Dial(l.Addr().Network(), l.Addr().String()) | |
141 | if err != nil { | |
142 | b.Fatal(err) | |
143 | } | |
144 | out, err := NewFrameStreamOutput(c) | |
145 | if err != nil { | |
146 | b.Fatal(err) | |
147 | } | |
148 | go out.RunOutputLoop() | |
149 | ||
150 | // write to the output channel exactly b.N frames | |
151 | for i := 0; i < b.N; i++ { | |
152 | out.GetOutputChannel() <- []byte("frame") | |
153 | } | |
154 | out.Close() | |
155 | ||
156 | // wait for the reader | |
157 | <-readDone | |
158 | } | |
159 | ||
160 | func BenchmarkConnectBidirectional(b *testing.B) { | |
161 | b.StopTimer() | |
162 | l, err := net.Listen("tcp", "localhost:0") | |
163 | if err != nil { | |
164 | b.Fatal(err) | |
165 | } | |
166 | ||
167 | // start an infinite tcp socket reader | |
168 | in := NewFrameStreamSockInput(l) | |
169 | outch := make(chan []byte, 32) | |
170 | go in.ReadInto(outch) | |
171 | ||
172 | // read up to b.N frames in background | |
173 | readDone := make(chan struct{}) | |
174 | go func() { | |
175 | <-outch | |
176 | b.StartTimer() | |
177 | for i := 1; i < b.N; i++ { <-outch } // NB: read never fails | |
178 | close(readDone) | |
179 | }() | |
180 | ||
181 | // connect to tcp socket and start the output loop | |
182 | out, err := NewFrameStreamSockOutput(l.Addr()) | |
183 | if err != nil { | |
184 | b.Fatal(err) | |
185 | } | |
186 | go out.RunOutputLoop() | |
187 | ||
188 | // write to the output channel exactly b.N frames | |
189 | for i := 0; i < b.N; i++ { | |
190 | out.GetOutputChannel() <- []byte("frame") | |
191 | } | |
192 | out.Close() | |
193 | ||
194 | // wait for the reader | |
195 | <-readDone | |
196 | } |