/*
* Copyright (c) 2011
* Author: Ronald van der Pol
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <syslog.h>
#include "ieee8021ag.h"
#include "dot1ag_eth.h"
#include <pcap.h>
#define WAKEUP 20000 /* wakeup every 20 ms */
enum LOG_ACTION {
UP,
DOWN,
INFO
};
typedef struct _facmap {
char *name;
int value;
} FACMAP;
FACMAP facilitymap[] = {
{"LOG_KERN", LOG_KERN, },
{"LOG_USER", LOG_USER, },
{"LOG_MAIL", LOG_MAIL, },
{"LOG_NEWS", LOG_NEWS, },
{"LOG_UUCP", LOG_UUCP, },
{"LOG_DAEMON", LOG_DAEMON, },
{"LOG_AUTH", LOG_AUTH, },
{"LOG_CRON", LOG_CRON, },
{"LOG_LPR", LOG_LPR, },
{"LOG_LOCAL0", LOG_LOCAL0, },
{"LOG_LOCAL1", LOG_LOCAL1, },
{"LOG_LOCAL2", LOG_LOCAL2, },
{"LOG_LOCAL3", LOG_LOCAL3, },
{"LOG_LOCAL4", LOG_LOCAL4, },
{"LOG_LOCAL5", LOG_LOCAL5, },
{"LOG_LOCAL6", LOG_LOCAL6, },
{"LOG_LOCAL7", LOG_LOCAL7, },
{NULL, -1 }
};
pcap_t *handle;
static void
usage(void);
static int
equalstrings(char *s1, char *s2);
static void
cfm_ccm_receiver(char *ifname, struct pcap_pkthdr *pcap_hdr,
const u_char *buf, uint16_t vlan, int verbose);
static void
ccmlog(int mepid, enum LOG_ACTION action);
/* log status of all MEPs on HUP signal */
static void
log_mep_info(int sig) {
ccmlog(0, INFO);
}
struct rMEP rMEPdb[MAX_MEPID + 1];
int CCMinterval = -1;
int mepid = -1;
uint8_t mdLevel = 0;
char *md = NULL;
char *ma = NULL;
int
main(int argc, char **argv) {
int i;
int ch;
uint16_t vlan = 0;
char *ifname = NULL;
uint8_t localmac[ETHER_ADDR_LEN];
struct bpf_program filter; /* compiled BPF filter */
char filter_src[1024];
char errbuf[PCAP_ERRBUF_SIZE];
struct timeval tval;
struct sigaction act;
struct timeval now, next_ccm;
fd_set fdset;
int pcap_fd;
FACMAP *m;
char *syslog_fac = "LOG_DAEMON";
int facility = LOG_DAEMON;
int opts;
int verbose = 0;
/* schedule next CCM to be sent to now */
gettimeofday(&next_ccm, NULL);
/* parse command line options */
while ((ch = getopt(argc, argv, "hi:l:m:v:t:d:a:f:V")) != -1) {
switch(ch) {
case 'h':
usage();
break;
case 'i':
ifname = optarg;
break;
case 'l':
mdLevel = atoi(optarg);
break;
case 'm':
mepid = atoi(optarg);
break;
case 'v':
vlan = atoi(optarg);
break;
case 't':
CCMinterval = atoi(optarg);
break;
case 'd':
md = optarg;
break;
case 'a':
ma = optarg;
break;
case 'f':
syslog_fac = optarg;
facility = -1; /* will be set below if optarg OK */
break;
case 'V':
verbose = 1;
break;
case '?':
default:
usage();
}
}
if (argc - optind != 0) {
usage();
}
/* check for mandatory '-i' flag */
if (ifname == NULL) {
usage();
}
/* MD level should be in range 0-7 */
if (mdLevel > 7) {
fprintf(stderr, "MD level should be in range 0-7\n");
exit(EXIT_FAILURE);
}
/* check for valid '-t' flag */
/*
* 10 ms is not supported because it is probably a too short
* interrupt time for most Unix based systems.
*/
switch (CCMinterval) {
case 100:
case 1000:
case 10000:
case 60000:
case 600000:
break;
default:
fprintf(stderr, "Supported CCM interval times are:\n");
fprintf(stderr, "100, 1000, 10000, 60000, 600000 ms\n");
exit(EXIT_FAILURE);
}
/* check for mandatory '-d' flag */
if (md == NULL) {
usage();
}
/* check for mandatory '-a' flag */
if (ma == NULL) {
usage();
}
/* check for mandatory '-m' flag */
if ((mepid < 1) || (mepid > 8191)) {
fprintf(stderr, "MEPID should be in range 1-8191\n");
exit(EXIT_FAILURE);
}
/* loop through all facility names until a correct one is found */
for (m = facilitymap; m->name; m++) {
if (equalstrings(syslog_fac, m->name)) {
/* syslog_fac is a known syslog facility */
facility = m->value;
break;
}
/* try also without LOG_ prefix */
if (equalstrings(syslog_fac, m->name + 4)) {
/* syslog_fac is a known syslog facility */
facility = m->value;
break;
}
}
if (facility == -1) {
fprintf(stderr, "Syslog facility '%s' unknown, "
"supported: LOG_KERN, LOG_USER, LOG_MAIL, "
"LOG_NEWS, LOG_UUCP, LOG_DAEMON, LOG_AUTH, "
"LOG_CRON, LOG_LPR, LOG_LOCAL0 .. LOG_LOCAL7\n",
syslog_fac);
exit(EXIT_FAILURE);
}
/* command line argument parsing finished */
/* initialize remote MEP database */
for (i = 1; i <= MAX_MEPID; i++) {
rMEPdb[i].active = 0;
}
/* get Ethernet address of outgoing interface */
if (get_local_mac(ifname, localmac) != 0) {
perror(ifname);
exit(EXIT_FAILURE);
}
openlog("dot1ag_ccd", 0, facility);
/* define signal handler */
act.sa_handler = &log_mep_info;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGHUP, &act, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
/* open pcap device for listening */
handle = pcap_open_live(ifname, SNAPLEN, 1, 100, errbuf);
if (handle == NULL) {
perror(errbuf);
exit(EXIT_FAILURE);
}
/* Compile and apply the filter */
/*
* Filter to receive CFM frames (Ethertype 0x8902) only.
* Filter on both untagged (ether[12:2]) or 802.1Q tagged
* (ether[16:2]) frames.
*/
sprintf(filter_src, "ether[12:2] == 0x%x or "
"(ether[12:2] == 0x%x and ether[16:2] == 0x%x)",
ETYPE_CFM, ETYPE_8021Q, ETYPE_CFM);
/*
printf("filter is %s\n", filter_src);
*/
pcap_compile(handle, &filter, filter_src, 0, 0);
pcap_setfilter(handle, &filter);
/* get pcap file descriptor */
pcap_fd = pcap_get_selectable_fd(handle);
/* set pcap file descriptor to non-blocking */
opts = fcntl(pcap_fd, F_GETFL);
if (opts < 0) {
perror("F_GETFL on pcap fd");
exit(EXIT_FAILURE);
}
opts = (opts | O_NONBLOCK);
if (fcntl(pcap_fd, F_SETFL, opts) < 0) {
perror("F_SETFL on pcap fd");
exit(EXIT_FAILURE);
}
printf("Sending CFM frames on %s every %d %s\n", ifname,
CCMinterval >= 1000 ? CCMinterval / 1000 : CCMinterval,
CCMinterval >= 1000 ? "seconds" : "ms");
printf("Listening on interface %s for CFM frames\n", ifname);
/* listen for CFM frames */
while (1) {
int n;
struct cfmhdr *cfmhdr;
struct pcap_pkthdr *pcap_hdr; /* header returned by pcap */
const u_char *data;
/* do we need to send a CCM? */
gettimeofday(&now, NULL);
if (cfm_timevalcmp(next_ccm, now, <)) {
cfm_ccm_sender(ifname, vlan, mdLevel, md,
ma, mepid, CCMinterval);
if (CCMinterval >= 1000) {
next_ccm.tv_sec =
now.tv_sec + CCMinterval / 1000;
next_ccm.tv_usec = now.tv_usec;
} else {
next_ccm.tv_sec = now.tv_sec;
next_