cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

mon_network_sniffer.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 Northern.tech AS
3 
4  This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the
8  Free Software Foundation; version 3.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18 
19  To the extent this program is licensed as part of the Enterprise
20  versions of CFEngine, the applicable Commercial Open Source License
21  (COSL) may apply to this file if you as a licensee so wish it. See
22  included file COSL.txt.
23 */
24 
25 #include <cf3.defs.h>
26 
27 #include <files_names.h>
28 #include <files_interfaces.h>
29 #include <mon.h>
30 #include <item_lib.h>
31 #include <pipes.h>
32 #include <signals.h>
33 #include <string_lib.h>
34 #include <misc_lib.h>
35 #include <addr_lib.h>
36 #include <known_dirs.h>
37 
38 typedef enum
39 {
48 
49 /* Constants */
50 
51 #define CF_TCPDUMP_COMM "/usr/sbin/tcpdump -t -n -v"
52 
53 static const int SLEEPTIME = 2.5 * 60; /* Should be a fraction of 5 minutes */
54 
55 static const char *const TCPNAMES[CF_NETATTR] =
56 {
57  "icmp",
58  "udp",
59  "dns",
60  "tcpsyn",
61  "tcpack",
62  "tcpfin",
63  "misc"
64 };
65 
66 /* Global variables */
67 
68 static bool TCPDUMP = false;
69 static bool TCPPAUSE = false;
70 static FILE *TCPPIPE = NULL;
71 
72 static Item *NETIN_DIST[CF_NETATTR] = { NULL };
74 
75 /* Prototypes */
76 
77 static void Sniff(Item *ip_addresses, long iteration, double *cf_this);
78 static void AnalyzeArrival(Item *ip_addresses, long iteration, char *arrival, double *cf_this);
79 static void DePort(char *address);
80 
81 /* Implementation */
82 
83 void MonNetworkSnifferSniff(Item *ip_addresses, long iteration, double *cf_this)
84 {
85  if (TCPDUMP)
86  {
87  Sniff(ip_addresses, iteration, cf_this);
88  }
89  else
90  {
92  }
93 }
94 
95 /******************************************************************************/
96 
98 {
99  char tcpbuffer[CF_BUFSIZE];
100 
101  if (TCPDUMP)
102  {
103  struct stat statbuf;
104  char buffer[CF_MAXVARSIZE];
105 
106  sscanf(CF_TCPDUMP_COMM, "%s", buffer);
107 
108  if (stat(buffer, &statbuf) != -1)
109  {
110  if ((TCPPIPE = cf_popen(CF_TCPDUMP_COMM, "r", true)) == NULL)
111  {
112  TCPDUMP = false;
113  }
114 
115  /* Skip first banner */
116  if (fgets(tcpbuffer, sizeof(tcpbuffer), TCPPIPE) == NULL)
117  {
118  UnexpectedError("Failed to read output from '%s'", CF_TCPDUMP_COMM);
120  TCPPIPE = NULL;
121  TCPDUMP = false;
122  }
123  }
124  else
125  {
126  TCPDUMP = false;
127  }
128  }
129 }
130 
131 /******************************************************************************/
132 
133 void MonNetworkSnifferEnable(bool enable)
134 {
135  TCPDUMP = enable;
136  Log(LOG_LEVEL_DEBUG, "use tcpdump = %d", TCPDUMP);
137 }
138 
139 /******************************************************************************/
140 
141 static void CfenvTimeOut(ARG_UNUSED int signum)
142 {
143  alarm(0);
144  TCPPAUSE = true;
145  Log(LOG_LEVEL_VERBOSE, "Time out");
146 }
147 
148 /******************************************************************************/
149 
150 static void Sniff(Item *ip_addresses, long iteration, double *cf_this)
151 {
152  char tcpbuffer[CF_BUFSIZE];
153 
154  Log(LOG_LEVEL_VERBOSE, "Reading from tcpdump...");
155  memset(tcpbuffer, 0, CF_BUFSIZE);
156  signal(SIGALRM, CfenvTimeOut);
157  alarm(SLEEPTIME);
158  TCPPAUSE = false;
159 
160  while (!feof(TCPPIPE) && !IsPendingTermination())
161  {
162  if (TCPPAUSE)
163  {
164  break;
165  }
166 
167  if (fgets(tcpbuffer, sizeof(tcpbuffer), TCPPIPE) == NULL)
168  {
169  UnexpectedError("Unable to read data from tcpdump; closing pipe");
171  TCPPIPE = NULL;
172  TCPDUMP = false;
173  break;
174  }
175 
176  if (TCPPAUSE)
177  {
178  break;
179  }
180 
181  if (strstr(tcpbuffer, "tcpdump:")) /* Error message protect sleeptime */
182  {
183  Log(LOG_LEVEL_DEBUG, "Error - '%s'", tcpbuffer);
184  alarm(0);
185  TCPDUMP = false;
186  break;
187  }
188 
189  AnalyzeArrival(ip_addresses, iteration, tcpbuffer, cf_this);
190  }
191 
192  signal(SIGALRM, SIG_DFL);
193  TCPPAUSE = false;
194  fflush(TCPPIPE);
195 }
196 
197 /******************************************************************************/
198 
199 static void IncrementCounter(Item **list, char *name)
200 {
201  if (!IsItemIn(*list, name))
202  {
203  AppendItem(list, name, "");
204  }
205 
206  IncrementItemListCounter(*list, name);
207 }
208 
209 /* This coarsely classifies TCP dump data */
210 
211 static void AnalyzeArrival(Item *ip_addresses, long iteration, char *arrival, double *cf_this)
212 {
213  char src[CF_BUFSIZE], dest[CF_BUFSIZE], flag = '.', *arr;
214  int isme_dest, isme_src;
215 
216  src[0] = dest[0] = '\0';
217 
218  if (strstr(arrival, "listening"))
219  {
220  return;
221  }
222 
223  if (Chop(arrival, CF_EXPANDSIZE) == -1)
224  {
225  Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
226  }
227 
228 /* Most hosts have only a few dominant services, so anomalies will
229  show up even in the traffic without looking too closely. This
230  will apply only to the main interface .. not multifaces
231 
232  New format in tcpdump
233 
234  IP (tos 0x10, ttl 64, id 14587, offset 0, flags [DF], proto TCP (6), length 692) 128.39.89.232.22 > 84.215.40.125.48022: P 1546432:1547072(640) ack 1969 win 1593 <nop,nop,timestamp 25662737 1631360>
235  IP (tos 0x0, ttl 251, id 14109, offset 0, flags [DF], proto UDP (17), length 115) 84.208.20.110.53 > 192.168.1.103.32769: 45266 NXDomain 0/1/0 (87)
236  arp who-has 192.168.1.1 tell 192.168.1.103
237  arp reply 192.168.1.1 is-at 00:1d:7e:28:22:c6
238  IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.1.103 > 128.39.89.10: ICMP echo request, id 48474, seq 1, length 64
239  IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.1.103 > 128.39.89.10: ICMP echo request, id 48474, seq 2, length 64
240 */
241 
242  for (arr = strstr(arrival, "length"); (arr != NULL) && (*arr != ')'); arr++)
243  {
244  }
245 
246  if (arr == NULL)
247  {
248  arr = arrival;
249  }
250  else
251  {
252  arr++;
253  }
254  if ((strstr(arrival, "proto TCP")) || (strstr(arrival, "ack")))
255  {
256  assert(sizeof(src) == CF_BUFSIZE);
257  assert(sizeof(dest) == CF_BUFSIZE);
258  assert(CF_BUFSIZE == 4096);
259  sscanf(arr, "%4095s %*c %4095s %c ", src, dest, &flag);
260  DePort(src);
261  DePort(dest);
262  isme_dest = IsInterfaceAddress(ip_addresses, dest);
263  isme_src = IsInterfaceAddress(ip_addresses, src);
264 
265  switch (flag)
266  {
267  case 'S':
268  Log(LOG_LEVEL_DEBUG, "%ld: TCP new connection from '%s' to '%s' - i am '%s'", iteration, src, dest, VIPADDRESS);
269  if (isme_dest)
270  {
271  cf_this[ob_tcpsyn_in]++;
273  }
274  else if (isme_src)
275  {
276  cf_this[ob_tcpsyn_out]++;
278  }
279  break;
280 
281  case 'F':
282  Log(LOG_LEVEL_DEBUG, "%ld: TCP end connection from '%s' to '%s'", iteration, src, dest);
283  if (isme_dest)
284  {
285  cf_this[ob_tcpfin_in]++;
287  }
288  else if (isme_src)
289  {
290  cf_this[ob_tcpfin_out]++;
292  }
293  break;
294 
295  default:
296  Log(LOG_LEVEL_DEBUG, "%ld: TCP established from '%s' to '%s'", iteration, src, dest);
297 
298  if (isme_dest)
299  {
300  cf_this[ob_tcpack_in]++;
302  }
303  else if (isme_src)
304  {
305  cf_this[ob_tcpack_out]++;
307  }
308  break;
309  }
310  }
311  else if (strstr(arrival, ".53"))
312  {
313  assert(sizeof(src) == CF_BUFSIZE);
314  assert(sizeof(dest) == CF_BUFSIZE);
315  assert(CF_BUFSIZE == 4096);
316  sscanf(arr, "%4095s %*c %4095s %c ", src, dest, &flag);
317  DePort(src);
318  DePort(dest);
319  isme_dest = IsInterfaceAddress(ip_addresses, dest);
320  isme_src = IsInterfaceAddress(ip_addresses, src);
321 
322  Log(LOG_LEVEL_DEBUG, "%ld: DNS packet from '%s' to '%s'", iteration, src, dest);
323  if (isme_dest)
324  {
325  cf_this[ob_dns_in]++;
327  }
328  else if (isme_src)
329  {
330  cf_this[ob_dns_out]++;
332  }
333  }
334  else if (strstr(arrival, "proto UDP"))
335  {
336  assert(sizeof(src) == CF_BUFSIZE);
337  assert(sizeof(dest) == CF_BUFSIZE);
338  assert(CF_BUFSIZE == 4096);
339  sscanf(arr, "%4095s %*c %4095s %c ", src, dest, &flag);
340  DePort(src);
341  DePort(dest);
342  isme_dest = IsInterfaceAddress(ip_addresses, dest);
343  isme_src = IsInterfaceAddress(ip_addresses, src);
344 
345  Log(LOG_LEVEL_DEBUG, "%ld: UDP packet from '%s' to '%s'", iteration, src, dest);
346  if (isme_dest)
347  {
348  cf_this[ob_udp_in]++;
350  }
351  else if (isme_src)
352  {
353  cf_this[ob_udp_out]++;
355  }
356  }
357  else if (strstr(arrival, "proto ICMP"))
358  {
359  assert(sizeof(src) == CF_BUFSIZE);
360  assert(sizeof(dest) == CF_BUFSIZE);
361  assert(CF_BUFSIZE == 4096);
362  sscanf(arr, "%4095s %*c %4095s %c ", src, dest, &flag);
363  DePort(src);
364  DePort(dest);
365  isme_dest = IsInterfaceAddress(ip_addresses, dest);
366  isme_src = IsInterfaceAddress(ip_addresses, src);
367 
368  Log(LOG_LEVEL_DEBUG, "%ld: ICMP packet from '%s' to '%s'", iteration, src, dest);
369 
370  if (isme_dest)
371  {
372  cf_this[ob_icmp_in]++;
374  }
375  else if (isme_src)
376  {
377  cf_this[ob_icmp_out]++;
379  }
380  }
381  else
382  {
383  Log(LOG_LEVEL_DEBUG, "%ld: Miscellaneous undirected packet (%.100s)", iteration, arrival);
384 
385  cf_this[ob_tcpmisc_in]++;
386 
387  /* Here we don't know what source will be, but .... */
388  assert(sizeof(src) == CF_BUFSIZE);
389  assert(CF_BUFSIZE == 4096);
390  sscanf(arrival, "%4095s", src);
391 
392  if (!isdigit((int) *src))
393  {
394  Log(LOG_LEVEL_DEBUG, "Assuming continuation line...");
395  return;
396  }
397 
398  DePort(src);
399 
400  if (strstr(arrival, ".138"))
401  {
402  snprintf(dest, CF_BUFSIZE - 1, "%s NETBIOS", src);
403  }
404  else if (strstr(arrival, ".2049"))
405  {
406  snprintf(dest, CF_BUFSIZE - 1, "%s NFS", src);
407  }
408  else
409  {
410  strncpy(dest, src, 60);
411  dest[60] = '\0';
412  }
414  }
415 }
416 
417 /******************************************************************************/
418 
419 static void SaveTCPEntropyData(Item *list, int i, char *inout)
420 {
421  Item *ip;
422  char filename[CF_BUFSIZE];
423 
424  Log(LOG_LEVEL_VERBOSE, "TCP Save '%s'", TCPNAMES[i]);
425 
426  if (list == NULL)
427  {
428  Log(LOG_LEVEL_VERBOSE, "No %s-%s events", TCPNAMES[i], inout);
429  return;
430  }
431 
432  if (strncmp(inout, "in", 2) == 0)
433  {
434  snprintf(filename, CF_BUFSIZE, "%s%ccf_incoming.%s", GetStateDir(), FILE_SEPARATOR, TCPNAMES[i]);
435  }
436  else
437  {
438  snprintf(filename, CF_BUFSIZE, "%s%ccf_outgoing.%s", GetStateDir(), FILE_SEPARATOR, TCPNAMES[i]);
439  }
440 
441  Log(LOG_LEVEL_VERBOSE, "TCP Save '%s'", filename);
442 
443  FILE *fp = safe_fopen(filename, "w");
444  if (fp == NULL)
445  {
446  Log(LOG_LEVEL_ERR, "Couldn't save TCP entropy to '%s' (fopen: %s)", filename, GetErrorStr());
447  return;
448  }
449 
450  for (ip = list; ip != NULL; ip = ip->next)
451  {
452  fprintf(fp, "%d %s\n", ip->counter, ip->name);
453  }
454 
455  fclose(fp);
456 }
457 
458 /******************************************************************************/
459 
461 {
462  int i;
463  char vbuff[CF_BUFSIZE];
464 
465  const char* const statedir = GetStateDir();
466 
467  for (i = 0; i < CF_NETATTR; i++)
468  {
469  struct stat statbuf;
470  double entropy;
471  time_t now = time(NULL);
472 
473  Log(LOG_LEVEL_DEBUG, "save incoming '%s'", TCPNAMES[i]);
474  snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_incoming.%s", statedir, FILE_SEPARATOR, TCPNAMES[i]);
475 
476  if (stat(vbuff, &statbuf) != -1)
477  {
478  if (ItemListSize(NETIN_DIST[i]) < statbuf.st_size &&
479  now < statbuf.st_mtime + 40 * 60)
480  {
481  Log(LOG_LEVEL_VERBOSE, "New state %s is smaller, retaining old for 40 mins longer", TCPNAMES[i]);
483  NETIN_DIST[i] = NULL;
484  continue;
485  }
486  }
487 
488  SaveTCPEntropyData(NETIN_DIST[i], i, "in");
489 
490  entropy = MonEntropyCalculate(NETIN_DIST[i]);
491  MonEntropyClassesSet(TCPNAMES[i], "in", entropy);
493  NETIN_DIST[i] = NULL;
494  }
495 
496  for (i = 0; i < CF_NETATTR; i++)
497  {
498  struct stat statbuf;
499  double entropy;
500  time_t now = time(NULL);
501 
502  Log(LOG_LEVEL_DEBUG, "save outgoing '%s'", TCPNAMES[i]);
503  snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_outgoing.%s", statedir, FILE_SEPARATOR, TCPNAMES[i]);
504 
505  if (stat(vbuff, &statbuf) != -1)
506  {
507  if (ItemListSize(NETOUT_DIST[i]) < statbuf.st_size &&
508  now < statbuf.st_mtime + 40 * 60)
509  {
510  Log(LOG_LEVEL_VERBOSE, "New state '%s' is smaller, retaining old for 40 mins longer", TCPNAMES[i]);
512  NETOUT_DIST[i] = NULL;
513  continue;
514  }
515  }
516 
517  SaveTCPEntropyData(NETOUT_DIST[i], i, "out");
518 
519  entropy = MonEntropyCalculate(NETOUT_DIST[i]);
520  MonEntropyClassesSet(TCPNAMES[i], "out", entropy);
522  NETOUT_DIST[i] = NULL;
523  }
524 }
525 
526 void DePort(char *address)
527 {
528  char *sp, *chop, *fc = NULL, *fd = NULL, *ld = NULL;
529  int ccount = 0, dcount = 0;
530 
531 /* Start looking for ethernet/ipv6 addresses */
532 
533  for (sp = address; *sp != '\0'; sp++)
534  {
535  if (*sp == ':')
536  {
537  if (!fc)
538  {
539  fc = sp;
540  }
541  ccount++;
542  }
543 
544  if (*sp == '.')
545  {
546  if (!fd)
547  {
548  fd = sp;
549  }
550 
551  ld = sp;
552 
553  dcount++;
554  }
555  }
556 
557  if (!fd)
558  {
559  /* This does not look like an IP address+port, maybe ethernet */
560  return;
561  }
562 
563  if (dcount == 4)
564  {
565  chop = ld;
566  }
567  else if ((dcount > 1) && (fc != NULL))
568  {
569  chop = fc;
570  }
571  else if ((ccount > 1) && (fd != NULL))
572  {
573  chop = fd;
574  }
575  else
576  {
577  /* Don't recognize address */
578  return;
579  }
580 
581  if (chop < address + strlen(address))
582  {
583  *chop = '\0';
584  }
585 
586  return;
587 }
#define ARG_UNUSED
Definition: cf-net.c:47
#define CF_NETATTR
Definition: cf3.defs.h:142
char VIPADDRESS[64]
Definition: cf3globals.c:81
@ ob_dns_in
Definition: db_structs.h:118
@ ob_tcpack_out
Definition: db_structs.h:118
@ ob_tcpfin_out
Definition: db_structs.h:118
@ ob_icmp_in
Definition: db_structs.h:118
@ ob_udp_out
Definition: db_structs.h:118
@ ob_tcpfin_in
Definition: db_structs.h:118
@ ob_dns_out
Definition: db_structs.h:118
@ ob_tcpsyn_in
Definition: db_structs.h:118
@ ob_tcpmisc_in
Definition: db_structs.h:118
@ ob_tcpsyn_out
Definition: db_structs.h:118
@ ob_icmp_out
Definition: db_structs.h:118
@ ob_tcpack_in
Definition: db_structs.h:118
@ ob_udp_in
Definition: db_structs.h:118
#define CF_BUFSIZE
Definition: definitions.h:50
#define CF_EXPANDSIZE
Definition: definitions.h:51
#define CF_MAXVARSIZE
Definition: definitions.h:36
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
#define FILE_SEPARATOR
Definition: file_lib.h:102
#define NULL
Definition: getopt1.c:56
void IncrementItemListCounter(Item *list, const char *item)
Definition: item_lib.c:734
bool IsInterfaceAddress(const Item *ip_addresses, const char *adr)
Definition: item_lib.c:1085
void AppendItem(Item **liststart, const char *itemstring, const char *classes)
Definition: item_lib.c:415
void DeleteItemList(Item *item)
Definition: item_lib.c:808
int ItemListSize(const Item *list)
Definition: item_lib.c:143
bool IsItemIn(const Item *list, const char *item)
Definition: item_lib.c:226
const char * GetStateDir(void)
Definition: known_dirs.c:186
const char * GetErrorStr(void)
Definition: logging.c:275
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
#define UnexpectedError(...)
Definition: misc_lib.h:38
void MonEntropyClassesSet(const char *service, const char *direction, double entropy)
Definition: mon_entropy.c:98
double MonEntropyCalculate(const Item *items)
Definition: mon_entropy.c:68
static Item * NETIN_DIST[7]
void MonNetworkSnifferEnable(bool enable)
static void SaveTCPEntropyData(Item *list, int i, char *inout)
void MonNetworkSnifferSniff(Item *ip_addresses, long iteration, double *cf_this)
static void AnalyzeArrival(Item *ip_addresses, long iteration, char *arrival, double *cf_this)
static bool TCPPAUSE
static const int SLEEPTIME
void MonNetworkSnifferGatherData(void)
static void IncrementCounter(Item **list, char *name)
static void Sniff(Item *ip_addresses, long iteration, double *cf_this)
static const char *const TCPNAMES[7]
static bool TCPDUMP
#define CF_TCPDUMP_COMM
static void DePort(char *address)
static void CfenvTimeOut(int signum)
static Item * NETOUT_DIST[7]
@ IP_TYPES_ICMP
@ IP_TYPES_DNS
@ IP_TYPES_UDP
@ IP_TYPES_TCP_MISC
@ IP_TYPES_TCP_SYN
@ IP_TYPES_TCP_ACK
@ IP_TYPES_TCP_FIN
static FILE * TCPPIPE
void MonNetworkSnifferOpen(void)
int cf_pclose(FILE *pp)
Definition: pipes_unix.c:812
FILE * cf_popen(const char *command, const char *type, bool capture_stderr)
Definition: pipes_unix.c:332
unsigned int sleep(unsigned int seconds)
unsigned int alarm(unsigned int seconds)
bool IsPendingTermination(void)
Definition: signals.c:35
int Chop(char *str, size_t max_length)
Remove trailing spaces.
Definition: string_lib.c:1174
char * strstr(const char *haystack, const char *needle)
Definition: strstr.c:35
Definition: item_lib.h:33
Item * next
Definition: item_lib.h:38
int counter
Definition: item_lib.h:36
char * name
Definition: item_lib.h:34