1 /*
2 * Copyright (c) 2018 by Farsight Security, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18 #include <stddef.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25
26 #include "errors.h"
27
28 #include "nmsg.h"
29 #include "nmsg/asprintf.h"
30 #include "nmsg/alias.h"
31 #include "nmsg/chalias.h"
32 #include "nmsg/container.h"
33 #include "nmsg/msgmod.h"
34 #include "nmsg/vendors.h"
35 #include "nmsg/base/defs.h"
36 #include "defs.h"
37
38 #define NAME "test-io"
39
40
41 /*
42 * Test a wide variety of nmsg input filter functions.
43 *
44 * A small amount of trickery and indirection is required here.
45 * Certain filters are only applicable to nmsg inputs of type stream.
46 * This precludes certain vehicles like data in json and presentation format.
47 * While we could theoretically just open up a binary nmsg file with the
48 * function nmsg_input_open_sock(), the subsequent read via recvfrom() would
49 * fail on it since it's a local file and not a socket.
50 *
51 * Therefore we create a dummy fd with socketpair() and manually proxy
52 * our locally stored nmsg data across it.
53 */
54 static int
test_io_filters2(void)55 test_io_filters2(void)
56 {
57 int n;
58
59 for (n = 0; n < 11; n++) {
60 nmsg_input_t i;
61 nmsg_message_t m;
62 int fd, sfds[2];
63
64 check_return(socketpair(AF_LOCAL, SOCK_STREAM, 0, sfds) != -1);
65
66 fd = open(SRCDIR "/tests/generic-tests/newdomain.nmsg", O_RDONLY);
67 check_return(fd != -1);
68
69 i = nmsg_input_open_sock(sfds[0]);
70 check_return(i != NULL);
71
72 /* Only need to try this once. */
73 if (!n) {
74 check(nmsg_input_set_filter_msgtype_byname(i, "some_vendor", "nonexistent_type") != nmsg_res_success);
75 }
76
77 /* The ordering is particular. Every odd numbered test should
78 * succeed, and vice versa. */
79 switch(n) {
80 case 1:
81 nmsg_input_set_filter_msgtype(i, NMSG_VENDOR_SIE_ID, NMSG_VENDOR_SIE_DNSDEDUPE_ID);
82 break;
83 case 2:
84 nmsg_input_set_filter_msgtype(i, NMSG_VENDOR_SIE_ID, NMSG_VENDOR_SIE_NEWDOMAIN_ID);
85 break;
86 case 3:
87 nmsg_input_set_filter_group(i, 2835122346);
88 break;
89 case 4:
90 nmsg_input_set_filter_group(i, 0);
91 break;
92 case 5:
93 nmsg_input_set_filter_source(i, 1235817825);
94 break;
95 case 6:
96 nmsg_input_set_filter_source(i, 0xa1ba02cf);
97 break;
98 case 7:
99 nmsg_input_set_filter_operator(i, 138158152);
100 break;
101 case 8:
102 nmsg_input_set_filter_operator(i, 0);
103 break;
104 case 9:
105 check(nmsg_input_set_filter_msgtype_byname(i, "SIE", "dnsdedupe") == nmsg_res_success);
106 break;
107 case 10:
108 check(nmsg_input_set_filter_msgtype_byname(i, "SIE", "newdomain") == nmsg_res_success);
109 break;
110 default:
111 break;
112 }
113
114 while (1) {
115 char buf[1024];
116 int nread;
117
118 nread = read(fd, buf, sizeof(buf));
119
120 if (nread <= 0)
121 break;
122
123 check_return(write(sfds[1], buf, nread) == nread);
124 }
125
126 if (!(n % 2)) {
127 check(nmsg_input_read(i, &m) == nmsg_res_success);
128 } else {
129 check(nmsg_input_read(i, &m) != nmsg_res_success);
130 }
131
132 check(nmsg_input_close(&i) == nmsg_res_success);
133
134 close(fd);
135 close(sfds[1]);
136 }
137
138 l_return_test_status();
139 }
140
141 static void *user_data = (void *)0xdeadbeef;
142 static int touched_exit, touched_atstart, touched_close, num_received, touched_filter;
143
144 static void
test_close_fp(struct nmsg_io_close_event * ce)145 test_close_fp(struct nmsg_io_close_event *ce)
146 {
147 (void)ce; /* unused parameter */
148
149 __sync_add_and_fetch(&touched_close, 1);
150
151 return;
152 }
153
154 static void
test_atstart_fp(unsigned threadno,void * user)155 test_atstart_fp(unsigned threadno, void *user)
156 {
157 (void)threadno; /* unused parameter */
158
159 if (user == user_data)
160 __sync_add_and_fetch(&touched_atstart, 1);
161
162 return;
163 }
164
165 static void
test_atexit_fp(unsigned threadno,void * user)166 test_atexit_fp(unsigned threadno, void *user)
167 {
168 (void)threadno; /* unused parameter */
169
170 if (user == user_data)
171 __sync_add_and_fetch(&touched_exit, 1);
172
173 return;
174 }
175
176 static void
output_callback(nmsg_message_t msg,void * user)177 output_callback(nmsg_message_t msg, void *user)
178 {
179 (void)msg; /* unused parameter */
180
181 if (user == user_data)
182 __sync_add_and_fetch(&num_received, 1);
183
184 return;
185 }
186
187 /* A filter to permit only msg type NMSG_VENDOR_SIE_DNSDEDUPE_ID */
188 static nmsg_res
filter_callback(nmsg_message_t * msg,void * user,nmsg_filter_message_verdict * vres)189 filter_callback(nmsg_message_t *msg, void *user, nmsg_filter_message_verdict *vres)
190 {
191
192 if (user != user_data)
193 return nmsg_res_failure;
194
195 if (nmsg_message_get_msgtype(*msg) == NMSG_VENDOR_SIE_DNSDEDUPE_ID)
196 *vres = nmsg_filter_message_verdict_DROP;
197 else
198 *vres = nmsg_filter_message_verdict_ACCEPT;
199
200 __sync_add_and_fetch(&touched_filter, 1);
201
202 return nmsg_res_success;
203 }
204
205 /* Just to test the filter policy. */
206 static nmsg_res
filter_callback2(nmsg_message_t * msg,void * user,nmsg_filter_message_verdict * vres)207 filter_callback2(nmsg_message_t *msg, void *user, nmsg_filter_message_verdict *vres)
208 {
209 (void)msg; /* unused parameter */
210
211 if (user != user_data)
212 return nmsg_res_failure;
213
214 *vres = nmsg_filter_message_verdict_DECLINED;
215 __sync_add_and_fetch(&touched_filter, 1);
216
217 return nmsg_res_success;
218 }
219
220
221 /* XXX: Partially crippled.
222 * Test custom nmsg io filter callbacks and output callbacks;
223 * These are for close, at-start, and at-exit.
224 * Test nmsg_io_set_count() [broken]. */
225 static int
test_io_filters(void)226 test_io_filters(void)
227 {
228 nmsg_io_t io;
229 nmsg_output_t o;
230 size_t run_cnt = 0;
231
232 /*
233 * Loop #1: Verify all 10 nmsgs read normally.
234 * Loop #2: Set count to 7 and verify 7 msgs read normally.
235 * Loop #3: Apply first filter callback. It should drop all msgs of type !=
236 * dnsdedupe, meaning that half (5) of the packets will be dropped.
237 * Loop #4: Apply second filter callback.
238 * Loop #5: Apply second filter callback with default filter policy of DROP.
239 */
240 while (run_cnt < 5) {
241 io = nmsg_io_init();
242 check_return(io != NULL);
243
244 /* Feed the nmsg io loop with 2 nmsg files that have 5 messages each. */
245 check_return(nmsg_io_add_input_fname(io, SRCDIR "/tests/generic-tests/dedupe.nmsg", NULL) == nmsg_res_success);
246 check_return(nmsg_io_add_input_fname(io, SRCDIR "/tests/generic-tests/newdomain.nmsg", NULL) == nmsg_res_success);
247
248 /* Use an output callback for the output. */
249 o = nmsg_output_open_callback(output_callback, user_data);
250 check_return(o != NULL);
251 check_return(nmsg_io_add_output(io, o, user_data) == nmsg_res_success);
252
253 /* Reset the counters and set up all custom callbacks. */
254 touched_atstart = touched_exit = touched_close = num_received = touched_filter = 0;
255 nmsg_io_set_close_fp(io, test_close_fp);
256 nmsg_io_set_atstart_fp(io, test_atstart_fp, user_data);
257 nmsg_io_set_atexit_fp(io, test_atexit_fp, user_data);
258
259 if (!run_cnt)
260 nmsg_io_set_count(io, 10);
261 else if (run_cnt == 1)
262 nmsg_io_set_count(io, 7);
263 else
264 nmsg_io_set_count(io, 10);
265
266 if (run_cnt == 2) {
267 check(nmsg_io_add_filter(io, filter_callback, user_data) == nmsg_res_success);
268 } else if (run_cnt == 3) {
269 check(nmsg_io_add_filter(io, filter_callback2, user_data) == nmsg_res_success);
270 } else if (run_cnt == 4) {
271 check(nmsg_io_add_filter(io, filter_callback2, user_data) == nmsg_res_success);
272 /* XXX: This isn't working; it appears to be a bug in libnmsg? */
273 nmsg_io_set_filter_policy(io, nmsg_filter_message_verdict_DROP);
274
275 }
276
277 check(nmsg_io_loop(io) == nmsg_res_success);
278
279 nmsg_io_destroy(&io);
280 check(io == NULL);
281
282 check(touched_atstart != 0);
283 check(touched_exit == touched_atstart);
284 check(touched_close >= touched_atstart);
285
286 if (run_cnt == 2) {
287 check(touched_filter == 10);
288 check(num_received == 5);
289 } else if (run_cnt == 3) {
290 check(touched_filter == 10);
291 check(num_received == 10);
292 } else if (run_cnt == 4) {
293 check(touched_filter == 10);
294 check(num_received == 0);
295 } else {
296 check(touched_filter == 0);
297 check(num_received == 10);
298 }
299
300 run_cnt++;
301 }
302
303 l_return_test_status();
304 }
305
306 int
main(void)307 main(void)
308 {
309 check_abort(nmsg_init() == nmsg_res_success);
310
311 check_explicit2_display_only(test_io_filters() == 0, "test-io/ test_io_filters");
312 check_explicit2_display_only(test_io_filters2() == 0, "test-io/ test_io_filters2");
313
314 g_check_test_status(false);
315
316 }
317