[dhcp-relay]: Add DHCP Relay Monitor (#3886)

DHCP relay MONitor (dhcpmon) keeps track of DORA messages. If DHCP Relay
is detected to be not forwarding DORA message, dhcpmon will log such event
to syslog. Under the hood dhcpmon keeps counts of clients DR messages,
forwarded DR messages, DHCP server OA messages, and forwarded OA messages.
dhcpmon will check every 12 sec (configurable) if counts are monotonically
increasing and record snapshot of those counters. dhcpmon will report
discrepancies when detected between current counters and snapshot counters.

pull-request: https://github.com/Azure/sonic-buildimage/pull/3886
signed-off-by: Tamer Ahmed <tamer.ahmed@microsoft.com>
This commit is contained in:
Tamer Ahmed 2020-01-07 17:48:03 -08:00 committed by GitHub
parent 3548587b73
commit 2658ab8add
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1413 additions and 2 deletions

6
.gitignore vendored
View File

@ -23,6 +23,11 @@ target/
# Subdirectories in src
src/bash/*
!src/bash/Makefile
src/dhcpmon/debian/*
!src/dhcpmon/debian/changelog
!src/dhcpmon/debian/compat
!src/dhcpmon/debian/control
!src/dhcpmon/debian/rules
src/ixgbe/*
!src/ixgbe/Makefile
src/isc-dhcp/*
@ -106,6 +111,7 @@ src/thrift/*
# Autogenerated Dockerfiles
sonic-slave/Dockerfile
sonic-slave-stretch/Dockerfile
sonic-slave-jessie/Dockerfile
dockers/docker-base/Dockerfile
dockers/docker-base-stretch/Dockerfile
dockers/docker-config-engine/Dockerfile

View File

@ -85,5 +85,53 @@ stderr_logfile=syslog
{% endif %}
{% endif %}
{% endfor %}
[group:dhcpmon]
programs=
{%- set add_preceding_comma = { 'flag': False } %}
{% for vlan_name in VLAN %}
{% if VLAN[vlan_name]['dhcp_servers'] %}
{% if add_preceding_comma.flag %},{% endif %}
{% set _dummy = add_preceding_comma.update({'flag': True}) %}
dhcpmon-{{ vlan_name }}
{%- endif %}
{% endfor %}
{# Create a program entry for each DHCP MONitor instance #}
{% set relay_for_ipv4 = { 'flag': False } %}
{% for vlan_name in VLAN %}
{% if VLAN[vlan_name]['dhcp_servers'] %}
{% for dhcp_server in VLAN[vlan_name]['dhcp_servers'] %}
{% if dhcp_server | ipv4 %}
{% set _dummy = relay_for_ipv4.update({'flag': True}) %}
{% endif %}
{% endfor %}
{% if relay_for_ipv4.flag %}
{% set _dummy = relay_for_ipv4.update({'flag': False}) %}
[program:dhcpmon-{{ vlan_name }}]
{# We treat this VLAN as a downstream interface (-id), as we only want to listen for requests #}
command=/usr/sbin/dhcpmon -id {{ vlan_name }}
{#- We treat all other interfaces as upstream interfaces (-iu), as we only want to listen for replies #}
{% for (name, prefix) in VLAN_INTERFACE|pfx_filter %}
{% if prefix | ipv4 and name != vlan_name %} -iu {{ name }}{% endif -%}
{% endfor %}
{% for (name, prefix) in INTERFACE|pfx_filter %}
{% if prefix | ipv4 %} -iu {{ name }}{% endif -%}
{% endfor %}
{% for (name, prefix) in PORTCHANNEL_INTERFACE|pfx_filter %}
{% if prefix | ipv4 %} -iu {{ name }}{% endif -%}
{% endfor %}
priority=4
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}

View File

@ -19,3 +19,9 @@ if [ $(supervisorctl status | grep -c "^isc-dhcp-relay:") -gt 0 ]; then
# Start all DHCP relay agent(s)
supervisorctl start isc-dhcp-relay:*
fi
# If our supervisor config has entries in the "dhcpmon" group...
if [ $(supervisorctl status | grep -c "^dhcpmon:") -gt 0 ]; then
# Start all DHCP Monitor daemon(s)
supervisorctl start dhcpmon:*
fi

8
rules/dhcpmon.mk Normal file
View File

@ -0,0 +1,8 @@
# SONiC DHCP MONitor package
SONIC_DHCPMON_VERSION = 1.0.0-0
SONIC_DHCPMON_PKG_NAME = dhcpmon
SONIC_DHCPMON = sonic-$(SONIC_DHCPMON_PKG_NAME)_$(SONIC_DHCPMON_VERSION)_$(CONFIGURED_ARCH).deb
$(SONIC_DHCPMON)_SRC_PATH = $(SRC_PATH)/$(SONIC_DHCPMON_PKG_NAME)
SONIC_DPKG_DEBS += $(SONIC_DHCPMON)

View File

@ -6,7 +6,7 @@ DOCKER_DHCP_RELAY_DBG = $(DOCKER_DHCP_RELAY_STEM)-$(DBG_IMAGE_MARK).gz
$(DOCKER_DHCP_RELAY)_PATH = $(DOCKERS_PATH)/$(DOCKER_DHCP_RELAY_STEM)
$(DOCKER_DHCP_RELAY)_DEPENDS += $(ISC_DHCP_RELAY) $(REDIS_TOOLS)
$(DOCKER_DHCP_RELAY)_DEPENDS += $(ISC_DHCP_RELAY) $(REDIS_TOOLS) $(SONIC_DHCPMON)
$(DOCKER_DHCP_RELAY)_DBG_DEPENDS = $($(DOCKER_CONFIG_ENGINE_STRETCH)_DBG_DEPENDS)
$(DOCKER_DHCP_RELAY)_DBG_DEPENDS += $(ISC_DHCP_RELAY_DBG)

View File

@ -295,7 +295,10 @@ RUN apt-get update && apt-get install -y \
# For kdump-tools
liblzo2-dev \
# For SAI3.7
libprotobuf-dev
libprotobuf-dev \
# For DHCP Monitor tool
libexplain-dev \
libevent-dev
## Config dpkg
## install the configuration file if its currently missing

44
src/dhcpmon/Makefile Normal file
View File

@ -0,0 +1,44 @@
RM := rm -rf
DHCPMON_TARGET := dhcpmon
CP := cp
MKDIR := mkdir
CC := gcc
MV := mv
# All of the sources participating in the build are defined here
-include src/subdir.mk
-include objects.mk
ifneq ($(MAKECMDGOALS),clean)
ifneq ($(strip $(C_DEPS)),)
-include $(C_DEPS)
endif
endif
# Add inputs and outputs from these tool invocations to the build variables
# All Target
all: sonic-dhcpmon
# Tool invocations
sonic-dhcpmon: $(OBJS) $(USER_OBJS)
@echo 'Building target: $@'
@echo 'Invoking: GCC C Linker'
$(CC) -o "$(DHCPMON_TARGET)" $(OBJS) $(USER_OBJS) $(LIBS)
@echo 'Finished building target: $@'
@echo ' '
# Other Targets
install:
$(MKDIR) -p $(DESTDIR)/usr/sbin
$(MV) $(DHCPMON_TARGET) $(DESTDIR)/usr/sbin
deinstall:
$(RM) $(DESTDIR)/usr/sbin/$(DHCPMON_TARGET)
$(RM) -rf $(DESTDIR)/usr/sbin
clean:
-$(RM) $(EXECUTABLES)$(OBJS)$(C_DEPS) $(DHCPMON_TARGET)
-@echo ' '
.PHONY: all clean dependents

View File

@ -0,0 +1,5 @@
sonic-dhcpmon (1.0.0-0) UNRELEASED; urgency=medium
* Initial release.
-- Tamer Ahmed <tamer.ahmed@microsoft.com> Mon, 09 Dec 2019 12:00:00 -0700

View File

@ -0,0 +1 @@
9

View File

@ -0,0 +1,16 @@
Source: sonic-dhcpmon
Section: devel
Priority: optional
Maintainer: Tamer Ahmed <tamer.ahmed@microsoft.com>
Build-Depends: debhelper (>= 8.0.0),
dh-systemd
Standards-Version: 3.9.3
Homepage: https://github.com/Azure/sonic-buildimage
XS-Go-Import-Path: github.com/Azure/sonic-buildimage
Package: sonic-dhcpmon
Architecture: any
Built-Using: ${misc:Built-Using}
Depends: libexplain51,
libevent-2.0-5
Description: SONiC DHCP Monitor

3
src/dhcpmon/debian/rules Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/make -f
%:
dh $@ --with systemd

4
src/dhcpmon/objects.mk Normal file
View File

@ -0,0 +1,4 @@
USER_OBJS :=
LIBS := -levent -lexplain

View File

@ -0,0 +1,460 @@
/**
* @file dhcp_device.c
*
* device (interface) module
*/
#include <err.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/ether.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <syslog.h>
#include <libexplain/ioctl.h>
#include <linux/filter.h>
#include <netpacket/packet.h>
#include "dhcp_device.h"
/** Start of Ether header of a captured frame */
#define ETHER_START_OFFSET 0
/** Start of IP header of a captured frame */
#define IP_START_OFFSET (ETHER_START_OFFSET + ETHER_HDR_LEN)
/** Start of UDP header of a captured frame */
#define UDP_START_OFFSET (IP_START_OFFSET + sizeof(struct ip))
/** Start of DHCP header of a captured frame */
#define DHCP_START_OFFSET (UDP_START_OFFSET + sizeof(struct udphdr))
/** Start of DHCP Options segment of a captured frame */
#define DHCP_OPTIONS_HEADER_SIZE 240
#define OP_LDHA (BPF_LD | BPF_H | BPF_ABS) /** bpf ldh Abs */
#define OP_LDHI (BPF_LD | BPF_H | BPF_IND) /** bpf ldh Ind */
#define OP_LDB (BPF_LD | BPF_B | BPF_ABS) /** bpf ldb Abs*/
#define OP_JEQ (BPF_JMP | BPF_JEQ | BPF_K) /** bpf jeq */
#define OP_JGT (BPF_JMP | BPF_JGT | BPF_K) /** bpf jgt */
#define OP_RET (BPF_RET | BPF_K) /** bpf ret */
#define OP_JSET (BPF_JMP | BPF_JSET | BPF_K) /** bpf jset */
#define OP_LDXB (BPF_LDX | BPF_B | BPF_MSH) /** bpf ldxb */
/** Berkley Packet Fitler program for "udp and (port 67 or port 68)". This program is obtained suing the following
* tcpdump command: 'tcpdump -dd "udp and (port 67 or port 68)"'
*/
static struct sock_filter dhcp_bpf_code[] = {
{.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x0000000c}, // (000) ldh [12]
{.code = OP_JEQ, .jt = 0, .jf = 7, .k = 0x000086dd}, // (001) jeq #0x86dd jt 2 jf 9
{.code = OP_LDB, .jt = 0, .jf = 0, .k = 0x00000014}, // (002) ldb [20]
{.code = OP_JEQ, .jt = 0, .jf = 18, .k = 0x00000011}, // (003) jeq #0x11 jt 4 jf 22
{.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000036}, // (004) ldh [54]
{.code = OP_JEQ, .jt = 15, .jf = 0, .k = 0x00000043}, // (005) jeq #0x43 jt 21 jf 6
{.code = OP_JEQ, .jt = 14, .jf = 0, .k = 0x00000044}, // (006) jeq #0x44 jt 21 jf 7
{.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000038}, // (007) ldh [56]
{.code = OP_JEQ, .jt = 12, .jf = 11, .k = 0x00000043}, // (008) jeq #0x43 jt 21 jf 20
{.code = OP_JEQ, .jt = 0, .jf = 12, .k = 0x00000800}, // (009) jeq #0x800 jt 10 jf 22
{.code = OP_LDB, .jt = 0, .jf = 0, .k = 0x00000017}, // (010) ldb [23]
{.code = OP_JEQ, .jt = 0, .jf = 10, .k = 0x00000011}, // (011) jeq #0x11 jt 12 jf 22
{.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000014}, // (012) ldh [20]
{.code = OP_JSET, .jt = 8, .jf = 0, .k = 0x00001fff}, // (013) jset #0x1fff jt 22 jf 14
{.code = OP_LDXB, .jt = 0, .jf = 0, .k = 0x0000000e}, // (014) ldxb 4*([14]&0xf)
{.code = OP_LDHI, .jt = 0, .jf = 0, .k = 0x0000000e}, // (015) ldh [x + 14]
{.code = OP_JEQ, .jt = 4, .jf = 0, .k = 0x00000043}, // (016) jeq #0x43 jt 21 jf 17
{.code = OP_JEQ, .jt = 3, .jf = 0, .k = 0x00000044}, // (017) jeq #0x44 jt 21 jf 18
{.code = OP_LDHI, .jt = 0, .jf = 0, .k = 0x00000010}, // (018) ldh [x + 16]
{.code = OP_JEQ, .jt = 1, .jf = 0, .k = 0x00000043}, // (019) jeq #0x43 jt 21 jf 20
{.code = OP_JEQ, .jt = 0, .jf = 1, .k = 0x00000044}, // (020) jeq #0x44 jt 21 jf 22
{.code = OP_RET, .jt = 0, .jf = 0, .k = 0x00040000}, // (021) ret #262144
{.code = OP_RET, .jt = 0, .jf = 0, .k = 0x00000000}, // (022) ret #0
};
/** Filter program socket struct */
static struct sock_fprog dhcp_sock_bfp = {
.len = sizeof(dhcp_bpf_code) / sizeof(*dhcp_bpf_code), .filter = dhcp_bpf_code
};
/** global aggregate counter for DHCP interfaces */
static dhcp_device_counters_t glob_counters[DHCP_DIR_COUNT] = {
[DHCP_RX] = {.discover = 0, .offer = 0, .request = 0, .ack = 0},
[DHCP_TX] = {.discover = 0, .offer = 0, .request = 0, .ack = 0},
};
/** global aggregate counter snapshot for DHCP interfaces */
static dhcp_device_counters_t glob_counters_snapshot[DHCP_DIR_COUNT] = {
[DHCP_RX] = {.discover = 0, .offer = 0, .request = 0, .ack = 0},
[DHCP_TX] = {.discover = 0, .offer = 0, .request = 0, .ack = 0},
};
/**
* @code handle_dhcp_option_53(context, dhcp_option, dir);
*
* @brief handle the logic related to DHCP option 53
*
* @param context Device (interface) context
* @param dhcp_option pointer to DHCP option buffer space
* @param dir packet direction
*
* @return none
*/
static void handle_dhcp_option_53(dhcp_device_context_t *context, const u_char *dhcp_option, dhcp_packet_direction_t dir)
{
switch (dhcp_option[2])
{
case 1:
context->counters[dir].discover++;
if ((context->is_uplink && dir == DHCP_TX) || (!context->is_uplink && dir == DHCP_RX)) {
glob_counters[dir].discover++;
}
break;
case 2:
context->counters[dir].offer++;
if ((!context->is_uplink && dir == DHCP_TX) || (context->is_uplink && dir == DHCP_RX)) {
glob_counters[dir].offer++;
}
break;
case 3:
context->counters[dir].request++;
if ((context->is_uplink && dir == DHCP_TX) || (!context->is_uplink && dir == DHCP_RX)) {
glob_counters[dir].request++;
}
break;
case 5:
context->counters[dir].ack++;
if ((!context->is_uplink && dir == DHCP_TX) || (context->is_uplink && dir == DHCP_RX)) {
glob_counters[dir].ack++;
}
break;
case 4: // type: Decline
case 6 ... 8:
// type: NAK, Release, Inform
break;
default:
syslog(LOG_WARNING, "handle_dhcp_option_53(%s): Unknown DHCP option 53 type %d", context->intf, dhcp_option[2]);
break;
}
}
/**
* @code read_callback(fd, event, arg);
*
* @brief callback for libevent which is called every time out in order to read queued packet capture
*
* @param fd socket to read from
* @param event libevent triggered event
* @param arg user provided argument for callback (interface context)
* @param packet pointer to packet data
*
* @return none
*/
static void read_callback(int fd, short event, void *arg)
{
dhcp_device_context_t *context = (dhcp_device_context_t*) arg;
ssize_t buffer_sz;
while ((event == EV_READ) &&
((buffer_sz = recv(fd, context->buffer, context->snaplen, MSG_DONTWAIT)) > 0)) {
struct ether_header *ethhdr = (struct ether_header*) context->buffer;
struct udphdr *udp = (struct udphdr*) (context->buffer + UDP_START_OFFSET);
int dhcp_option_offset = DHCP_START_OFFSET + DHCP_OPTIONS_HEADER_SIZE;
if ((buffer_sz > UDP_START_OFFSET + sizeof(struct udphdr) + DHCP_OPTIONS_HEADER_SIZE) &&
(ntohs(udp->len) > DHCP_OPTIONS_HEADER_SIZE)) {
int dhcp_sz = ntohs(udp->len) < buffer_sz - UDP_START_OFFSET - sizeof(struct udphdr) ?
ntohs(udp->len) : buffer_sz - UDP_START_OFFSET - sizeof(struct udphdr);
int dhcp_option_sz = dhcp_sz - DHCP_OPTIONS_HEADER_SIZE;
const u_char *dhcp_option = context->buffer + dhcp_option_offset;
dhcp_packet_direction_t dir = (ethhdr->ether_shost[0] == context->mac[0] &&
ethhdr->ether_shost[1] == context->mac[1] &&
ethhdr->ether_shost[2] == context->mac[2] &&
ethhdr->ether_shost[3] == context->mac[3] &&
ethhdr->ether_shost[4] == context->mac[4] &&
ethhdr->ether_shost[5] == context->mac[5]) ?
DHCP_TX : DHCP_RX;
int offset = 0;
int stop_dhcp_processing = 0;
while ((offset < (dhcp_option_sz + 1)) && dhcp_option[offset] != 255) {
switch (dhcp_option[offset])
{
case 53:
if (offset < (dhcp_option_sz + 2)) {
handle_dhcp_option_53(context, &dhcp_option[offset], dir);
}
stop_dhcp_processing = 1; // break while loop since we are only interested in Option 53
break;
default:
break;
}
if (stop_dhcp_processing == 1) {
break;
}
if (dhcp_option[offset] == 0) { // DHCP Option Padding
offset++;
} else {
offset += dhcp_option[offset + 1] + 2;
}
}
} else {
syslog(LOG_WARNING, "read_callback(%s): read length (%ld) is too small to capture DHCP options",
context->intf, buffer_sz);
}
}
}
/**
* @code dhcp_device_validate(counters, counters_snapshot);
*
* @brief validate current interface counters by comparing aggregate counter with snapshot counters.
*
* @param counters recent interface counter
* @param counters_snapshot snapshot counters
*
* @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE
*/
static dhcp_mon_status_t dhcp_device_validate(dhcp_device_counters_t *counters,
dhcp_device_counters_t *counters_snapshot)
{
dhcp_mon_status_t rv = DHCP_MON_STATUS_HEALTHY;
if ((counters[DHCP_RX].discover == counters_snapshot[DHCP_RX].discover) &&
(counters[DHCP_RX].offer == counters_snapshot[DHCP_RX].offer ) &&
(counters[DHCP_RX].request == counters_snapshot[DHCP_RX].request ) &&
(counters[DHCP_RX].ack == counters_snapshot[DHCP_RX].ack ) ) {
rv = DHCP_MON_STATUS_INDETERMINATE;
} else {
// if we have rx DORA then we should have corresponding tx DORA (DORA being relayed)
if (((counters[DHCP_RX].discover > counters_snapshot[DHCP_RX].discover) &&
(counters[DHCP_TX].discover <= counters_snapshot[DHCP_TX].discover) ) ||
((counters[DHCP_RX].offer > counters_snapshot[DHCP_RX].offer ) &&
(counters[DHCP_TX].offer <= counters_snapshot[DHCP_TX].offer ) ) ||
((counters[DHCP_RX].request > counters_snapshot[DHCP_RX].request ) &&
(counters[DHCP_TX].request <= counters_snapshot[DHCP_TX].request ) ) ||
((counters[DHCP_RX].ack > counters_snapshot[DHCP_RX].ack ) &&
(counters[DHCP_TX].ack <= counters_snapshot[DHCP_TX].ack ) ) ) {
rv = DHCP_MON_STATUS_UNHEALTHY;
}
}
return rv;
}
/**
* @code dhcp_print_counters(counters);
*
* @brief prints DHCP counters to sylsog.
*
* @param counters interface counter
*/
static void dhcp_print_counters(dhcp_device_counters_t *counters)
{
syslog(LOG_NOTICE, "DHCP Discover rx: %lu, tx:%lu, Offer rx: %lu, tx:%lu, Request rx: %lu, tx:%lu, ACK rx: %lu, tx:%lu\n",
counters[DHCP_RX].discover, counters[DHCP_TX].discover,
counters[DHCP_RX].offer, counters[DHCP_TX].offer,
counters[DHCP_RX].request, counters[DHCP_TX].request,
counters[DHCP_RX].ack, counters[DHCP_TX].ack);
}
/**
* @code init_socket(context, intf, snaplen, base);
*
* @brief initializes socket, bind it to interface and bpf prgram, and
* associate with libevent base
*
* @param context pointer to device (interface) context
* @param intf interface name
* @param snaplen length of packet capture
* @param base libevent base
*
* @return 0 on success, otherwise for failure
*/
static int init_socket(dhcp_device_context_t *context,
const char *intf,
size_t snaplen,
struct event_base *base)
{
int rv = -1;
do {
if (snaplen < UDP_START_OFFSET + sizeof(struct udphdr) + DHCP_OPTIONS_HEADER_SIZE) {
syslog(LOG_ALERT, "init_socket(%s): snap length is too low to capture DHCP options", intf);
break;
}
context->sock = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL));
if (context->sock < 0) {
syslog(LOG_ALERT, "socket: failed to open socket with '%s'\n", strerror(errno));
break;
}
struct sockaddr_ll addr;
memset(&addr, 0, sizeof(addr));
addr.sll_ifindex = if_nametoindex(intf);
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ALL);
if (bind(context->sock, (struct sockaddr *) &addr, sizeof(addr))) {
syslog(LOG_ALERT, "bind: failed to bind to interface '%s' with '%s'\n", intf, strerror(errno));
break;
}
if (setsockopt(context->sock, SOL_SOCKET, SO_ATTACH_FILTER, &dhcp_sock_bfp, sizeof(dhcp_sock_bfp)) != 0) {
syslog(LOG_ALERT, "setsockopt: failed to attach filter with '%s'\n", strerror(errno));
break;
}
context->buffer = (uint8_t *) malloc(snaplen);
if (context->buffer == NULL) {
syslog(LOG_ALERT, "malloc: failed to allocate memory for socket buffer '%s'\n", strerror(errno));
break;
}
context->snaplen = snaplen;
struct event *ev = event_new(base, context->sock, EV_READ | EV_PERSIST, read_callback, context);
if (ev == NULL) {
syslog(LOG_ALERT, "event_new: failed to allocate memory for libevent event '%s'\n", strerror(errno));
break;
}
event_add(ev, NULL);
strncpy(context->intf, intf, sizeof(context->intf) - 1);
context->intf[sizeof(context->intf) - 1] = '\0';
rv = 0;
} while (0);
return rv;
}
/**
* @code initialize_intf_mac_and_ip_addr(context);
*
* @brief initializes device (interface) mac/ip addresses
*
* @param context pointer to device (interface) context
*
* @return 0 on success, otherwise for failure
*/
static int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context)
{
int rv = -1;
do {
int fd;
struct ifreq ifr;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
syslog(LOG_ALERT, "socket: %s", strerror(errno));
break;
}
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, context->intf, sizeof(ifr.ifr_name) - 1);
ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
// Get network address
if (ioctl(fd, SIOCGIFADDR, &ifr) == -1) {
syslog(LOG_ALERT, "ioctl: %s", explain_ioctl(fd, SIOCGIFADDR, &ifr));
break;
}
context->ip = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr;
// Get mac address
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) {
syslog(LOG_ALERT, "ioctl: %s", explain_ioctl(fd, SIOCGIFHWADDR, &ifr));
break;
}
memcpy(context->mac, ifr.ifr_hwaddr.sa_data, sizeof(context->mac));
close(fd);
rv = 0;
} while (0);
return rv;
}
/**
* @code dhcp_device_init(context, intf, snaplen, is_uplink, base);
*
* @brief initializes device (interface) that handles packet capture per interface.
*/
int dhcp_device_init(dhcp_device_context_t **context,
const char *intf,
int snaplen,
uint8_t is_uplink,
struct event_base *base)
{
int rv = -1;
dhcp_device_context_t *dev_context = NULL;
if ((context != NULL) && (strlen(intf) < sizeof(dev_context->intf))) {
dev_context = (dhcp_device_context_t *) malloc(sizeof(dhcp_device_context_t));
if (dev_context != NULL) {
if ((init_socket(dev_context, intf, snaplen, base) == 0) &&
(initialize_intf_mac_and_ip_addr(dev_context) == 0 ) ) {
dev_context->is_uplink = is_uplink;
memset(&dev_context->counters, 0, sizeof(dev_context->counters));
memset(&dev_context->counters_snapshot, 0, sizeof(dev_context->counters_snapshot));
*context = dev_context;
rv = 0;
}
}
else {
syslog(LOG_ALERT, "malloc: failed to allocated device context memory for '%s'", dev_context->intf);
}
}
return rv;
}
/**
* @code dhcp_device_shutdown(context);
*
* @brief shuts down device (interface). Also, stops packet capture on interface and cleans up any allocated memory
*/
void dhcp_device_shutdown(dhcp_device_context_t *context)
{
free(context);
}
/**
* @code dhcp_device_get_status(context);
*
* @brief collects DHCP relay status info for a given interface. If context is null, it will report aggregate
* status
*/
dhcp_mon_status_t dhcp_device_get_status(dhcp_device_context_t *context)
{
dhcp_mon_status_t rv = 0;
if (context != NULL) {
rv = dhcp_device_validate(context->counters, context->counters_snapshot);
memcpy(context->counters_snapshot, context->counters, sizeof(context->counters_snapshot));
} else {
rv = dhcp_device_validate(glob_counters, glob_counters_snapshot);
memcpy(glob_counters_snapshot, glob_counters, sizeof(glob_counters_snapshot));
}
return rv;
}
/**
* @code dhcp_device_print_status();
*
* @brief prints status counters to syslog. If context is null, it will print aggregate status
*/
void dhcp_device_print_status(dhcp_device_context_t *context)
{
if (context != NULL) {
dhcp_print_counters(context->counters);
} else {
dhcp_print_counters(glob_counters);
}
}

View File

@ -0,0 +1,113 @@
/**
* @file dhcp_device.h
*
* device (interface) module
*/
#ifndef DHCP_DEVICE_H_
#define DHCP_DEVICE_H_
#include <stdint.h>
#include <net/if.h>
#include <netinet/in.h>
#include <net/ethernet.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
/** packet direction */
typedef enum
{
DHCP_RX, /** RX DHCP packet */
DHCP_TX, /** TX DHCP packet */
DHCP_DIR_COUNT
} dhcp_packet_direction_t;
/** dhcp health status */
typedef enum
{
DHCP_MON_STATUS_HEALTHY, /** DHCP relay is healthy */
DHCP_MON_STATUS_UNHEALTHY, /** DHCP relay is unhealthy and is missing out on some packets */
DHCP_MON_STATUS_INDETERMINATE, /** DHCP relay health could not be determined */
} dhcp_mon_status_t;
/** DHCP device (interface) health counters */
typedef struct
{
uint64_t discover; /** DHCP discover packets */
uint64_t offer; /** DHCP offer packets */
uint64_t request; /** DHCP request packets */
uint64_t ack; /** DHCP ack packets */
} dhcp_device_counters_t;
/** DHCP device (interface) context */
typedef struct
{
int sock; /** Raw socket associated with this device/interface */
in_addr_t ip; /** network address of this device (interface) */
uint8_t mac[ETHER_ADDR_LEN]; /** hardware address of this device (interface) */
uint8_t is_uplink; /** north interface? */
char intf[IF_NAMESIZE]; /** device (interface) name */
uint8_t *buffer; /** buffer used to read socket data */
size_t snaplen; /** snap length or buffer size */
dhcp_device_counters_t counters[DHCP_DIR_COUNT];
/** current coutners of DORA packets */
dhcp_device_counters_t counters_snapshot[DHCP_DIR_COUNT];
/** counter snapshot */
} dhcp_device_context_t;
/**
* @code dhcp_device_init(context, intf, snaplen, timeout_ms, is_uplink, base);
*
* @brief initializes device (interface) that handles packet capture per interface.
*
* @param context(inout) pointer to device (interface) context
* @param intf interface name
* @param snaplen length of packet capture
* @param is_uplink uplink interface
* @param base pointer to libevent base
*
* @return 0 on success, otherwise for failure
*/
int dhcp_device_init(dhcp_device_context_t **context,
const char *intf,
int snaplen,
uint8_t is_uplink,
struct event_base *base);
/**
* @code dhcp_device_shutdown(context);
*
* @brief shuts down device (interface). Also, stops packet capture on interface and cleans up any allocated memory
*
* @param context Device (interface) context
*
* @return nonedhcp_device_shutdown
*/
void dhcp_device_shutdown(dhcp_device_context_t *context);
/**
* @code dhcp_device_get_status(context);
*
* @brief collects DHCP relay status info for a given interface. If context is null, it will report aggregate
* status
*
* @param context Device (interface) context
*
* @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE
*/
dhcp_mon_status_t dhcp_device_get_status(dhcp_device_context_t *context);
/**
* @code dhcp_device_print_status();
*
* @brief prints status counters to syslog. If context is null, it will print aggregate status
*
* @return none
*/
void dhcp_device_print_status(dhcp_device_context_t *context);
#endif /* DHCP_DEVICE_H_ */

View File

@ -0,0 +1,149 @@
/**
* @file dhcp_devman.c
*
* Device (interface) manager
*/
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/queue.h>
#include <stdlib.h>
#include "dhcp_devman.h"
/** struct for interface information */
struct intf
{
const char *name; /** interface name */
uint8_t is_uplink; /** is uplink (north) interface */
dhcp_device_context_t *dev_context; /** device (interface_ context */
LIST_ENTRY(intf) entry; /** list link/pointers entries */
};
/** intfs list of interfaces */
static LIST_HEAD(intf_list, intf) intfs;
/** dhcp_num_south_intf number of south interfaces */
static uint32_t dhcp_num_south_intf = 0;
/** dhcp_num_north_intf number of north interfaces */
static uint32_t dhcp_num_north_intf = 0;
/**
* @code dhcp_devman_init();
*
* initializes device (interface) manager that keeps track of interfaces and assert that there is one south
* interface and as many north interfaces
*/
void dhcp_devman_init()
{
LIST_INIT(&intfs);
}
/**
* @code dhcp_devman_shutdown();
*
* shuts down device (interface) manager. Also, stops packet capture on interface and cleans up any allocated
* memory
*/
void dhcp_devman_shutdown()
{
struct intf *int_ptr, *prev_intf = NULL;
LIST_FOREACH(int_ptr, &intfs, entry) {
dhcp_device_shutdown(int_ptr->dev_context);
if (prev_intf) {
LIST_REMOVE(prev_intf, entry);
free(prev_intf);
prev_intf = int_ptr;
}
}
if (prev_intf) {
LIST_REMOVE(prev_intf, entry);
free(prev_intf);
}
}
/**
* @code dhcp_devman_add_intf(name, uplink);
*
* @brief adds interface to the device manager.
*/
int dhcp_devman_add_intf(const char *name, uint8_t is_uplink)
{
int rv = -1;
struct intf *dev = malloc(sizeof(struct intf));
if (dev != NULL) {
dev->name = name;
dev->is_uplink = is_uplink;
if (is_uplink) {
dhcp_num_north_intf++;
} else {
dhcp_num_south_intf++;
assert(dhcp_num_south_intf <= 1);
}
LIST_INSERT_HEAD(&intfs, dev, entry);
rv = 0;
}
else {
syslog(LOG_ALERT, "malloc: failed to allocate memory for intf '%s'\n", name);
}
return rv;
}
/**
* @code dhcp_devman_start_capture(snaplen, base);
*
* @brief start packet capture on the devman interface list
*/
int dhcp_devman_start_capture(int snaplen, struct event_base *base)
{
int rv = -1;
struct intf *int_ptr;
if ((dhcp_num_south_intf == 1) && (dhcp_num_north_intf >= 1)) {
LIST_FOREACH(int_ptr, &intfs, entry) {
rv = dhcp_device_init(&int_ptr->dev_context, int_ptr->name, snaplen, int_ptr->is_uplink, base);
if (rv == 0) {
syslog(LOG_INFO,
"Capturing DHCP packets on interface %s, ip: 0x%08x, mac [%02x:%02x:%02x:%02x:%02x:%02x] \n",
int_ptr->name, int_ptr->dev_context->ip, int_ptr->dev_context->mac[0],
int_ptr->dev_context->mac[1], int_ptr->dev_context->mac[2], int_ptr->dev_context->mac[3],
int_ptr->dev_context->mac[4], int_ptr->dev_context->mac[5]);
}
else {
break;
}
}
}
else {
syslog(LOG_ERR, "Invalid number of interfaces, downlink/south %d, uplink/north %d\n",
dhcp_num_south_intf, dhcp_num_north_intf);
}
return rv;
}
/**
* @code dhcp_devman_get_status();
*
* @brief collects DHCP relay status info.
*/
dhcp_mon_status_t dhcp_devman_get_status()
{
return dhcp_device_get_status(NULL);
}
/**
* @code dhcp_devman_print_status();
*
* @brief prints status counters to syslog
*/
void dhcp_devman_print_status()
{
dhcp_device_print_status(NULL);
}

View File

@ -0,0 +1,76 @@
/**
* @file dhcp_devman.h
*
* Device (interface) manager
*/
#ifndef DHCP_DEVMAN_H_
#define DHCP_DEVMAN_H_
#include <stdint.h>
#include "dhcp_device.h"
/**
* @code dhcp_devman_init();
*
* @brief initializes device (interface) manager that keeps track of interfaces and assert that there is one south
* interface and as many north interfaces
*
* @return none
*/
void dhcp_devman_init();
/**
* @code dhcp_devman_shutdown();
*
* @brief shuts down device (interface) manager. Also, stops packet capture on interface and cleans up any allocated
* memory
*
* @return none
*/
void dhcp_devman_shutdown();
/**
* @code dhcp_devman_add_intf(name, uplink);
*
* @brief adds interface to the device manager.
*
* @param name interface name
* @param is_uplink true for uplink (north) interface
*
* @return 0 on success, nonzero otherwise
*/
int dhcp_devman_add_intf(const char *name, uint8_t is_uplink);
/**
* @code dhcp_devman_start_capture(snaplen, timeout_ms);
*
* @brief start packet capture on the devman interface list
*
* @param snaplen packet capture snap length
* @param base libevent base
*
* @return 0 on success, nonzero otherwise
*/
int dhcp_devman_start_capture(int snaplen, struct event_base *base);
/**
* @code dhcp_devman_get_status();
*
* @brief collects DHCP relay status info.
*
* @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE
*/
dhcp_mon_status_t dhcp_devman_get_status();
/**
* @code dhcp_devman_print_status();
*
* @brief prints status counters to syslog
*
* @return none
*/
void dhcp_devman_print_status();
#endif /* DHCP_DEVMAN_H_ */

199
src/dhcpmon/src/dhcp_mon.c Normal file
View File

@ -0,0 +1,199 @@
/**
* @file dhcp_mon.c
*
* @brief dhcp relay monitor module
*/
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <syslog.h>
#include <assert.h>
#include "dhcp_mon.h"
#include "dhcp_devman.h"
/** window_interval_sec monitoring window for dhcp relay health checks */
static int window_interval_sec = 12;
/** dhcp_unhealthy_max_count max count of consecutive unhealthy statuses before reporting to syslog */
static int dhcp_unhealthy_max_count = 10;
/** libevent base struct */
static struct event_base *base;
/** libevent timeout event struct */
static struct event *ev_timeout = NULL;
/** libevent SIGINT signal event struct */
static struct event *ev_sigint;
/** libevent SIGTERM signal event struct */
static struct event *ev_sigterm;
/**
* @code signal_callback(fd, event, arg);
*
* @brief signal handler for dhcpmon. It will initiate shutdown when signal is caught
*
* @param fd libevent socket
* @param event event triggered
* @param arg pointer user provided context (libevent base)
*
* @return none
*/
static void signal_callback(evutil_socket_t fd, short event, void *arg)
{
syslog(LOG_ALERT, "Received signal %d\n", event);
dhcp_devman_print_status();
dhcp_mon_stop();
}
/**
* @code timeout_callback(fd, event, arg);
*
* @brief periodic timer call back
*
* @param fd libevent socket
* @param event event triggered
* @param arg pointer user provided context (libevent base)
*
* @return none
*/
static void timeout_callback(evutil_socket_t fd, short event, void *arg)
{
static int count = 0;
dhcp_mon_status_t dhcp_mon_status = dhcp_devman_get_status();
switch (dhcp_mon_status)
{
case DHCP_MON_STATUS_UNHEALTHY:
if (++count > dhcp_unhealthy_max_count) {
syslog(LOG_ALERT, "DHCP Relay is not healthy after %d health checks\n", count);
}
break;
case DHCP_MON_STATUS_HEALTHY:
if (count > 0) {
count = 0;
}
break;
case DHCP_MON_STATUS_INDETERMINATE:
break;
default:
syslog(LOG_ERR, "DHCP Relay returned unknown status %d\n", dhcp_mon_status);
break;
}
}
/**
* @code dhcp_mon_init(window_sec, max_count);
*
* initializes event base and periodic timer event that continuously collects dhcp relay health status every window_sec
* seconds. It also writes to syslog when dhcp relay has been unhealthy for consecutive max_count checks.
*
*/
int dhcp_mon_init(int window_sec, int max_count)
{
int rv = -1;
do {
window_interval_sec = window_sec;
dhcp_unhealthy_max_count = max_count;
base = event_base_new();
if (base == NULL) {
syslog(LOG_ERR, "Could not initialize libevent!\n");
break;
}
ev_sigint = evsignal_new(base, SIGINT, signal_callback, base);
if (ev_sigint == NULL) {
syslog(LOG_ERR, "Could not create SIGINT libevent signal!\n");
break;
}
ev_sigterm = evsignal_new(base, SIGTERM, signal_callback, base);
if (ev_sigterm == NULL) {
syslog(LOG_ERR, "Could not create SIGTERM libevent signal!\n");
break;
}
ev_timeout = event_new(base, -1, EV_PERSIST, timeout_callback, base);
if (ev_timeout == NULL) {
syslog(LOG_ERR, "Could not create libevent timer!\n");
break;
}
rv = 0;
} while (0);
return rv;
}
/**
* @code dhcp_mon_shutdown();
*
* @brief shuts down libevent loop
*/
void dhcp_mon_shutdown()
{
event_del(ev_timeout);
event_del(ev_sigint);
event_del(ev_sigterm);
event_free(ev_timeout);
event_free(ev_sigint);
event_free(ev_sigterm);
event_base_free(base);
}
/**
* @code dhcp_mon_start(snaplen);
*
* @brief start monitoring DHCP Relay
*/
int dhcp_mon_start(int snaplen)
{
int rv = -1;
do
{
if (dhcp_devman_start_capture(snaplen, base) != 0) {
break;
}
if (evsignal_add(ev_sigint, NULL) != 0) {
syslog(LOG_ERR, "Could not add SIGINT libevent signal!\n");
break;
}
if (evsignal_add(ev_sigterm, NULL) != 0) {
syslog(LOG_ERR, "Could not add SIGTERM libevent signal!\n");
break;
}
struct timeval event_time = {.tv_sec = window_interval_sec, .tv_usec = 0};
if (evtimer_add(ev_timeout, &event_time) != 0) {
syslog(LOG_ERR, "Could not add event timer to libevent!\n");
break;
}
if (event_base_dispatch(base) != 0) {
syslog(LOG_ERR, "Could not start libevent dispatching loop!\n");
break;
}
rv = 0;
} while (0);
return rv;
}
/**
* @code dhcp_mon_stop();
*
* @brief stop monitoring DHCP Relay
*/
void dhcp_mon_stop()
{
event_base_loopexit(base, NULL);
}

View File

@ -0,0 +1,54 @@
/**
* @file dhcp_mon.h
*
* @brief dhcp relay monitor module
*
*/
#ifndef DHCP_MON_H_
#define DHCP_MON_H_
/**
* @code dhcp_mon_init(window_ssec, max_count);
*
* @brief initializes event base and periodic timer event that continuously collects dhcp relay health status every
* window_sec seconds. It also writes to syslog when dhcp relay has been unhealthy for consecutive max_count
* checks.
*
* @param window_sec time interval between health checks
* @param max_count max count of consecutive unhealthy statuses before reporting to syslog
*
* @return 0 upon success, otherwise upon failure
*/
int dhcp_mon_init(int window_sec, int max_count);
/**
* @code dhcp_mon_shutdown();
*
* @brief shuts down libevent loop
*
* @return none
*/
void dhcp_mon_shutdown();
/**
* @code dhcp_mon_start(snaplen);
*
* @brief start monitoring DHCP Relay
*
* @param snaplen packet capture length
*
* @return 0 upon success, otherwise upon failure
*/
int dhcp_mon_start(int snaplen);
/**
* @code dhcp_mon_stop();
*
* @brief stop monitoring DHCP Relay
*
* @return none
*/
void dhcp_mon_stop();
#endif /* DHCP_MON_H_ */

174
src/dhcpmon/src/main.c Normal file
View File

@ -0,0 +1,174 @@
/**
* @file main.c
*
* @brief: Main entry point for dhcpmon utility.
*
*/
#include <err.h>
#include <errno.h>
#include <libgen.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "dhcp_mon.h"
#include "dhcp_devman.h"
/** dhcpmon_default_snaplen: default snap length of packet being captured */
static const uint32_t dhcpmon_default_snaplen = 65535;
/** dhcpmon_default_health_check_window: default value for a time window, during which DHCP DORA packet counts are being
* collected */
static const uint32_t dhcpmon_default_health_check_window = 12;
/** dhcpmon_default_unhealthy_max_count: default max consecutive unhealthy status reported before reporting an issue
* with DHCP relay */
static const uint32_t dhcpmon_default_unhealthy_max_count = 10;
/**
* @code usage(prog);
*
* @brief prints help message about how to use dhcpmon utility
*
* @param prog program name
*
* @return none
*/
static void usage(const char *prog)
{
printf("Usage: %s -id <south interface> {-iu <north interface>}+ [-w <snapshot window in sec>]"
"[-c <unhealthy status count>] [-s <snap length>] [-d]\n", prog);
printf("where\n");
printf("\tsouth interface: is a vlan interface,\n");
printf("\tnorth interface: is a TOR-T1 interface,\n");
printf("\tsnapshot window: during which DHCP counters are gathered and DHCP status is validated (default %d),\n",
dhcpmon_default_health_check_window);
printf("\tunhealthy status count: count of consecutive unhealthy status before writing an alert to syslog "
"(default %d),\n",
dhcpmon_default_unhealthy_max_count);
printf("\tsnap length: snap length of packet capture (default %d),\n", dhcpmon_default_snaplen);
printf("\t-d: daemonize %s.\n", prog);
exit(EXIT_SUCCESS);
}
/**
* @code dhcpmon_daemonize();
*
* @brief make this utility run as a daemon.
*
* @return none
*/
static void dhcpmon_daemonize()
{
pid_t pid, sid;
pid = fork();
if (pid < 0) {
syslog(LOG_ALERT, "fork: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
// this is the daemon running now
umask(0);
// Create a new SID for the child process
sid = setsid();
if (sid < 0) {
syslog(LOG_ALERT, "setsid: %s", strerror(errno));
exit(EXIT_FAILURE);
}
// Change the current working directory
if ((chdir("/")) < 0) {
syslog(LOG_ALERT, "chdir: %s", strerror(errno));
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
/**
* @code main(argc, argv);
*
* @brief main entry point of dhcpmon utility
*
* @return int 0 on success, otherwise on failure
*/
int main(int argc, char **argv)
{
int rv = EXIT_FAILURE;
int i;
int window_interval = dhcpmon_default_health_check_window;
int max_unhealthy_count = dhcpmon_default_unhealthy_max_count;
uint32_t snaplen = dhcpmon_default_snaplen;
int make_daemon = 0;
setlogmask(LOG_UPTO(LOG_INFO));
openlog(basename(argv[0]), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON);
dhcp_devman_init();
for (i = 1; i < argc;) {
if ((argv[i] == NULL) || (argv[i][0] != '-')) {
break;
}
switch (argv[i][1])
{
case 'h':
usage(basename(argv[0]));
break;
case 'i':
if (dhcp_devman_add_intf(argv[i + 1], argv[i][2] == 'u') != 0) {
usage(basename(argv[0]));
}
i += 2;
break;
case 'd':
make_daemon = 1;
i++;
break;
case 's':
snaplen = atoi(argv[i + 1]);
i += 2;
break;
case 'w':
window_interval = atoi(argv[i + 1]);
i += 2;
break;
case 'c':
max_unhealthy_count = atoi(argv[i + 1]);
i += 2;
break;
default:
fprintf(stderr, "%s: %c: Unknown option\n", basename(argv[0]), argv[i][1]);
usage(basename(argv[0]));
}
}
if (make_daemon) {
dhcpmon_daemonize();
}
if ((dhcp_mon_init(window_interval, max_unhealthy_count) == 0) &&
(dhcp_mon_start(snaplen) == 0)) {
rv = EXIT_SUCCESS;
dhcp_mon_shutdown();
}
dhcp_devman_shutdown();
closelog();
return rv;
}

29
src/dhcpmon/src/subdir.mk Normal file
View File

@ -0,0 +1,29 @@
# Add inputs and outputs from these tool invocations to the build variables
CC := gcc
C_SRCS += \
../src/dhcp_device.c \
../src/dhcp_devman.c \
../src/dhcp_mon.c \
../src/main.c
OBJS += \
./src/dhcp_device.o \
./src/dhcp_devman.o \
./src/dhcp_mon.o \
./src/main.o
C_DEPS += \
./src/dhcp_device.d \
./src/dhcp_devman.d \
./src/dhcp_mon.d \
./src/main.d
# Each subdirectory must supply rules for building sources it contributes
src/%.o: ../src/%.c
@echo 'Building file: $<'
@echo 'Invoking: GCC C Compiler'
$(CC) -O3 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@)" -o "$@" "$<"
@echo 'Finished building: $<'
@echo ' '

View File

@ -37,3 +37,16 @@ stdout_logfile=syslog
stderr_logfile=syslog
[group:dhcpmon]
programs=dhcpmon-Vlan1000
[program:dhcpmon-Vlan1000]
command=/usr/sbin/dhcpmon -id Vlan1000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04
priority=4
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog