diff --git a/dockers/docker-dhcp-relay/Dockerfile.j2 b/dockers/docker-dhcp-relay/Dockerfile.j2 index 0a760d301f3..d72a09113f5 100644 --- a/dockers/docker-dhcp-relay/Dockerfile.j2 +++ b/dockers/docker-dhcp-relay/Dockerfile.j2 @@ -27,5 +27,7 @@ RUN rm -rf /debs COPY ["docker_init.sh", "start.sh", "/usr/bin/"] COPY ["docker-dhcp-relay.supervisord.conf.j2", "wait_for_intf.sh.j2", "/usr/share/sonic/templates/"] +COPY ["dhcp-relay.programs.j2", "dhcpv4-relay.agents.j2", "dhcpv6-relay.agents.j2", "dhcpv6-relay.monitors.j2", "/usr/share/sonic/templates/"] +COPY ["critical_processes", "/etc/supervisor"] ENTRYPOINT ["/usr/bin/docker_init.sh"] diff --git a/dockers/docker-dhcp-relay/critical_processes b/dockers/docker-dhcp-relay/critical_processes new file mode 100644 index 00000000000..43bf6af2794 --- /dev/null +++ b/dockers/docker-dhcp-relay/critical_processes @@ -0,0 +1 @@ +group:dhcp-relay diff --git a/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 b/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 new file mode 100644 index 00000000000..8b2207c81ec --- /dev/null +++ b/dockers/docker-dhcp-relay/dhcp-relay.programs.j2 @@ -0,0 +1,16 @@ +[group:dhcp-relay] +programs= +{%- set add_preceding_comma = { 'flag': False } %} +{% for vlan_name in vlan_list %} +{% if VLAN and vlan_name in VLAN and VLAN[vlan_name]['dhcp_servers'] %} +{% if add_preceding_comma.flag %},{% endif %} +{% set _dummy = add_preceding_comma.update({'flag': True}) %} +isc-dhcpv4-relay-{{ vlan_name }} +{%- endif %} +{# Append DHCPv6 agents #} +{% if VLAN and vlan_name in VLAN and 'dhcpv6_servers' in VLAN[vlan_name] %} +{% if add_preceding_comma.flag %},{% endif %} +{% set _dummy = add_preceding_comma.update({'flag': True}) %} +dhcp6relay +{%- endif %} +{% endfor %} diff --git a/dockers/docker-dhcp-relay/dhcpv4-relay.agents.j2 b/dockers/docker-dhcp-relay/dhcpv4-relay.agents.j2 new file mode 100644 index 00000000000..133a3415bd9 --- /dev/null +++ b/dockers/docker-dhcp-relay/dhcpv4-relay.agents.j2 @@ -0,0 +1,35 @@ +{# Append DHCPv4 agents #} +{# Create a program entry for each DHCP relay agent instance #} +{% for vlan_name in vlan_list %} +{% if VLAN and vlan_name in VLAN and 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:isc-dhcpv4-relay-{{ vlan_name }}] +{# We treat this VLAN as a downstream interface (-id), as we only want to listen for requests #} +command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -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 %} +{% if prefix | ipv4 and name != vlan_name %} -iu {{ name }}{% endif -%} +{% endfor %} +{% for (name, prefix) in INTERFACE %} +{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} +{% endfor %} +{% for (name, prefix) in PORTCHANNEL_INTERFACE %} +{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} +{% endfor %} +{% for dhcp_server in VLAN[vlan_name]['dhcp_servers'] %} {{ dhcp_server }}{% endfor %} + +priority=3 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog + +{% endif %} +{% endif %} +{% endfor %} diff --git a/dockers/docker-dhcp-relay/dhcpv6-relay.agents.j2 b/dockers/docker-dhcp-relay/dhcpv6-relay.agents.j2 new file mode 100644 index 00000000000..511f6e77c30 --- /dev/null +++ b/dockers/docker-dhcp-relay/dhcpv6-relay.agents.j2 @@ -0,0 +1,23 @@ +{# Append DHCPv6 agents #} +{# Create a program entry for each DHCPv6 relay agent instance #} +{% for vlan_name in vlan_list %} +{% if VLAN and vlan_name in VLAN and 'dhcpv6_servers' in VLAN[vlan_name] %} +{% for dhcpv6_server in VLAN[vlan_name]['dhcpv6_servers'] %} +{% if dhcpv6_server | ipv6 %} +{% set _dummy = relay_for_ipv6.update({'flag': True}) %} +{% endif %} +{% endfor %} +{% if relay_for_ipv6.flag %} +{% set _dummy = relay_for_ipv6.update({'flag': False}) %} +[program:dhcp6relay] +command=/usr/sbin/dhcp6relay + +priority=3 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog + +{% endif %} +{% endif %} +{% endfor %} diff --git a/dockers/docker-dhcp-relay/dhcpv6-relay.monitors.j2 b/dockers/docker-dhcp-relay/dhcpv6-relay.monitors.j2 new file mode 100644 index 00000000000..b374215aeb5 --- /dev/null +++ b/dockers/docker-dhcp-relay/dhcpv6-relay.monitors.j2 @@ -0,0 +1,77 @@ +[group:dhcpmon] +programs= +{%- set add_preceding_comma = { 'flag': False } %} +{% set monitor_instance = { 'flag': False } %} +{% for vlan_name in vlan_list %} +{% if VLAN and vlan_name in VLAN and VLAN[vlan_name]['dhcp_servers'] %} +{% set _dummy = monitor_instance.update({'flag': True}) %} +{%- endif %} +{% if VLAN and vlan_name in VLAN and 'dhcpv6_servers' in VLAN[vlan_name] %} +{% set _dummy = monitor_instance.update({'flag': True}) %} +{% endif %} +{% if monitor_instance.flag %} +{% if add_preceding_comma.flag %},{% endif %} +{% set _dummy = add_preceding_comma.update({'flag': True}) %} +dhcpmon-{{ vlan_name }} +{%- set _dummy = monitor_instance.update({'flag': False}) %} +{%- endif %} +{% endfor %} + + +{# Create a program entry for each DHCP MONitor instance #} +{% set relay_for_ipv4 = { 'flag': False } %} +{% set relay_for_ipv6 = { 'flag': False } %} +{% for vlan_name in vlan_list %} +{# Check DHCPv4 agents #} +{% if VLAN and vlan_name in VLAN and 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 %} +{% endif %} +{# Check DHCPv6 agents #} +{% if VLAN and vlan_name in VLAN and 'dhcpv6_servers' in VLAN[vlan_name] %} +{% for dhcpv6_server in VLAN[vlan_name]['dhcpv6_servers'] %} +{% if dhcpv6_server | ipv6 %} +{% set _dummy = relay_for_ipv6.update({'flag': True}) %} +{% endif %} +{% endfor %} +{% endif %} +{% if relay_for_ipv4.flag %} +{% set _dummy = relay_for_ipv4.update({'flag': False}) %} +{% if relay_for_ipv6.flag %} +{% set _dummy = relay_for_ipv6.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 %} +{% if prefix | ipv4 and name != vlan_name %} -iu {{ name }}{% endif -%} +{% endfor %} +{% for (name, prefix) in INTERFACE %} +{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} +{% endfor %} +{% for (name, prefix) in PORTCHANNEL_INTERFACE %} +{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} +{% endfor %} +{% if MGMT_INTERFACE %} +{% for (name, prefix) in MGMT_INTERFACE %} +{% if prefix | ipv4 %} -im {{ name }}{% endif -%} +{% endfor %} +{% endif %} +{% if relay_for_ipv4.flag %} -4{% endif %} +{% if relay_for_ipv6.flag %} -6{% endif %} + +priority=4 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog + + +{% set _dummy = relay_for_ipv4.update({'flag': False}) %} +{% set _dummy = relay_for_ipv6.update({'flag': False}) %} +{% endif %} +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 b/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 index 08aad04a8b1..8dc0631c0ad 100644 --- a/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 +++ b/dockers/docker-dhcp-relay/docker-dhcp-relay.supervisord.conf.j2 @@ -21,6 +21,9 @@ stderr_logfile=syslog {# If our configuration has VLANs... #} {% if VLAN_INTERFACE %} +{# Count how many VLANs require a DHCP relay agent... #} +{% set ipv4_num_relays = { 'count': 0 } %} +{% set ipv6_num_relays = { 'count': 0 } %} {% set d = namespace(vlan_list=[]) %} {% for (name, prefix) in VLAN_INTERFACE %} {% if name not in d.vlan_list %} @@ -32,101 +35,22 @@ stderr_logfile=syslog {% set num_relays = { 'count': 0 } %} {% for vlan_name in vlan_list %} {% if VLAN and vlan_name in VLAN and VLAN[vlan_name]['dhcp_servers'] %} -{% set _dummy = num_relays.update({'count': num_relays.count + 1}) %} +{% set _dummy = ipv4_num_relays.update({'count': ipv4_num_relays.count + 1}) %} {% endif %} -{% endfor %} -{# If one or more of the VLANs require a DHCP relay agent... #} -{% if num_relays.count > 0 %} -[group:isc-dhcp-relay] -programs= -{%- set add_preceding_comma = { 'flag': False } %} -{% for vlan_name in vlan_list %} -{% if VLAN and vlan_name in VLAN and VLAN[vlan_name]['dhcp_servers'] %} -{% if add_preceding_comma.flag %},{% endif %} -{% set _dummy = add_preceding_comma.update({'flag': True}) %} -isc-dhcp-relay-{{ vlan_name }} -{%- endif %} -{% endfor %} - - -{# Create a program entry for each DHCP relay agent instance #} -{% for vlan_name in vlan_list %} -{% if VLAN and vlan_name in VLAN and VLAN[vlan_name]['dhcp_servers'] %} -[program:isc-dhcp-relay-{{ vlan_name }}] -{# We treat this VLAN as a downstream interface (-id), as we only want to listen for requests #} -command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -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 %} -{% if prefix | ipv4 and name != vlan_name %} -iu {{ name }}{% endif -%} -{% endfor %} -{% for (name, prefix) in INTERFACE %} -{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} -{% endfor %} -{% for (name, prefix) in PORTCHANNEL_INTERFACE %} -{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} -{% endfor %} -{% for dhcp_server in VLAN[vlan_name]['dhcp_servers'] %} {{ dhcp_server }}{% endfor %} - -priority=3 -autostart=false -autorestart=false -stdout_logfile=syslog -stderr_logfile=syslog - +{% if VLAN and vlan_name in VLAN and 'dhcpv6_servers' in VLAN[vlan_name] %} +{% set _dummy = ipv6_num_relays.update({'count': ipv6_num_relays.count + 1}) %} {% endif %} {% endfor %} +{# If one or more of the VLANs require a DHCP relay agent... #} +{% if ipv4_num_relays.count > 0 or ipv6_num_relays.count > 0 %} +{% include 'dhcp-relay.programs.j2' %} -[group:dhcpmon] -programs= -{%- set add_preceding_comma = { 'flag': False } %} -{% for vlan_name in vlan_list %} -{% if VLAN and vlan_name in VLAN and 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_list %} -{% if VLAN and vlan_name in VLAN and 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 %} -{% if prefix | ipv4 and name != vlan_name %} -iu {{ name }}{% endif -%} -{% endfor %} -{% for (name, prefix) in INTERFACE %} -{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} -{% endfor %} -{% for (name, prefix) in PORTCHANNEL_INTERFACE %} -{% if prefix | ipv4 %} -iu {{ name }}{% endif -%} -{% endfor %} -{% if MGMT_INTERFACE %} -{% for (name, prefix) in MGMT_INTERFACE %} -{% if prefix | ipv4 %} -im {{ name }}{% endif -%} -{% endfor %} -{% endif %} +{% set relay_for_ipv6 = { 'flag': False } %} -priority=4 -autostart=false -autorestart=false -stdout_logfile=syslog -stderr_logfile=syslog +{% include 'dhcpv4-relay.agents.j2' %} +{% include 'dhcpv6-relay.agents.j2' %} +{% include 'dhcpv6-relay.monitors.j2' %} {% endif %} -{% endif %} -{% endfor %} - -{% endif %} -{% endif %} +{% endif %} \ No newline at end of file diff --git a/dockers/docker-dhcp-relay/start.sh b/dockers/docker-dhcp-relay/start.sh index dbbf3225108..7a1b659bf2b 100755 --- a/dockers/docker-dhcp-relay/start.sh +++ b/dockers/docker-dhcp-relay/start.sh @@ -6,8 +6,8 @@ rm -f /var/run/rsyslogd.pid # Start rsyslog supervisorctl start rsyslogd -# If our supervisor config has entries in the "isc-dhcp-relay" group... -if [ $(supervisorctl status | grep -c "^isc-dhcp-relay:") -gt 0 ]; then +# If our supervisor config has entries in the "dhcp-relay" group... +if [ $(supervisorctl status | grep -c "^dhcp-relay:") -gt 0 ]; then # Wait for all interfaces to come up and be assigned IPv4 addresses before # starting the DHCP relay agent(s). If an interface the relay should listen # on is down, the relay agent will not start. If an interface the relay @@ -17,7 +17,7 @@ if [ $(supervisorctl status | grep -c "^isc-dhcp-relay:") -gt 0 ]; then /usr/bin/wait_for_intf.sh # Start all DHCP relay agent(s) - supervisorctl start isc-dhcp-relay:* + supervisorctl start dhcp-relay:* fi # If our supervisor config has entries in the "dhcpmon" group... diff --git a/dockers/docker-dhcp-relay/wait_for_intf.sh.j2 b/dockers/docker-dhcp-relay/wait_for_intf.sh.j2 index f697b2432df..7c03dcb8cc8 100644 --- a/dockers/docker-dhcp-relay/wait_for_intf.sh.j2 +++ b/dockers/docker-dhcp-relay/wait_for_intf.sh.j2 @@ -40,3 +40,8 @@ wait_until_iface_ready {{ name }} {{ prefix }} wait_until_iface_ready {{ name }} {{ prefix }} {% endif %} {% endfor %} + +# Wait 10 seconds for the rest of interfaces to get added/populated. +# dhcrelay listens on each of the interfaces (in addition to the port +# channels and vlan interfaces) +sleep 10 diff --git a/rules/dhcp6relay.mk b/rules/dhcp6relay.mk new file mode 100644 index 00000000000..5a0f9d9b819 --- /dev/null +++ b/rules/dhcp6relay.mk @@ -0,0 +1,9 @@ +# SONiC DHCPV6 RELAY Package + +SONIC_DHCP6RELAY_VERSION = 1.0.0-0 +SONIC_DHCP6RELAY_PKG_NAME = dhcp6relay + +SONIC_DHCP6RELAY = sonic-$(SONIC_DHCP6RELAY_PKG_NAME)_$(SONIC_DHCP6RELAY_VERSION)_amd64.deb +$(SONIC_DHCP6RELAY)_DEPENDS = $(LIBSWSSCOMMON) $(LIBHIREDIS) $(LIBSWSSCOMMON_DEV) $(LIBHIREDIS_DEV) +$(SONIC_DHCP6RELAY)_SRC_PATH = $(SRC_PATH)/$(SONIC_DHCP6RELAY_PKG_NAME) +SONIC_DPKG_DEBS += $(SONIC_DHCP6RELAY) diff --git a/rules/docker-dhcp-relay.mk b/rules/docker-dhcp-relay.mk index 31c0529d03c..a96abc442ef 100644 --- a/rules/docker-dhcp-relay.mk +++ b/rules/docker-dhcp-relay.mk @@ -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 -$(DOCKER_DHCP_RELAY)_DEPENDS += $(ISC_DHCP_COMMON) $(ISC_DHCP_RELAY) $(REDIS_TOOLS) $(SONIC_DHCPMON) +$(DOCKER_DHCP_RELAY)_DEPENDS += $(ISC_DHCP_COMMON) $(ISC_DHCP_RELAY) $(REDIS_TOOLS) $(SONIC_DHCPMON) $(SONIC_DHCP6RELAY) $(LIBSWSSCOMMON) $(DOCKER_DHCP_RELAY)_DBG_DEPENDS = $($(DOCKER_CONFIG_ENGINE)_DBG_DEPENDS) $(DOCKER_DHCP_RELAY)_DBG_DEPENDS += $(ISC_DHCP_DBG) $(DOCKER_DHCP_RELAY)_DBG_IMAGE_PACKAGES = $($(DOCKER_CONFIG_ENGINE)_DBG_IMAGE_PACKAGES) diff --git a/sonic-slave/Dockerfile b/sonic-slave/Dockerfile index acb7dd8aefb..d98099e80e1 100644 --- a/sonic-slave/Dockerfile +++ b/sonic-slave/Dockerfile @@ -242,7 +242,11 @@ RUN apt-get update && apt-get install -y \ qemu-kvm \ libvirt-bin \ # For DHCP Monitor tool - libevent-dev + libevent-dev \ +# For DHCPv6 Relay + libboost-dev \ + libboost-thread1.55.0 \ + libboost-system1.55.0 # For linux build RUN apt-get -y build-dep linux diff --git a/src/dhcp6relay/.gitignore b/src/dhcp6relay/.gitignore new file mode 100644 index 00000000000..9d09ae6b3f1 --- /dev/null +++ b/src/dhcp6relay/.gitignore @@ -0,0 +1,5 @@ +debian/* +!debian/changelog +!debian/compat +!debian/control +!debian/rules diff --git a/src/dhcp6relay/Makefile b/src/dhcp6relay/Makefile new file mode 100644 index 00000000000..b6352f6ab4b --- /dev/null +++ b/src/dhcp6relay/Makefile @@ -0,0 +1,42 @@ +RM := rm -rf +DHCP6RELAY_TARGET := dhcp6relay +CP := cp +MKDIR := mkdir +CC := g++ +MV := mv +LIBS := -levent -lswsscommon -pthread -lboost_thread -lboost_system -I $(PWD)/../sonic-swss-common/common +CFLAGS = -g -Wall -std=gnu11 +PWD := $(shell pwd) + +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(C_DEPS)),) +-include $(C_DEPS) $(OBJS) +endif +endif + +-include src/subdir.mk + +all: sonic-dhcp6relay + +sonic-dhcp6relay: $(OBJS) + @echo 'Building target: $@' + @echo 'Invoking: G++ Linker' + $(CC) -o $(DHCP6RELAY_TARGET) $(OBJS) $(LIBS) + @echo 'Finished building target: $@' + @echo ' ' + +install: + $(MKDIR) -p $(DESTDIR)/usr/sbin + $(MV) $(DHCP6RELAY_TARGET) $(DESTDIR)/usr/sbin + +deinstall: + $(RM) $(DESTDIR)/usr/sbin/$(DHCP6RELAY_TARGET) + $(RM) -rf $(DESTDIR)/usr/sbin + +clean: + -$(RM) $(EXECUTABLES) $(C_DEPS) $(OBJS) $(DHCP6RELAY_TARGET) + -@echo ' ' + +.PHONY: all clean dependents + + diff --git a/src/dhcp6relay/debian/changelog b/src/dhcp6relay/debian/changelog new file mode 100644 index 00000000000..aec73a09580 --- /dev/null +++ b/src/dhcp6relay/debian/changelog @@ -0,0 +1,5 @@ +sonic-dhcp6relay (1.0.0-0) UNRELEASED; urgency=medium + + * Initial release. + + -- Kelly Yeh Fri, 15 Oct 2021 00:00:00 +1000 diff --git a/src/dhcp6relay/debian/compat b/src/dhcp6relay/debian/compat new file mode 100644 index 00000000000..ec635144f60 --- /dev/null +++ b/src/dhcp6relay/debian/compat @@ -0,0 +1 @@ +9 diff --git a/src/dhcp6relay/debian/control b/src/dhcp6relay/debian/control new file mode 100644 index 00000000000..d73759c4e07 --- /dev/null +++ b/src/dhcp6relay/debian/control @@ -0,0 +1,17 @@ +Source: sonic-dhcp6relay +Section: devel +Priority: optional +Maintainer: Kelly Yeh +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-dhcp6relay +Architecture: any +Built-Using: ${misc:Built-Using} +Depends: libevent-2.0-5, + libboost-thread1.55.0, + libboost-system1.55.0 +Description: SONiC DHCPv6 Relay diff --git a/src/dhcp6relay/debian/rules b/src/dhcp6relay/debian/rules new file mode 100755 index 00000000000..ce2eb52beb5 --- /dev/null +++ b/src/dhcp6relay/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh $@ --parallel diff --git a/src/dhcp6relay/src/configInterface.cpp b/src/dhcp6relay/src/configInterface.cpp new file mode 100644 index 00000000000..dfe6031e046 --- /dev/null +++ b/src/dhcp6relay/src/configInterface.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include "configInterface.h" + +constexpr auto DEFAULT_TIMEOUT_MSEC = 1000; + +bool pollSwssNotifcation = true; +std::shared_ptr mSwssThreadPtr; + +std::shared_ptr configDbPtr = std::make_shared (4, "localhost", 6379, 0); +swss::SubscriberStateTable ipHelpersTable(configDbPtr.get(), "DHCP_RELAY"); +swss::Select swssSelect; + +/** + * @code void initialize_swss() + * + * @brief initialize DB tables and start SWSS listening thread + * + * @return none + */ +void initialize_swss(std::vector *vlans) +{ + try { + swssSelect.addSelectable(&ipHelpersTable); + get_dhcp(vlans); + mSwssThreadPtr = std::make_shared (&handleSwssNotification, vlans); + } + catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "Failed allocate memory. Exception details: %s", e.what()); + } +} + +/** + * @code void deinitialize_swss() + * + * @brief deinitialize DB interface and join SWSS listening thread + * + * @return none + */ +void deinitialize_swss() +{ + stopSwssNotificationPoll(); + mSwssThreadPtr->interrupt(); +} + + +/** + * @code void get_dhcp(std::vector *vlans) + * + * @brief initialize and get vlan table information from DHCP_RELAY + * + * @return none + */ +void get_dhcp(std::vector *vlans) { + swss::Selectable *selectable; + int ret = swssSelect.select(&selectable, DEFAULT_TIMEOUT_MSEC); + if (ret == swss::Select::ERROR) { + syslog(LOG_WARNING, "Select: returned ERROR"); + } else if (ret == swss::Select::TIMEOUT) { + } + if (selectable == static_cast (&ipHelpersTable)) { + handleRelayNotification(ipHelpersTable, vlans); + } +} +/** + * @code void handleSwssNotification(std::vector *vlans) + * + * @brief main thread for handling SWSS notification + * + * @param context list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void handleSwssNotification(std::vector *vlans) +{ + while (pollSwssNotifcation) { + get_dhcp(vlans); + } +} + +/** + * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::vector *vlans) + * + * @brief handles DHCPv6 relay configuration change notification + * + * @param ipHelpersTable DHCP table + * @param vlans list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::vector *vlans) +{ + std::deque entries; + + ipHelpersTable.pops(entries); + processRelayNotification(entries, vlans); +} + +/** + * @code void processRelayNotification(std::deque &entries, std::vector *vlans) + * + * @brief process DHCPv6 relay servers and options configuration change notification + * + * @param entries queue of std::tuple> entries in DHCP table + * @param vlans list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void processRelayNotification(std::deque &entries, std::vector *vlans) +{ + std::vector servers; + + for (auto &entry: entries) { + std::string vlan = kfvKey(entry); + std::string operation = kfvOp(entry); + std::vector fieldValues = kfvFieldsValues(entry); + + relay_config intf; + intf.is_option_79 = true; + intf.interface = vlan; + for (auto &fieldValue: fieldValues) { + std::string f = fvField(fieldValue); + std::string v = fvValue(fieldValue); + if(f == "dhcpv6_servers") { + std::stringstream ss(v); + while (ss.good()) { + std::string substr; + getline(ss, substr, ','); + intf.servers.push_back(substr); + } + syslog(LOG_DEBUG, "key: %s, Operation: %s, f: %s, v: %s", vlan.c_str(), operation.c_str(), f.c_str(), v.c_str()); + } + if(f == "dhcpv6_option|rfc6939_support" && v == "false") { + intf.is_option_79 = false; + } + } + vlans->push_back(intf); + } +} + +/** +*@code stopSwssNotificationPoll +* +*@brief stop SWSS listening thread +* +*@return none +*/ +void stopSwssNotificationPoll() { + pollSwssNotifcation = false; +}; diff --git a/src/dhcp6relay/src/configInterface.h b/src/dhcp6relay/src/configInterface.h new file mode 100644 index 00000000000..20b0912c5ce --- /dev/null +++ b/src/dhcp6relay/src/configInterface.h @@ -0,0 +1,75 @@ +#include +#include "subscriberstatetable.h" +#include "select.h" +#include "relay.h" + +/** + * @code void initialize_swss() + * + * @brief initialize DB tables and start SWSS listening thread + * + * @return none + */ +void initialize_swss(std::vector *vlans); + +/** + * @code void deinitialize_swss() + * + * @brief deinitialize DB interface and join SWSS listening thread + * + * @return none + */ +void deinitialize_swss(); + +/** + * @code void get_dhcp(std::vector *vlans) + * + * @brief initialize and get vlan information from DHCP_RELAY + * + * @return none + */ +void get_dhcp(std::vector *vlans); + +/** + * @code void handleSwssNotification(std::vector *vlans) + * + * @brief main thread for handling SWSS notification + * + * @param vlans list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void handleSwssNotification(std::vector *vlans); + +/** + * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::vector *vlans) + * + * @brief handles DHCPv6 relay configuration change notification + * + * @param ipHelpersTable DHCP table + * @param vlans list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void handleRelayNotification(swss::SubscriberStateTable &configMuxTable, std::vector *vlans); + +/** + * @code void processRelayNotification(std::deque &entries, std::vector *vlans) + * + * @brief process DHCPv6 relay servers and options configuration change notification + * + * @param entries queue of std::tuple> entries in DHCP table + * @param context list of vlans/argument config that contains strings of server and option + * + * @return none + */ +void processRelayNotification(std::deque &entries, std::vector *vlans); + +/** +*@code stopSwssNotificationPoll +* +*@brief stop SWSS listening thread +* +*@return none +*/ +void stopSwssNotificationPoll(); diff --git a/src/dhcp6relay/src/main.cpp b/src/dhcp6relay/src/main.cpp new file mode 100644 index 00000000000..6d0fb29c050 --- /dev/null +++ b/src/dhcp6relay/src/main.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "configInterface.h" + +int main(int argc, char *argv[]) { + try { + std::vector vlans; + initialize_swss(&vlans); + loop_relay(&vlans); + } + catch (std::exception &e) + { + syslog(LOG_ERR, "An exception occurred.\n"); + return 1; + } + return 0; +} diff --git a/src/dhcp6relay/src/relay.cpp b/src/dhcp6relay/src/relay.cpp new file mode 100644 index 00000000000..ba22d4845b7 --- /dev/null +++ b/src/dhcp6relay/src/relay.cpp @@ -0,0 +1,744 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dbconnector.h" +#include "configInterface.h" +#include "table.h" +#include "redisclient.h" + + +struct event *listen_event; +struct event *server_listen_event; +struct event_base *base; +struct event *ev_sigint; +struct event *ev_sigterm; +static std::string counter_table = "DHCPv6_COUNTER_TABLE|"; +swss::DBConnector state_db(6, "localhost", 6379, 0); +swss::RedisClient m_applDbRedisClient(&state_db); + +/* DHCPv6 filter */ +/* sudo tcpdump -dd "ip6 dst ff02::1:2 && udp dst port 547" */ + +static struct sock_filter ether_relay_filter[] = { + + { 0x28, 0, 0, 0x0000000c }, + { 0x15, 0, 13, 0x000086dd }, + { 0x20, 0, 0, 0x00000026 }, + { 0x15, 0, 11, 0xff020000 }, + { 0x20, 0, 0, 0x0000002a }, + { 0x15, 0, 9, 0x00000000 }, + { 0x20, 0, 0, 0x0000002e }, + { 0x15, 0, 7, 0x00000000 }, + { 0x20, 0, 0, 0x00000032 }, + { 0x15, 0, 5, 0x00010002 }, + { 0x30, 0, 0, 0x00000014 }, + { 0x15, 0, 3, 0x00000011 }, + { 0x28, 0, 0, 0x00000038 }, + { 0x15, 0, 1, 0x00000223 }, + { 0x6, 0, 0, 0x00040000 }, + { 0x6, 0, 0, 0x00000000 }, +}; +const struct sock_fprog ether_relay_fprog = { + lengthof(ether_relay_filter), + ether_relay_filter +}; + +/* DHCPv6 Counter */ +uint64_t counters[DHCPv6_MESSAGE_TYPE_COUNT]; +std::map counterMap = {{1, "Solicit"}, + {2, "Advertise"}, + {3, "Request"}, + {4, "Confirm"}, + {5, "Renew"}, + {6, "Rebind"}, + {7, "Reply"}, + {8, "Release"}, + {9, "Decline"}, + {12, "Relay-Forward"}, + {13, "Relay-Reply"}}; + +/** + * @code void initialize_counter(std::string counterVlan); + * + * @brief initialize the counter by each Vlan + * + * @param counterVlan counter table with interface name + * + * @return none + */ +void initialize_counter(std::string counterVlan) { + m_applDbRedisClient.hset(counterVlan, "Solicit", toString(counters[DHCPv6_MESSAGE_TYPE_SOLICIT])); + m_applDbRedisClient.hset(counterVlan, "Advertise", toString(counters[DHCPv6_MESSAGE_TYPE_ADVERTISE])); + m_applDbRedisClient.hset(counterVlan, "Request", toString(counters[DHCPv6_MESSAGE_TYPE_REQUEST])); + m_applDbRedisClient.hset(counterVlan, "Confirm", toString(counters[DHCPv6_MESSAGE_TYPE_CONFIRM])); + m_applDbRedisClient.hset(counterVlan, "Renew", toString(counters[DHCPv6_MESSAGE_TYPE_RENEW])); + m_applDbRedisClient.hset(counterVlan, "Rebind", toString(counters[DHCPv6_MESSAGE_TYPE_REBIND])); + m_applDbRedisClient.hset(counterVlan, "Reply", toString(counters[DHCPv6_MESSAGE_TYPE_REPLY])); + m_applDbRedisClient.hset(counterVlan, "Release", toString(counters[DHCPv6_MESSAGE_TYPE_RELEASE])); + m_applDbRedisClient.hset(counterVlan, "Decline", toString(counters[DHCPv6_MESSAGE_TYPE_DECLINE])); + m_applDbRedisClient.hset(counterVlan, "Relay-Forward", toString(counters[DHCPv6_MESSAGE_TYPE_RELAY_FORW])); + m_applDbRedisClient.hset(counterVlan, "Relay-Reply", toString(counters[DHCPv6_MESSAGE_TYPE_RELAY_REPL])); +} + +/** + * @code void update_counter(std::string CounterVlan, uint8_t msg_type); + * + * @brief update the counter in state_db with count of each DHCPv6 message type + * + * @param counterVlan counter table with interface name + * @param msg_type dhcpv6 message type to be updated in counter + * + * @return none + */ +void update_counter(std::string counterVlan, uint8_t msg_type) { + m_applDbRedisClient.hset(counterVlan, counterMap.find(msg_type)->second, toString(counters[msg_type])); +} + +/** + * @code std::string toString(uint16_t count); + * + * @brief convert uint16_t to string + * + * @param count count of messages in counter + * + * @return count in string + */ +std::string toString(uint16_t count) { + std::stringstream ss; + ss << count; + std::string countValue = ss.str(); + return countValue; +} + +/** + * @code bool is_addr_gua(in6_addr addr); + * + * @brief check if address is global + * + * @param addr ipv6 address + * + * @return bool + */ +bool is_addr_gua(in6_addr addr) { + auto masked = addr.__in6_u.__u6_addr8[0] & 0xe0; + return (masked ^ 0x20) == 0x00; +} + +/** + * @code is_addr_link_local(in6_addr addr); + * + * @brief check if address is link_local + * + * @param addr ipv6 address + * + * @return bool + */ +bool is_addr_link_local(in6_addr addr) { + auto masked = ntohs(addr.__in6_u.__u6_addr16[0]) & 0xffc0; + return (masked ^ 0xfe80) == 0x0000; +} + +/** + * @code const struct ether_header *parse_ether_frame(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through ethernet frame + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return ether_header end of ethernet header position + */ +const struct ether_header *parse_ether_frame(const uint8_t *buffer, const uint8_t **out_end) { + (*out_end) = buffer + sizeof(struct ether_header); + return (const struct ether_header *)buffer; +} + +/** + * @code const struct ip6_hdr *parse_ip6_hdr(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through ipv6 header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return ip6_hdr end of ipv6 header position + */ +const struct ip6_hdr *parse_ip6_hdr(const uint8_t *buffer, const uint8_t **out_end) { + (*out_end) = buffer + sizeof(struct ip6_hdr); + return (struct ip6_hdr *)buffer; +} + +/** + * @code const struct udphdr *parse_udp(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through udp header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return udphdr end of udp header position + */ +const struct udphdr *parse_udp(const uint8_t *buffer, const uint8_t **out_end) { + (*out_end) = buffer + sizeof(struct udphdr); + return (const struct udphdr *)buffer; +} + +/** + * @code const struct dhcpv6_msg *parse_dhcpv6_hdr(const uint8_t *buffer); + * + * @brief parse through dhcpv6 header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_msg end of dhcpv6 header position + */ +const struct dhcpv6_msg *parse_dhcpv6_hdr(const uint8_t *buffer) { + return (const struct dhcpv6_msg *)buffer; +} + +/** + * @code const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); + * + * @brief parse through dhcpv6 relay message + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_relay_msg start of dhcpv6 relay message or end of dhcpv6 message type position + */ +const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer) { + return (const struct dhcpv6_relay_msg *)buffer; +} + +/** + * @code const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through dhcpv6 option + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_option end of dhcpv6 message option + */ +const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_t **out_end) { + uint32_t size = 4; // option-code + option-len + size += ntohs(*(uint16_t *)(buffer + 2)); + (*out_end) = buffer + size; + + return (const struct dhcpv6_option *)buffer; +} + +/** + * @code void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n); + * + * @brief send udp packet + * + * @param *buffer message buffer + * @param sockaddr_in6 target target socket + * @param n length of message + * + * @return dhcpv6_option end of dhcpv6 message option + */ +void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n) { + if(sendto(sock, buffer, n, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) + syslog(LOG_ERR, "sendto: Failed to send to target address\n"); +} + +/** + * @code relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length); + * + * @brief embed the DHCPv6 message received into DHCPv6 relay forward message + * + * @param buffer pointer to buffer + * @param msg pointer to parsed DHCPv6 message + * @param msg_length length of DHCPv6 message + * + * @return none + */ +void relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length) { + struct dhcpv6_option option; + option.option_code = htons(OPTION_RELAY_MSG); + option.option_length = htons(msg_length); + memcpy(buffer, &option, sizeof(struct dhcpv6_option)); + memcpy(buffer + sizeof(struct dhcpv6_option), msg, msg_length); +} + +/** + * @code sock_open(int ifindex, const struct sock_fprog *fprog); + * + * @brief prepare L2 socket to attach to "udp and port 547" filter + * + * @param ifindex interface index + * @param fprog bpf filter "udp and port 547" + * + * @return socket descriptor + */ +int sock_open(int ifindex, const struct sock_fprog *fprog) +{ + if (!ifindex) { + errno = EINVAL; + return -1; + } + + int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (s == -1) { + syslog(LOG_ERR, "socket: Failed to create socket\n"); + return -1; + } + + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = ifindex + }; + + if (bind(s, (struct sockaddr *)&sll, sizeof sll) == -1) { + syslog(LOG_ERR, "bind: Failed to bind to specified interface\n"); + (void) close(s); + return -1; + } + + if (fprog && setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, fprog, sizeof *fprog) == -1) + { + syslog(LOG_ERR, "setsockopt: Failed to attach filter\n"); + (void) close(s); + return -1; + } + + return s; +} + +/** + * @code prepare_relay_config(relay_config *interface_config, int local_sock, int filter); + * + * @brief prepare for specified relay interface config: server and link address + * + * @param interface_config pointer to relay config to be prepared + * @param local_sock L3 socket used for relaying messages + * @param filter socket attached with filter + * + * @return none + */ +void prepare_relay_config(relay_config *interface_config, int *local_sock, int filter) { + struct ifaddrs *ifa, *ifa_tmp; + sockaddr_in6 non_link_local; + sockaddr_in6 link_local; + + interface_config->local_sock = *local_sock; + interface_config->filter = filter; + + for(auto server: interface_config->servers) { + sockaddr_in6 tmp; + if(inet_pton(AF_INET6, server.c_str(), &tmp.sin6_addr) != 1) + { + syslog(LOG_WARNING, "inet_pton: Failed to convert IPv6 address\n"); + } + tmp.sin6_family = AF_INET6; + tmp.sin6_flowinfo = 0; + tmp.sin6_port = htons(RELAY_PORT); + tmp.sin6_scope_id = 0; + interface_config->servers_sock.push_back(tmp); + } + + if (getifaddrs(&ifa) == -1) { + syslog(LOG_WARNING, "getifaddrs: Unable to get network interfaces\n"); + exit(1); + } + + ifa_tmp = ifa; + while (ifa_tmp) { + if (ifa_tmp->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr; + if((strcmp(ifa_tmp->ifa_name, interface_config->interface.c_str()) == 0) && is_addr_gua(in6->sin6_addr)) { + non_link_local = *in6; + break; + } + if((strcmp(ifa_tmp->ifa_name, interface_config->interface.c_str()) == 0) && is_addr_link_local(in6->sin6_addr)) { + link_local = *in6; + } + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + + if(is_addr_gua(non_link_local.sin6_addr)) { + interface_config->link_address = non_link_local; + } + else { + interface_config->link_address = link_local; + } +} + +/** + * @code prepare_socket(int *local_sock); + * + * @brief prepare L3 socket for sending + * + * @param local_sock pointer to socket to be prepared + * + * @return none + */ +void prepare_socket(int *local_sock) { + int flag = 1; + sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_addr = in6addr_any; + addr.sin6_port = htons(RELAY_PORT); + + if ((*local_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + syslog(LOG_ERR, "socket: Failed to create socket\n"); + } + + if((setsockopt(*local_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) == -1) { + syslog(LOG_ERR, "setsockopt: Unable to set socket option\n"); + } + + if (bind(*local_sock, (sockaddr *)&addr, sizeof(addr)) == -1) { + syslog(LOG_ERR, "bind: Failed to bind to socket\n"); + } +} + + +/** + * @code relay_client(int sock, const uint8_t *msg, int32_t len, ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); + * + * @brief construct relay-forward message + * + * @param sock L3 socket for sending data to servers + * @param msg pointer to dhcpv6 message header position + * @param len size of data received + * @param ip_hdr pointer to IPv6 header + * @param ether_hdr pointer to Ethernet header + * @param config pointer to the relay interface config + * + * @return none + */ +void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config) { + static uint8_t buffer[4096]; + auto current_buffer_position = buffer; + dhcpv6_relay_msg new_message; + new_message.msg_type = DHCPv6_MESSAGE_TYPE_RELAY_FORW; + memcpy(&new_message.peer_address, &ip_hdr->ip6_src, sizeof(in6_addr)); + new_message.hop_count = 0; + + memcpy(&new_message.link_address, &config->link_address.sin6_addr, sizeof(in6_addr)); + memcpy(current_buffer_position, &new_message, sizeof(dhcpv6_relay_msg)); + current_buffer_position += sizeof(dhcpv6_relay_msg); + + if(config->is_option_79) { + linklayer_addr_option option79; + option79.link_layer_type = htons(1); + option79.option_code = htons(OPTION_CLIENT_LINKLAYER_ADDR); + option79.option_length = htons(2 + 6); // link_layer_type field + address + + memcpy(current_buffer_position, &option79, sizeof(linklayer_addr_option)); + current_buffer_position += sizeof(linklayer_addr_option); + + memcpy(current_buffer_position, ðer_hdr->ether_shost, sizeof(ether_hdr->ether_shost)); + current_buffer_position += sizeof(ether_hdr->ether_shost); + } + + auto dhcp_message_length = len; + relay_forward(current_buffer_position, parse_dhcpv6_hdr(msg), dhcp_message_length); + current_buffer_position += dhcp_message_length + sizeof(dhcpv6_option); + + for(auto server: config->servers_sock) { + send_udp(sock, buffer, server, current_buffer_position - buffer); + } +} + +/** + * @code relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); + * + * @brief relay and unwrap a relay-reply message + * + * @param sock L3 socket for sending data to servers + * @param msg pointer to dhcpv6 message header position + * @param len size of data received + * @param config relay interface config + * + * @return none + */ + void relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs) { + static uint8_t buffer[4096]; + char ifname[configs->interface.size()]; + struct sockaddr_in6 target_addr; + auto current_buffer_position = buffer; + auto current_position = msg; + const uint8_t *tmp = NULL; + auto dhcp_relay_header = parse_dhcpv6_relay(msg); + current_position += sizeof(struct dhcpv6_relay_msg); + + while ((current_position - msg) != len) { + auto option = parse_dhcpv6_opt(current_position, &tmp); + current_position = tmp; + switch (ntohs(option->option_code)) { + case OPTION_RELAY_MSG: + memcpy(current_buffer_position, ((uint8_t *)option) + sizeof(struct dhcpv6_option), ntohs(option->option_length)); + current_buffer_position += ntohs(option->option_length); + break; + default: + break; + } + } + + strcpy(ifname, configs->interface.c_str()); + memcpy(&target_addr.sin6_addr, &dhcp_relay_header->peer_address, sizeof(struct in6_addr)); + target_addr.sin6_family = AF_INET6; + target_addr.sin6_flowinfo = 0; + target_addr.sin6_port = htons(CLIENT_PORT); + target_addr.sin6_scope_id = if_nametoindex(ifname); + + send_udp(sock, buffer, target_addr, current_buffer_position - buffer); +} + + +/** + * @code callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the filter socket + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void callback(evutil_socket_t fd, short event, void *arg) { + struct relay_config *config = (struct relay_config *)(arg); + static uint8_t message_buffer[4096]; + int32_t len = recv(config->filter, message_buffer, 4096, 0); + if (len <= 0) { + syslog(LOG_WARNING, "recv: Failed to receive data at filter socket: %s\n", strerror(errno)); + return; + } + + char* ptr = (char *)message_buffer; + const uint8_t *current_position = (uint8_t *)ptr; + const uint8_t *tmp = NULL; + const uint8_t *prev = NULL; + + auto ether_header = parse_ether_frame(current_position, &tmp); + current_position = tmp; + + auto ip_header = parse_ip6_hdr(current_position, &tmp); + current_position = tmp; + + prev = current_position; + if (ip_header->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_UDP) { + const struct ip6_ext *ext_header; + do { + ext_header = (const struct ip6_ext *)current_position; + current_position += ext_header->ip6e_len; + if((current_position == prev) || (current_position >= (uint8_t *)ptr + sizeof(message_buffer))) { + return; + } + prev = current_position; + } + while (ext_header->ip6e_nxt != IPPROTO_UDP); + } + + auto udp_header = parse_udp(current_position, &tmp); + current_position = tmp; + + auto msg = parse_dhcpv6_hdr(current_position); + counters[msg->msg_type]++; + std::string counterVlan = counter_table; + update_counter(counterVlan.append(config->interface), msg->msg_type); + + relay_client(config->local_sock, current_position, ntohs(udp_header->len) - sizeof(udphdr), ip_header, ether_header, config); +} + +/** + * @code void server_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the server socket + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void server_callback(evutil_socket_t fd, short event, void *arg) { + struct relay_config *config = (struct relay_config *)arg; + sockaddr_in6 from; + socklen_t len = sizeof(from); + int32_t data = 0; + static uint8_t message_buffer[4096]; + + if ((data = recvfrom(config->local_sock, message_buffer, 4096, 0, (sockaddr *)&from, &len)) == -1) { + syslog(LOG_WARNING, "recv: Failed to receive data from server\n"); + } + + auto msg = parse_dhcpv6_hdr(message_buffer); + + counters[msg->msg_type]++; + std::string counterVlan = counter_table; + update_counter(counterVlan.append(config->interface), msg->msg_type); + if (msg->msg_type == DHCPv6_MESSAGE_TYPE_RELAY_REPL) { + relay_relay_reply(config->local_sock, message_buffer, data, config); + } +} + +/** + * @code signal_init(); + * + * @brief initialize DHCPv6 Relay libevent signals + */ +int signal_init() { + int rv = -1; + do { + 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; + } + rv = 0; + } while(0); + return rv; +} + +/** + * @code signal_start(); + * + * @brief start DHCPv6 Relay libevent base and add signals + */ +int signal_start() +{ + int rv = -1; + do + { + 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; + } + + if (event_base_dispatch(base) != 0) { + syslog(LOG_ERR, "Could not start libevent dispatching loop\n"); + } + + rv = 0; + } while (0); + + return rv; +} + +/** + * @code signal_callback(fd, event, arg); + * + * @brief signal handler for dhcp6relay. Initiate shutdown when signal is caught + * + * @param fd libevent socket + * @param event event triggered + * @param arg pointer to libevent base + * + * @return none + */ +void signal_callback(evutil_socket_t fd, short event, void *arg) +{ + syslog(LOG_ALERT, "Received signal: '%s'\n", strsignal(fd)); + if ((fd == SIGTERM) || (fd == SIGINT)) { + dhcp6relay_stop(); + } +} + +/** + * @code dhcp6relay_stop(); + * + * @brief stop DHCPv6 Relay libevent loop upon signal + */ +void dhcp6relay_stop() +{ + event_base_loopexit(base, NULL); +} + +/** + * @code loop_relay(std::vector *vlans); + * + * @brief main loop: configure sockets, create libevent base, start server listener thread + * + * @param vlans list of vlans retrieved from config_db + */ +void loop_relay(std::vector *vlans) { + std::vector sockets; + + for(std::size_t i = 0; isize(); i++) { + struct relay_config config = vlans->at(i); + int filter = 0; + int local_sock = 0; + const char *ifname = config.interface.c_str(); + int index = if_nametoindex(ifname); + + std::string counterVlan = counter_table; + initialize_counter(counterVlan.append(config.interface)); + + filter = sock_open(index, ðer_relay_fprog); + + prepare_socket(&local_sock); + sockets.push_back(filter); + sockets.push_back(local_sock); + + prepare_relay_config(&config, &local_sock, filter); + + evutil_make_listen_socket_reuseable(filter); + evutil_make_socket_nonblocking(filter); + + evutil_make_listen_socket_reuseable(local_sock); + evutil_make_socket_nonblocking(local_sock); + + base = event_base_new(); + if(base == NULL) { + syslog(LOG_ERR, "libevent: Failed to create base\n"); + } + + listen_event = event_new(base, filter, EV_READ|EV_PERSIST, callback, (void *)&config); + server_listen_event = event_new(base, local_sock, EV_READ|EV_PERSIST, server_callback, (void *)&config); + if (listen_event == NULL || server_listen_event == NULL) { + syslog(LOG_ERR, "libevent: Failed to create libevent\n"); + } + + event_add(listen_event, NULL); + event_add(server_listen_event, NULL); + } + + if((signal_init() == 0) && signal_start() == 0) { + shutdown(); + for(std::size_t i = 0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define PACKED __attribute__ ((packed)) + +#define RELAY_PORT 547 +#define CLIENT_PORT 546 +#define HOP_LIMIT 32 + +#define lengthof(A) (sizeof (A) / sizeof (A)[0]) + +#define OPTION_RELAY_MSG 9 +#define OPTION_CLIENT_LINKLAYER_ADDR 79 + +/* DHCPv6 message types */ +typedef enum +{ + DHCPv6_MESSAGE_TYPE_SOLICIT = 1, + DHCPv6_MESSAGE_TYPE_ADVERTISE = 2, + DHCPv6_MESSAGE_TYPE_REQUEST = 3, + DHCPv6_MESSAGE_TYPE_CONFIRM = 4, + DHCPv6_MESSAGE_TYPE_RENEW = 5, + DHCPv6_MESSAGE_TYPE_REBIND = 6, + DHCPv6_MESSAGE_TYPE_REPLY = 7, + DHCPv6_MESSAGE_TYPE_RELEASE = 8, + DHCPv6_MESSAGE_TYPE_DECLINE = 9, + DHCPv6_MESSAGE_TYPE_RELAY_FORW = 12, + DHCPv6_MESSAGE_TYPE_RELAY_REPL = 13, + + DHCPv6_MESSAGE_TYPE_COUNT +} dhcp_message_type_t; + +struct relay_config { + int local_sock; + int filter; + sockaddr_in6 link_address; + std::string interface; + std::vector servers; + std::vector servers_sock; + bool is_option_79; +}; + + +/* DHCPv6 messages and options */ + +struct dhcpv6_msg { + uint8_t msg_type; +}; + +struct PACKED dhcpv6_relay_msg { + uint8_t msg_type; + uint8_t hop_count; + struct in6_addr link_address; + struct in6_addr peer_address; +}; + + +struct dhcpv6_option { + uint16_t option_code; + uint16_t option_length; +}; + +struct linklayer_addr_option { + uint16_t option_code; + uint16_t option_length; + uint16_t link_layer_type; +}; + +/** + * @code sock_open(int ifindex, const struct sock_fprog *fprog); + * + * @brief prepare L2 socket to attach to "udp and port 547" filter + * + * @param ifindex interface index + * @param fprog bpf filter "udp and port 547" + * + * @return socket descriptor + */ +int sock_open(int ifindex, const struct sock_fprog *fprog); + +/** + * @code prepare_socket(int *local_sock); + * + * @brief prepare L3 socket for sending + * + * @param local_sock pointer to socket to be prepared + * @param config argument config that contains strings of server and interface addresses + * @param index interface id + * + * @return none + */ +void prepare_socket(int *local_sock); + +/** + * @code prepare_relay_config(relay_config *interface_config, int *local_sock, int filter); + * + * @brief prepare for specified relay interface config: server and link address + * + * @param interface_config pointer to relay config to be prepared + * @param local_sock L3 socket used for relaying messages + * @param filter socket attached with filter + * + * @return none + */ +void prepare_relay_config(relay_config *interface_config, int *local_sock, int filter); + +/** + * @code relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length); + * + * @brief embed the DHCPv6 message received into DHCPv6 relay forward message + * + * @param buffer pointer to buffer + * @param msg pointer to parsed DHCPv6 message + * @param msg_length length of DHCPv6 message + * + * @return none + */ +void relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length); + +/** + * @code relay_client(int sock, const uint8_t *msg, int32_t len, ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); + * + * @brief construct relay-forward message + * + * @param sock L3 socket for sending data to servers + * @param msg pointer to dhcpv6 message header position + * @param len size of data received + * @param ip_hdr pointer to IPv6 header + * @param ether_hdr pointer to Ethernet header + * @param config pointer to the relay interface config + * + * @return none + */ +void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); + +/** + * @code relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); + * + * @brief relay and unwrap a relay-reply message + * + * @param sock L3 socket for sending data to servers + * @param msg pointer to dhcpv6 message header position + * @param len size of data received + * @param config relay interface config + * + * @return none + */ +void relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); + +/** + * @code loop_relay(std::vector *vlans); + * + * @brief main loop: configure sockets, create libevent base, start server listener thread + * + * @param vlans list of vlans retrieved from config_db + */ +void loop_relay(std::vector *vlans); + +/** + * @code signal_init(); + * + * @brief initialize DHCPv6 Relay libevent signals + */ +int signal_init(); + +/** + * @code signal_start(); + * + * @brief start DHCPv6 Relay libevent base and add signals + */ +int signal_start(); + +/** + * @code dhcp6relay_stop(); + * + * @brief stop DHCPv6 Relay libevent loop upon signal + */ +void dhcp6relay_stop(); + +/** + * @code signal_callback(fd, event, arg); + * + * @brief signal handler for dhcp6relay. Initiate shutdown when signal is caught + * + * @param fd libevent socket + * @param event event triggered + * @param arg pointer to libevent base + * + * @return none + */ +void signal_callback(evutil_socket_t fd, short event, void *arg); + +/** + * @code shutdown(); + * + * @brief free signals and terminate threads + */ +void shutdown(); + +/** + * @code void initialize_counter(std::string counterVlan) + * + * @brief initialize the counter by each Vlan + * + * @param counterVlan counter table with interface name + * + * @return none + */ +void initialize_counter(std::string counterVlan); + +/** + * @code void update_counter(std::string CounterVlan, uint8_t msg_type); + * + * @brief update the counter in state_db with count of each DHCPv6 message type + * + * @param counterVlan counter table with interface name + * @param msg_type dhcpv6 message type to be updated in counter + * + * @return none + */ +void update_counter(std::string counterVlan, uint8_t msg_type); + +/* Helper functions */ + +/** + * @code std::string toString(uint16_t count); + * + * @brief convert uint16_t to string + * + * @param count count of messages in counter + * + * @return count in string + */ +std::string toString(uint16_t count); + +/** + * @code bool is_addr_gua(in6_addr addr); + * + * @brief check if address is global + * + * @param addr ipv6 address + * + * @return bool + */ +bool is_addr_gua(in6_addr addr); + +/** + * @code is_addr_link_local(in6_addr addr); + * + * @brief check if address is link_local + * + * @param addr ipv6 address + * + * @return bool + */ +bool is_addr_link_local(in6_addr addr); + +/** + * @code const struct ether_header *parse_ether_frame(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through ethernet frame + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return ether_header end of ethernet header position + */ +const struct ether_header *parse_ether_frame(const uint8_t *buffer, const uint8_t **out_end); + +/** + * @code const struct ip6_hdr *parse_ip6_hdr(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through ipv6 header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return ip6_hdr end of ipv6 header position + */ +const struct ip6_hdr *parse_ip6_hdr(const uint8_t *buffer, const uint8_t **out_end); + +/** + * @code const struct udphdr *parse_udp(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through udp header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return udphdr end of udp header position + */ +const struct udphdr *parse_udp(const uint8_t *buffer, const uint8_t **out_end); + +/** + * @code const struct dhcpv6_msg *parse_dhcpv6_hdr(const uint8_t *buffer); + * + * @brief parse through dhcpv6 header + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_msg end of dhcpv6 header position + */ +const struct dhcpv6_msg *parse_dhcpv6_hdr(const uint8_t *buffer); + +/** + * @code const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); + * + * @brief parse through dhcpv6 relay message + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_relay_msg start of dhcpv6 relay message or end of dhcpv6 message type position + */ +const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); + +/** + * @code const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_t **out_end); + * + * @brief parse through dhcpv6 option + * + * @param *buffer message buffer + * @param **out_end pointer + * + * @return dhcpv6_option end of dhcpv6 message option + */ +const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_t **out_end); + +/** + * @code void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n); + * + * @brief send udp packet + * + * @param *buffer message buffer + * @param sockaddr_in6 target target socket + * @param n length of message + * + * @return dhcpv6_option end of dhcpv6 message option + */ +void send_udp(int sock, struct sockaddr_in6 target, uint8_t *buffer, uint32_t n); + diff --git a/src/dhcp6relay/src/subdir.mk b/src/dhcp6relay/src/subdir.mk new file mode 100644 index 00000000000..e822f96a4bc --- /dev/null +++ b/src/dhcp6relay/src/subdir.mk @@ -0,0 +1,23 @@ +CC := g++ + +C_SRCS += \ +../src/relay.c \ +../src/configInterface.c \ +../src/main.c + +OBJS += \ +./src/relay.o \ +./src/configInterface.o \ +./src/main.o + +C_DEPS += \ +./src/relay.d \ +./src/configInterface.d \ +./src/main.d + +src/%.o: src/%.cpp + @echo 'Building file: $<' + @echo 'Invoking: GCC C++ Compiler' + $(CC) -std=c++14 -D__FILENAME__="$(subst src/,,$<)" $(LIBS) -Wall -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@)" -o "$@" "$<" + @echo 'Finished building: $<' + @echo ' ' diff --git a/src/dhcpmon/src/dhcp_device.c b/src/dhcpmon/src/dhcp_device.c index be2a3e95330..4ef59c298c4 100644 --- a/src/dhcpmon/src/dhcp_device.c +++ b/src/dhcpmon/src/dhcp_device.c @@ -20,25 +20,51 @@ #include #include #include +#include +#include +#include #include "dhcp_device.h" +/** DHCP versions flags */ +static bool dhcpv4_enabled; +static bool dhcpv6_enabled; + /** Counter print width */ #define DHCP_COUNTER_WIDTH 9 /** Start of Ether header of a captured frame */ #define ETHER_START_OFFSET 0 +/** EtherType field offset from Ether header of a captured frame */ +#define ETHER_TYPE_OFFSET (ETHER_START_OFFSET + 12) /** 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 +/** Start of UDP header on IPv4 packet of a captured frame */ +#define UDPv4_START_OFFSET (IP_START_OFFSET + sizeof(struct ip)) +/** Start of DHCPv4 header of a captured frame */ +#define DHCPv4_START_OFFSET (UDPv4_START_OFFSET + sizeof(struct udphdr)) +/** Start of DHCPv4 Options segment of a captured frame */ +#define DHCPv4_OPTIONS_HEADER_SIZE 240 /** Offset of DHCP GIADDR */ #define DHCP_GIADDR_OFFSET 24 +/** IPv6 link-local prefix */ +#define IPV6_LINK_LOCAL_PREFIX 0x80fe +/** Start of UDP header on IPv6 packet of a captured frame */ +#define UDPv6_START_OFFSET (IP_START_OFFSET + sizeof(struct ip6_hdr)) +/** Start of DHCPv6 header of a captured frame */ +#define DHCPv6_START_OFFSET (UDPv6_START_OFFSET + sizeof(struct udphdr)) +/** Size of 'type' field on DHCPv6 header */ +#define DHCPv6_TYPE_LENGTH 1 +/** Size of DHCPv6 relay message header to first option */ +#define DHCPv6_RELAY_MSG_OPTIONS_OFFSET 34 +/** Size of 'option' field on DHCPv6 header */ +#define DHCPv6_OPTION_LENGTH 2 +/** Size of 'option length' field on DHCPv6 header */ +#define DHCPv6_OPTION_LEN_LENGTH 2 +/** DHCPv6 OPTION_RELAY_MSG */ +#define DHCPv6_OPTION_RELAY_MSG 9 + #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*/ @@ -48,34 +74,40 @@ #define OP_JSET (BPF_JMP | BPF_JSET | BPF_K) /** bpf jset */ #define OP_LDXB (BPF_LDX | BPF_B | BPF_MSH) /** bpf ldxb */ -/** Berkeley Packet Filter program for "udp and (port 67 or port 68)". +/** Berkeley Packet Filter program for "udp and (port 546 or port 547 or port 67 or port 68)". * This program is obtained using the following command tcpdump: - * `tcpdump -dd "udp and (port 67 or port 68)"` + * `tcpdump -dd "udp and (port 546 or port 547 or 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 + {.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x0000000c}, // (000) ldh [12] + {.code = OP_JEQ, .jt = 0, .jf = 9, .k = 0x000086dd}, // (001) jeq #0x86dd jt 2 jf 11 + {.code = OP_LDB, .jt = 0, .jf = 0, .k = 0x00000014}, // (002) ldb [20] + {.code = OP_JEQ, .jt = 0, .jf = 24, .k = 0x00000011}, // (003) jeq #0x11 jt 4 jf 28 + {.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000036}, // (004) ldh [54] + {.code = OP_JEQ, .jt = 21, .jf = 0, .k = 0x00000222}, // (005) jeq #0x222 jt 27 jf 6 + {.code = OP_JEQ, .jt = 20, .jf = 0, .k = 0x00000223}, // (006) jeq #0x223 jt 27 jf 7 + {.code = OP_JEQ, .jt = 19, .jf = 0, .k = 0x00000043}, // (007) jeq #0x43 jt 27 jf 8 + {.code = OP_JEQ, .jt = 18, .jf = 0, .k = 0x00000044}, // (008) jeq #0x44 jt 27 jf 9 + {.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000038}, // (009) ldh [56] + {.code = OP_JEQ, .jt = 16, .jf = 13, .k = 0x00000222}, // (010) jeq #0x222 jt 27 jf 24 + {.code = OP_JEQ, .jt = 0, .jf = 16, .k = 0x00000800}, // (011) jeq #0x800 jt 12 jf 28 + {.code = OP_LDB, .jt = 0, .jf = 0, .k = 0x00000017}, // (012) ldb [23] + {.code = OP_JEQ, .jt = 0, .jf = 14, .k = 0x00000011}, // (013) jeq #0x11 jt 14 jf 28 + {.code = OP_LDHA, .jt = 0, .jf = 0, .k = 0x00000014}, // (014) ldh [20] + {.code = OP_JSET, .jt = 12, .jf = 0, .k = 0x00001fff}, // (015) jset #0x1fff jt 28 jf 16 + {.code = OP_LDXB, .jt = 0, .jf = 0, .k = 0x0000000e}, // (016) ldxb 4*([14]&0xf) + {.code = OP_LDHI, .jt = 0, .jf = 0, .k = 0x0000000e}, // (017) ldh [x + 14] + {.code = OP_JEQ, .jt = 8, .jf = 0, .k = 0x00000222}, // (018) jeq #0x222 jt 27 jf 19 + {.code = OP_JEQ, .jt = 7, .jf = 0, .k = 0x00000223}, // (019) jeq #0x223 jt 27 jf 20 + {.code = OP_JEQ, .jt = 6, .jf = 0, .k = 0x00000043}, // (020) jeq #0x43 jt 27 jf 21 + {.code = OP_JEQ, .jt = 5, .jf = 0, .k = 0x00000044}, // (021) jeq #0x44 jt 27 jf 22 + {.code = OP_LDHI, .jt = 0, .jf = 0, .k = 0x00000010}, // (022) ldh [x + 16] + {.code = OP_JEQ, .jt = 3, .jf = 0, .k = 0x00000222}, // (023) jeq #0x222 jt 27 jf 24 + {.code = OP_JEQ, .jt = 2, .jf = 0, .k = 0x00000223}, // (024) jeq #0x223 jt 27 jf 25 + {.code = OP_JEQ, .jt = 1, .jf = 0, .k = 0x00000043}, // (025) jeq #0x43 jt 27 jf 26 + {.code = OP_JEQ, .jt = 0, .jf = 1, .k = 0x00000044}, // (026) jeq #0x44 jt 27 jf 28 + {.code = OP_RET, .jt = 0, .jf = 0, .k = 0x00040000}, // (027) ret #262144 + {.code = OP_RET, .jt = 0, .jf = 0, .k = 0x00000000}, // (028) ret }; /** Filter program socket struct */ @@ -88,16 +120,29 @@ static struct sock_fprog dhcp_sock_bfp = { */ static dhcp_device_context_t aggregate_dev = {0}; -/** Monitored DHCP message type */ -static dhcp_message_type_t monitored_msgs[] = { - DHCP_MESSAGE_TYPE_DISCOVER, - DHCP_MESSAGE_TYPE_OFFER, - DHCP_MESSAGE_TYPE_REQUEST, - DHCP_MESSAGE_TYPE_ACK +static dhcp_device_context_t *mgmt_intf = NULL; + +/** Monitored DHCPv4 message type */ +static dhcpv4_message_type_t v4_monitored_msgs[] = { + DHCPv4_MESSAGE_TYPE_DISCOVER, + DHCPv4_MESSAGE_TYPE_OFFER, + DHCPv4_MESSAGE_TYPE_REQUEST, + DHCPv4_MESSAGE_TYPE_ACK +}; + +/** Monitored DHCPv6 message type */ +static dhcpv6_message_type_t v6_monitored_msgs[] = { + DHCPv6_MESSAGE_TYPE_SOLICIT, + DHCPv6_MESSAGE_TYPE_ADVERTISE, + DHCPv6_MESSAGE_TYPE_REQUEST, + DHCPv6_MESSAGE_TYPE_REPLY }; -/** Number of monitored DHCP message type */ -static uint8_t monitored_msg_sz = sizeof(monitored_msgs) / sizeof(*monitored_msgs); +/** Number of monitored DHCPv4 message type */ +static uint8_t v4_monitored_msg_sz = sizeof(v4_monitored_msgs) / sizeof(*v4_monitored_msgs); + +/** Number of monitored DHCPv6 message type */ +static uint8_t v6_monitored_msg_sz = sizeof(v6_monitored_msgs) / sizeof(*v6_monitored_msgs); /** * @code handle_dhcp_option_53(context, dhcp_option, dir, iphdr, dhcphdr); @@ -122,27 +167,37 @@ static void handle_dhcp_option_53(dhcp_device_context_t *context, switch (dhcp_option[2]) { // DHCP messages send by client - case DHCP_MESSAGE_TYPE_DISCOVER: - case DHCP_MESSAGE_TYPE_REQUEST: - case DHCP_MESSAGE_TYPE_DECLINE: - case DHCP_MESSAGE_TYPE_RELEASE: - case DHCP_MESSAGE_TYPE_INFORM: + case DHCPv4_MESSAGE_TYPE_DISCOVER: + case DHCPv4_MESSAGE_TYPE_REQUEST: + case DHCPv4_MESSAGE_TYPE_DECLINE: + case DHCPv4_MESSAGE_TYPE_RELEASE: + case DHCPv4_MESSAGE_TYPE_INFORM: giaddr = ntohl(dhcphdr[DHCP_GIADDR_OFFSET] << 24 | dhcphdr[DHCP_GIADDR_OFFSET + 1] << 16 | dhcphdr[DHCP_GIADDR_OFFSET + 2] << 8 | dhcphdr[DHCP_GIADDR_OFFSET + 3]); - if ((context->vlan_ip == giaddr && context->is_uplink && dir == DHCP_TX) || + if ((context->giaddr_ip == giaddr && context->is_uplink && dir == DHCP_TX) || (!context->is_uplink && dir == DHCP_RX && iphdr->ip_dst.s_addr == INADDR_BROADCAST)) { - context->counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; - aggregate_dev.counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; + context->counters.v4counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; + // If the packet recieved on the mgmt interface, we don't want to increment the aggregate device + if (context == mgmt_intf) + { + break; + } + aggregate_dev.counters.v4counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; } break; // DHCP messages send by server - case DHCP_MESSAGE_TYPE_OFFER: - case DHCP_MESSAGE_TYPE_ACK: - case DHCP_MESSAGE_TYPE_NAK: - if ((context->vlan_ip == iphdr->ip_dst.s_addr && context->is_uplink && dir == DHCP_RX) || + case DHCPv4_MESSAGE_TYPE_OFFER: + case DHCPv4_MESSAGE_TYPE_ACK: + case DHCPv4_MESSAGE_TYPE_NAK: + if ((context->giaddr_ip == iphdr->ip_dst.s_addr && context->is_uplink && dir == DHCP_RX) || (!context->is_uplink && dir == DHCP_TX)) { - context->counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; - aggregate_dev.counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; + context->counters.v4counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; + // If the packet recieved on the mgmt interface, we don't want to increment the aggregate device + if (context == mgmt_intf) + { + break; + } + aggregate_dev.counters.v4counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option[2]]++; } break; default: @@ -151,6 +206,48 @@ static void handle_dhcp_option_53(dhcp_device_context_t *context, } } +/** + * @code handle_dhcpv6_option(context, dhcp_option, dir); + * + * @brief handle the logic related to DHCPv6 option + * + * @param context Device (interface) context + * @param dhcp_option pointer to DHCP option buffer space + * @param dir packet direction + * + * @return none + */ +static void handle_dhcpv6_option(dhcp_device_context_t *context, + const u_char dhcp_option, + dhcp_packet_direction_t dir) +{ + switch (dhcp_option) + { + case DHCPv6_MESSAGE_TYPE_SOLICIT: + case DHCPv6_MESSAGE_TYPE_REQUEST: + case DHCPv6_MESSAGE_TYPE_CONFIRM: + case DHCPv6_MESSAGE_TYPE_RENEW: + case DHCPv6_MESSAGE_TYPE_REBIND: + case DHCPv6_MESSAGE_TYPE_RELEASE: + case DHCPv6_MESSAGE_TYPE_DECLINE: + case DHCPv6_MESSAGE_TYPE_ADVERTISE: + case DHCPv6_MESSAGE_TYPE_REPLY: + case DHCPv6_MESSAGE_TYPE_RECONFIGURE: + case DHCPv6_MESSAGE_TYPE_INFORMATION_REQUEST: + context->counters.v6counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option]++; + // If the packet recieved on the mgmt interface, we don't want to increment the aggregate device + if (context == mgmt_intf) + { + break; + } + aggregate_dev.counters.v6counters[DHCP_COUNTERS_CURRENT][dir][dhcp_option]++; + break; + default: + syslog(LOG_WARNING, "handle_dhcpv6_option(%s): Unknown DHCPv6 option type %d", context->intf, dhcp_option); + break; + } +} + /** * @code read_callback(fd, event, arg); * @@ -170,16 +267,29 @@ static void read_callback(int fd, short event, void *arg) 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 ip *iphdr = (struct ip*) (context->buffer + IP_START_OFFSET); - struct udphdr *udp = (struct udphdr*) (context->buffer + UDP_START_OFFSET); - uint8_t *dhcphdr = context->buffer + DHCP_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; + struct ip *iphdr; + struct ip6_hdr *ipv6hdr; + struct udphdr *udp; + uint8_t *dhcphdr; + int dhcp_option_offset; + + bool is_ipv4 = (ntohs(ethhdr->ether_type) == ETHERTYPE_IP); + if (is_ipv4) { + iphdr = (struct ip*) (context->buffer + IP_START_OFFSET); + udp = (struct udphdr*) (context->buffer + UDPv4_START_OFFSET); + dhcphdr = context->buffer + DHCPv4_START_OFFSET; + dhcp_option_offset = DHCPv4_START_OFFSET + DHCPv4_OPTIONS_HEADER_SIZE; + } else { + ipv6hdr = (struct ip6_hdr*) (context->buffer + IP_START_OFFSET); + udp = (struct udphdr*) (context->buffer + UDPv6_START_OFFSET); + dhcphdr = context->buffer + DHCPv6_START_OFFSET; + dhcp_option_offset = DHCPv6_START_OFFSET; + } + if (is_ipv4 && dhcpv4_enabled && (buffer_sz > UDPv4_START_OFFSET + sizeof(struct udphdr) + DHCPv4_OPTIONS_HEADER_SIZE) && + (ntohs(udp->len) > DHCPv4_OPTIONS_HEADER_SIZE)) { + int dhcp_sz = ntohs(udp->len) < buffer_sz - UDPv4_START_OFFSET - sizeof(struct udphdr) ? + ntohs(udp->len) : buffer_sz - UDPv4_START_OFFSET - sizeof(struct udphdr); + int dhcp_option_sz = dhcp_sz - DHCPv4_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] && @@ -213,6 +323,37 @@ static void read_callback(int fd, short event, void *arg) offset += dhcp_option[offset + 1] + 2; } } + } + else if (!is_ipv4 && dhcpv6_enabled && (buffer_sz > UDPv6_START_OFFSET + sizeof(struct udphdr) + DHCPv6_TYPE_LENGTH)) { + const u_char* dhcp_header = 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; + uint16_t option = 0; + uint16_t current_option_len = 0; + // Get to inner DHCP header from encapsulated RELAY_FORWARD or RELAY_REPLY header + while (dhcp_header[offset] == DHCPv6_MESSAGE_TYPE_RELAY_FORWARD || dhcp_header[offset] == DHCPv6_MESSAGE_TYPE_RELAY_REPLY) + { + // Get to DHCPv6_OPTION_RELAY_MSG from all options + offset += DHCPv6_RELAY_MSG_OPTIONS_OFFSET; + option = htons(*((uint16_t*)(&(dhcp_header[offset])))); + + while (option != DHCPv6_OPTION_RELAY_MSG) + { + // Add to offset the option length and get the next option ID + current_option_len = htons(*((uint16_t*)(&(dhcp_header[offset + DHCPv6_OPTION_LENGTH])))); + offset += DHCPv6_OPTION_LENGTH + DHCPv6_OPTION_LEN_LENGTH + current_option_len; + option = htons(*((uint16_t*)(&(dhcp_header[offset])))); + } + // Set the offset to DHCP-relay-message data + offset += DHCPv6_OPTION_LENGTH + DHCPv6_OPTION_LEN_LENGTH; + } + handle_dhcpv6_option(context, dhcp_header[offset], dir); } else { syslog(LOG_WARNING, "read_callback(%s): read length (%ld) is too small to capture DHCP options", context->intf, buffer_sz); @@ -221,29 +362,54 @@ static void read_callback(int fd, short event, void *arg) } /** - * @code dhcp_device_is_dhcp_inactive(counters); + * @code dhcp_device_is_dhcp_inactive(v4counters, v6counters, type); * * @brief Check if there were no DHCP activity * - * @param counters current/snapshot counter + * @param v4counters current/snapshot v4counter + * + * @param v6counters current/snapshot v6counter + * + * @param type DHCP type * * @return true if there were no DHCP activity, false otherwise */ -static bool dhcp_device_is_dhcp_inactive(uint64_t counters[][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]) +static bool dhcp_device_is_dhcp_inactive(uint64_t v4counters[][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT], + uint64_t v6counters[][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT], + dhcp_type_t type) { - uint64_t *rx_counters = counters[DHCP_COUNTERS_CURRENT][DHCP_RX]; - uint64_t *rx_counter_snapshot = counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX]; - bool rv = true; - for (uint8_t i = 0; (i < monitored_msg_sz) && rv; i++) { - rv = rx_counters[monitored_msgs[i]] == rx_counter_snapshot[monitored_msgs[i]]; + uint64_t *rx_counters; + uint64_t *rx_counter_snapshot; + + switch (type) + { + case DHCPv4_TYPE: + rx_counters = v4counters[DHCP_COUNTERS_CURRENT][DHCP_RX]; + rx_counter_snapshot = v4counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX]; + for (uint8_t i = 0; (i < v4_monitored_msg_sz) && rv; i++) { + rv = rx_counters[v4_monitored_msgs[i]] == rx_counter_snapshot[v4_monitored_msgs[i]]; + } + break; + + case DHCPv6_TYPE: + rx_counters = v6counters[DHCP_COUNTERS_CURRENT][DHCP_RX]; + rx_counter_snapshot = v6counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX]; + for (uint8_t i = 0; (i < v6_monitored_msg_sz) && rv; i++) { + rv = rx_counters[v6_monitored_msgs[i]] == rx_counter_snapshot[v6_monitored_msgs[i]]; + } + break; + + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; } return rv; } /** - * @code dhcp_device_is_dhcp_msg_unhealthy(type, counters); + * @code dhcp_device_is_dhcpv4_msg_unhealthy(type, counters); * * @brief Check if DHCP relay is functioning properly for message of type 'type'. * For every rx of message 'type', there should be increment of the same message type. @@ -253,34 +419,76 @@ static bool dhcp_device_is_dhcp_inactive(uint64_t counters[][DHCP_DIR_COUNT][DHC * * @return true if DHCP message 'type' is transmitted,false otherwise */ -static bool dhcp_device_is_dhcp_msg_unhealthy(dhcp_message_type_t type, - uint64_t counters[][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]) +static bool dhcp_device_is_dhcpv4_msg_unhealthy(dhcpv4_message_type_t type, + uint64_t v4counters[][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT]) { // check if DHCP message 'type' is being relayed - return ((counters[DHCP_COUNTERS_CURRENT][DHCP_RX][type] > counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX][type]) && - (counters[DHCP_COUNTERS_CURRENT][DHCP_TX][type] <= counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX][type]) ); + return ((v4counters[DHCP_COUNTERS_CURRENT][DHCP_RX][type] > v4counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX][type]) && + (v4counters[DHCP_COUNTERS_CURRENT][DHCP_TX][type] <= v4counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX][type]) ); } /** - * @code dhcp_device_check_positive_health(counters, counters_snapshot); + * @code dhcp_device_is_dhcpv6_msg_unhealthy(type, counters); * - * @brief Check if DHCP relay is functioning properly for monitored messages (Discover, Offer, Request, ACK.) - * For every rx of monitored messages, there should be increment of the same message type. + * @brief Check if DHCP relay is functioning properly for message of type 'type'. + * For every rx of message 'type', there should be increment of the same message type. * + * @param type DHCP message type * @param counters current/snapshot counter * + * @return true if DHCP message 'type' is transmitted,false otherwise + */ +static bool dhcp_device_is_dhcpv6_msg_unhealthy(dhcpv6_message_type_t type, + uint64_t v6counters[][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT]) +{ + // check if DHCP message 'type' is being relayed + return ((v6counters[DHCP_COUNTERS_CURRENT][DHCP_RX][type] > v6counters[DHCP_COUNTERS_SNAPSHOT][DHCP_RX][type]) && + (v6counters[DHCP_COUNTERS_CURRENT][DHCP_TX][type] <= v6counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX][type]) ); +} + +/** + * @code dhcp_device_check_positive_health(v4counters, v6counters, type); + * + * @brief Check if DHCPv4/6 relay is functioning properly for monitored messages. + * DHCPv4 (Discover, Offer, Request, ACK.) and DHCPv6 (Solicit, Advertise, Request, Reply). + * For every rx of monitored messages, there should be increment of the same message type. + * + * @param v4counters current/snapshot counter + * + * @param v6counters current/snapshot counter + * + * @param type DHCP type + * * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE */ -static dhcp_mon_status_t dhcp_device_check_positive_health(uint64_t counters[][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]) +static dhcp_mon_status_t dhcp_device_check_positive_health(uint64_t v4counters[][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT], + uint64_t v6counters[][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT], + dhcp_type_t type) { dhcp_mon_status_t rv = DHCP_MON_STATUS_HEALTHY; bool is_dhcp_unhealthy = false; - for (uint8_t i = 0; (i < monitored_msg_sz) && !is_dhcp_unhealthy; i++) { - is_dhcp_unhealthy = dhcp_device_is_dhcp_msg_unhealthy(monitored_msgs[i], counters); + + switch (type) + { + case DHCPv4_TYPE: + for (uint8_t i = 0; (i < v4_monitored_msg_sz) && !is_dhcp_unhealthy; i++) { + is_dhcp_unhealthy = dhcp_device_is_dhcpv4_msg_unhealthy(v4_monitored_msgs[i], v4counters); + } + break; + + case DHCPv6_TYPE: + for (uint8_t i = 0; (i < v6_monitored_msg_sz) && !is_dhcp_unhealthy; i++) { + is_dhcp_unhealthy = dhcp_device_is_dhcpv6_msg_unhealthy(v6_monitored_msgs[i], v6counters); + } + break; + + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; } - // if we have rx DORA then we should have corresponding tx DORA (DORA being relayed) + // if we have rx DORA/SARR then we should have corresponding tx DORA/SARR (DORA/SARR being relayed) if (is_dhcp_unhealthy) { rv = DHCP_MON_STATUS_UNHEALTHY; } @@ -289,27 +497,47 @@ static dhcp_mon_status_t dhcp_device_check_positive_health(uint64_t counters[][D } /** - * @code dhcp_device_check_negative_health(counters); + * @code dhcp_device_check_negative_health(v4counters, v6counters, type); * * @brief Check that DHCP relayed messages are not being transmitted out of this interface/dev * using its counters. The interface is negatively healthy if there are not DHCP message * travelling through it. * - * @param counters recent interface counter - * @param counters_snapshot snapshot counters + * @param v4counters current/snapshot counter + * @param v6counters current/snapshot counter + * @param type DHCP type * * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE */ -static dhcp_mon_status_t dhcp_device_check_negative_health(uint64_t counters[][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]) +static dhcp_mon_status_t dhcp_device_check_negative_health(uint64_t v4counters[][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT], + uint64_t v6counters[][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT], + dhcp_type_t type) { dhcp_mon_status_t rv = DHCP_MON_STATUS_HEALTHY; + bool is_dhcp_unhealthy = false; - uint64_t *tx_counters = counters[DHCP_COUNTERS_CURRENT][DHCP_TX]; - uint64_t *tx_counter_snapshot = counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX]; + uint64_t *tx_counters; + uint64_t *tx_counter_snapshot; - bool is_dhcp_unhealthy = false; - for (uint8_t i = 0; (i < monitored_msg_sz) && !is_dhcp_unhealthy; i++) { - is_dhcp_unhealthy = tx_counters[monitored_msgs[i]] > tx_counter_snapshot[monitored_msgs[i]]; + switch (type) + { + case DHCPv4_TYPE: + tx_counters = v4counters[DHCP_COUNTERS_CURRENT][DHCP_TX]; + tx_counter_snapshot = v4counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX]; + for (uint8_t i = 0; (i < v4_monitored_msg_sz) && !is_dhcp_unhealthy; i++) { + is_dhcp_unhealthy = tx_counters[v4_monitored_msgs[i]] > tx_counter_snapshot[v4_monitored_msgs[i]]; + } + break; + case DHCPv6_TYPE: + tx_counters = v6counters[DHCP_COUNTERS_CURRENT][DHCP_TX]; + tx_counter_snapshot = v6counters[DHCP_COUNTERS_SNAPSHOT][DHCP_TX]; + for (uint8_t i = 0; (i < v6_monitored_msg_sz) && !is_dhcp_unhealthy; i++) { + is_dhcp_unhealthy = tx_counters[v6_monitored_msgs[i]] > tx_counter_snapshot[v6_monitored_msgs[i]]; + } + break; + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; } // for negative validation, return unhealthy if DHCP packet are being @@ -322,7 +550,7 @@ static dhcp_mon_status_t dhcp_device_check_negative_health(uint64_t counters[][D } /** - * @code dhcp_device_check_health(check_type, counters, counters_snapshot); + * @code dhcp_device_check_health(check_type, v4counters, v6counters, type); * * @brief Check that DHCP relay is functioning properly given a check type. Positive check * indicates for every rx of DHCP message of type 'type', there would increment of @@ -331,59 +559,80 @@ static dhcp_mon_status_t dhcp_device_check_negative_health(uint64_t counters[][D * considered unhealthy. * * @param check_type type of health check - * @param counters current/snapshot counter + * @param v4counters current/snapshot counters + * @param v6counters current/snapshot counters + * @param type DHCP type * * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE */ static dhcp_mon_status_t dhcp_device_check_health(dhcp_mon_check_t check_type, - uint64_t counters[][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]) + uint64_t v4counters[][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT], + uint64_t v6counters[][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT], + dhcp_type_t type) { dhcp_mon_status_t rv = DHCP_MON_STATUS_HEALTHY; - if (dhcp_device_is_dhcp_inactive(aggregate_dev.counters)) { + if (dhcp_device_is_dhcp_inactive(v4counters, v6counters, type)) { rv = DHCP_MON_STATUS_INDETERMINATE; } else if (check_type == DHCP_MON_CHECK_POSITIVE) { - rv = dhcp_device_check_positive_health(counters); + rv = dhcp_device_check_positive_health(v4counters, v6counters, type); } else if (check_type == DHCP_MON_CHECK_NEGATIVE) { - rv = dhcp_device_check_negative_health(counters); + rv = dhcp_device_check_negative_health(v4counters, v6counters, type); } return rv; } /** - * @code dhcp_print_counters(vlan_intf, type, counters); + * @code dhcp_print_counters(vlan_intf, type, v4counters, v6counters); * * @brief prints DHCP counters to sylsog. * - * @param vlan_intf vlan interface name - * @param type counter type - * @param counters interface counter + * @param vlan_intf vlan interface name + * @param type counter type + * @param v4counters interface counter + * @param v6counters interface counter * * @return none */ static void dhcp_print_counters(const char *vlan_intf, dhcp_counters_type_t type, - uint64_t counters[][DHCP_MESSAGE_TYPE_COUNT]) + uint64_t v4counters[][DHCPv4_MESSAGE_TYPE_COUNT], + uint64_t v6counters[][DHCPv6_MESSAGE_TYPE_COUNT]) { - static const char *counter_desc[DHCP_COUNTERS_COUNT] = { + static const char *v4_counter_desc[DHCP_COUNTERS_COUNT] = { + [DHCP_COUNTERS_CURRENT] = " Current", + [DHCP_COUNTERS_SNAPSHOT] = "Snapshot" + }; + static const char *v6_counter_desc[DHCP_COUNTERS_COUNT] = { [DHCP_COUNTERS_CURRENT] = " Current", [DHCP_COUNTERS_SNAPSHOT] = "Snapshot" }; syslog( LOG_NOTICE, - "[%*s-%*s rx/tx] Discover: %*lu/%*lu, Offer: %*lu/%*lu, Request: %*lu/%*lu, ACK: %*lu/%*lu\n", + "DHCPv4 [%*s-%*s rx/tx] Discover: %*lu/%*lu, Offer: %*lu/%*lu, Request: %*lu/%*lu, ACK: %*lu/%*lu\n\ + DHCPv6 [%*s-%*s rx/tx] Solicit: %*lu/%*lu, Advertise: %*lu/%*lu, Request: %*lu/%*lu, Reply: %*lu/%*lu\n", IF_NAMESIZE, vlan_intf, - (int) strlen(counter_desc[type]), counter_desc[type], - DHCP_COUNTER_WIDTH, counters[DHCP_RX][DHCP_MESSAGE_TYPE_DISCOVER], - DHCP_COUNTER_WIDTH, counters[DHCP_TX][DHCP_MESSAGE_TYPE_DISCOVER], - DHCP_COUNTER_WIDTH, counters[DHCP_RX][DHCP_MESSAGE_TYPE_OFFER], - DHCP_COUNTER_WIDTH, counters[DHCP_TX][DHCP_MESSAGE_TYPE_OFFER], - DHCP_COUNTER_WIDTH, counters[DHCP_RX][DHCP_MESSAGE_TYPE_REQUEST], - DHCP_COUNTER_WIDTH, counters[DHCP_TX][DHCP_MESSAGE_TYPE_REQUEST], - DHCP_COUNTER_WIDTH, counters[DHCP_RX][DHCP_MESSAGE_TYPE_ACK], - DHCP_COUNTER_WIDTH, counters[DHCP_TX][DHCP_MESSAGE_TYPE_ACK] + (int) strlen(v4_counter_desc[type]), v4_counter_desc[type], + DHCP_COUNTER_WIDTH, v4counters[DHCP_RX][DHCPv4_MESSAGE_TYPE_DISCOVER], + DHCP_COUNTER_WIDTH, v4counters[DHCP_TX][DHCPv4_MESSAGE_TYPE_DISCOVER], + DHCP_COUNTER_WIDTH, v4counters[DHCP_RX][DHCPv4_MESSAGE_TYPE_OFFER], + DHCP_COUNTER_WIDTH, v4counters[DHCP_TX][DHCPv4_MESSAGE_TYPE_OFFER], + DHCP_COUNTER_WIDTH, v4counters[DHCP_RX][DHCPv4_MESSAGE_TYPE_REQUEST], + DHCP_COUNTER_WIDTH, v4counters[DHCP_TX][DHCPv4_MESSAGE_TYPE_REQUEST], + DHCP_COUNTER_WIDTH, v4counters[DHCP_RX][DHCPv4_MESSAGE_TYPE_ACK], + DHCP_COUNTER_WIDTH, v4counters[DHCP_TX][DHCPv4_MESSAGE_TYPE_ACK], + IF_NAMESIZE, vlan_intf, + (int) strlen(v6_counter_desc[type]), v6_counter_desc[type], + DHCP_COUNTER_WIDTH, v6counters[DHCP_RX][DHCPv6_MESSAGE_TYPE_SOLICIT], + DHCP_COUNTER_WIDTH, v6counters[DHCP_TX][DHCPv6_MESSAGE_TYPE_SOLICIT], + DHCP_COUNTER_WIDTH, v6counters[DHCP_RX][DHCPv6_MESSAGE_TYPE_ADVERTISE], + DHCP_COUNTER_WIDTH, v6counters[DHCP_TX][DHCPv6_MESSAGE_TYPE_ADVERTISE], + DHCP_COUNTER_WIDTH, v6counters[DHCP_RX][DHCPv6_MESSAGE_TYPE_REQUEST], + DHCP_COUNTER_WIDTH, v6counters[DHCP_TX][DHCPv6_MESSAGE_TYPE_REQUEST], + DHCP_COUNTER_WIDTH, v6counters[DHCP_RX][DHCPv6_MESSAGE_TYPE_REPLY], + DHCP_COUNTER_WIDTH, v6counters[DHCP_TX][DHCPv6_MESSAGE_TYPE_REPLY] ); } @@ -437,7 +686,7 @@ static int init_socket(dhcp_device_context_t *context, const char *intf) * * @return 0 on success, otherwise for failure */ -static int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) +int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) { int rv = -1; @@ -453,12 +702,12 @@ static int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) strncpy(ifr.ifr_name, context->intf, sizeof(ifr.ifr_name) - 1); ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0'; - // Get network address + // Get v4 network address if (ioctl(fd, SIOCGIFADDR, &ifr) == -1) { syslog(LOG_ALERT, "ioctl: %s", strerror(errno)); break; } - context->ip = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + context->ipv4 = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; // Get mac address if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) { @@ -469,6 +718,30 @@ static int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) close(fd); + // Get v6 network address + memset(&context->ipv6, 0, sizeof(context->ipv6)); + struct ifaddrs *ifa, *ifa_tmp; + + if (getifaddrs(&ifa) == -1) { + syslog(LOG_ALERT, "getifaddrs failed"); + break; + } + + ifa_tmp = ifa; + while (ifa_tmp) { + // Check if current interface has a valid IPv6 address (not link local address) + if ((strncmp(ifa_tmp->ifa_name, context->intf, sizeof(context->intf)) == 0) && + (ifa_tmp->ifa_addr) && + (ifa_tmp->ifa_addr->sa_family == AF_INET6) && + (((struct sockaddr_in6*)(ifa_tmp->ifa_addr))->sin6_addr.__in6_u.__u6_addr16[0] != IPV6_LINK_LOCAL_PREFIX)) { + + struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr; + memcpy(&context->ipv6, &in6->sin6_addr, sizeof(context->ipv6)); + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + rv = 0; } while (0); @@ -476,20 +749,41 @@ static int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) } /** - * @code dhcp_device_get_ip(context); + * @code dhcp_device_get_ipv4(context); * * @brief Accessor method * * @param context pointer to device (interface) context * - * @return interface IP + * @return interface IPv4 */ -int dhcp_device_get_ip(dhcp_device_context_t *context, in_addr_t *ip) +int dhcp_device_get_ipv4(dhcp_device_context_t *context, in_addr_t *ip) { int rv = -1; if (context != NULL && ip != NULL) { - *ip = context->ip; + *ip = context->ipv4; + rv = 0; + } + + return rv; +} + +/** + * @code dhcp_device_get_ipv6(context); + * + * @brief Accessor method + * + * @param context pointer to device (interface) context + * + * @return interface IPv6 + */ +int dhcp_device_get_ipv6(dhcp_device_context_t *context, struct in6_addr *ip) +{ + int rv = -1; + + if (context != NULL && ip != NULL) { + *ip = context->ipv6; rv = 0; } @@ -527,7 +821,8 @@ int dhcp_device_init(dhcp_device_context_t **context, const char *intf, uint8_t dev_context->is_uplink = is_uplink; - memset(dev_context->counters, 0, sizeof(dev_context->counters)); + memset(dev_context->counters.v4counters, 0, sizeof(dev_context->counters.v4counters)); + memset(dev_context->counters.v6counters, 0, sizeof(dev_context->counters.v6counters)); *context = dev_context; rv = 0; @@ -542,14 +837,15 @@ int dhcp_device_init(dhcp_device_context_t **context, const char *intf, uint8_t } /** - * @code dhcp_device_start_capture(context, snaplen, base, vlan_ip); + * @code dhcp_device_start_capture(context, snaplen, base, giaddr_ip, v6_vlan_ip); * * @brief starts packet capture on this interface */ int dhcp_device_start_capture(dhcp_device_context_t *context, size_t snaplen, struct event_base *base, - in_addr_t vlan_ip) + in_addr_t giaddr_ip, + struct in6_addr v6_vlan_ip) { int rv = -1; @@ -559,12 +855,20 @@ int dhcp_device_start_capture(dhcp_device_context_t *context, break; } - if (snaplen < UDP_START_OFFSET + sizeof(struct udphdr) + DHCP_OPTIONS_HEADER_SIZE) { - syslog(LOG_ALERT, "dhcp_device_start_capture(%s): snap length is too low to capture DHCP options", context->intf); + // snaplen check for DHCPv4 size + if (dhcpv4_enabled && snaplen < UDPv4_START_OFFSET + sizeof(struct udphdr) + DHCPv4_OPTIONS_HEADER_SIZE) { + syslog(LOG_ALERT, "dhcp_device_start_capture(%s): snap length is too low to capture DHCPv4 options", context->intf); + break; + } + + // snaplen check for DHCPv6 size - DHCPv6 message type is the first byte of the udp payload + if (dhcpv6_enabled && snaplen < DHCPv6_START_OFFSET + 1) { + syslog(LOG_ALERT, "dhcp_device_start_capture(%s): snap length is too low to capture DHCPv6 option", context->intf); break; } - context->vlan_ip = vlan_ip; + context->giaddr_ip = giaddr_ip; + context->v6_vlan_ip = v6_vlan_ip; context->buffer = (uint8_t *) malloc(snaplen); if (context->buffer == NULL) { @@ -602,17 +906,17 @@ void dhcp_device_shutdown(dhcp_device_context_t *context) } /** - * @code dhcp_device_get_status(check_type, context); + * @code dhcp_device_get_status(check_type, context, type); * * @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_mon_check_t check_type, dhcp_device_context_t *context) +dhcp_mon_status_t dhcp_device_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context, dhcp_type_t type) { dhcp_mon_status_t rv = DHCP_MON_STATUS_HEALTHY; if (context != NULL) { - rv = dhcp_device_check_health(check_type, context->counters); + rv = dhcp_device_check_health(check_type, context->counters.v4counters, context->counters.v6counters, type); } return rv; @@ -626,9 +930,17 @@ dhcp_mon_status_t dhcp_device_get_status(dhcp_mon_check_t check_type, dhcp_devic void dhcp_device_update_snapshot(dhcp_device_context_t *context) { if (context != NULL) { - memcpy(context->counters[DHCP_COUNTERS_SNAPSHOT], - context->counters[DHCP_COUNTERS_CURRENT], - sizeof(context->counters[DHCP_COUNTERS_SNAPSHOT])); + if (dhcpv4_enabled) { + memcpy(context->counters.v4counters[DHCP_COUNTERS_SNAPSHOT], + context->counters.v4counters[DHCP_COUNTERS_CURRENT], + sizeof(context->counters.v4counters[DHCP_COUNTERS_SNAPSHOT])); + } + + if (dhcpv6_enabled) { + memcpy(context->counters.v6counters[DHCP_COUNTERS_SNAPSHOT], + context->counters.v6counters[DHCP_COUNTERS_CURRENT], + sizeof(context->counters.v6counters[DHCP_COUNTERS_SNAPSHOT])); + } } } @@ -640,6 +952,27 @@ void dhcp_device_update_snapshot(dhcp_device_context_t *context) void dhcp_device_print_status(dhcp_device_context_t *context, dhcp_counters_type_t type) { if (context != NULL) { - dhcp_print_counters(context->intf, type, context->counters[type]); + dhcp_print_counters(context->intf, type, context->counters.v4counters[type], context->counters.v6counters[type]); } } + +/** + * @code dhcp_device_active_types(dhcpv4, dhcpv6); + * + * @brief update local variables with active protocols + */ +void dhcp_device_active_types(bool dhcpv4, bool dhcpv6) +{ + dhcpv4_enabled = dhcpv4; + dhcpv6_enabled = dhcpv6; +} + +/** + * @code dhcp_device_init_mgmt_intf(mgmt_intf_context); + * + * @brief assign context address of mgmt interface + */ +void dhcp_device_init_mgmt_intf(dhcp_device_context_t *mgmt_intf_context) +{ + mgmt_intf = mgmt_intf_context; +} diff --git a/src/dhcpmon/src/dhcp_device.h b/src/dhcpmon/src/dhcp_device.h index 133b9265a43..433eb090762 100644 --- a/src/dhcpmon/src/dhcp_device.h +++ b/src/dhcpmon/src/dhcp_device.h @@ -18,21 +18,43 @@ /** - * DHCP message types + * DHCPv4 message types **/ typedef enum { - DHCP_MESSAGE_TYPE_DISCOVER = 1, - DHCP_MESSAGE_TYPE_OFFER = 2, - DHCP_MESSAGE_TYPE_REQUEST = 3, - DHCP_MESSAGE_TYPE_DECLINE = 4, - DHCP_MESSAGE_TYPE_ACK = 5, - DHCP_MESSAGE_TYPE_NAK = 6, - DHCP_MESSAGE_TYPE_RELEASE = 7, - DHCP_MESSAGE_TYPE_INFORM = 8, - - DHCP_MESSAGE_TYPE_COUNT -} dhcp_message_type_t; + DHCPv4_MESSAGE_TYPE_DISCOVER = 1, + DHCPv4_MESSAGE_TYPE_OFFER = 2, + DHCPv4_MESSAGE_TYPE_REQUEST = 3, + DHCPv4_MESSAGE_TYPE_DECLINE = 4, + DHCPv4_MESSAGE_TYPE_ACK = 5, + DHCPv4_MESSAGE_TYPE_NAK = 6, + DHCPv4_MESSAGE_TYPE_RELEASE = 7, + DHCPv4_MESSAGE_TYPE_INFORM = 8, + + DHCPv4_MESSAGE_TYPE_COUNT +} dhcpv4_message_type_t; + +/** + * DHCPv6 message types + **/ +typedef enum +{ + DHCPv6_MESSAGE_TYPE_SOLICIT = 1, + DHCPv6_MESSAGE_TYPE_ADVERTISE = 2, + DHCPv6_MESSAGE_TYPE_REQUEST = 3, + DHCPv6_MESSAGE_TYPE_CONFIRM = 4, + DHCPv6_MESSAGE_TYPE_RENEW = 5, + DHCPv6_MESSAGE_TYPE_REBIND = 6, + DHCPv6_MESSAGE_TYPE_REPLY = 7, + DHCPv6_MESSAGE_TYPE_RELEASE = 8, + DHCPv6_MESSAGE_TYPE_DECLINE = 9, + DHCPv6_MESSAGE_TYPE_RECONFIGURE = 10, + DHCPv6_MESSAGE_TYPE_INFORMATION_REQUEST = 11, + DHCPv6_MESSAGE_TYPE_RELAY_FORWARD = 12, + DHCPv6_MESSAGE_TYPE_RELAY_REPLY = 13, + + DHCPv6_MESSAGE_TYPE_COUNT +} dhcpv6_message_type_t; /** packet direction */ typedef enum @@ -60,6 +82,13 @@ typedef enum DHCP_MON_STATUS_INDETERMINATE, /** DHCP relay health could not be determined */ } dhcp_mon_status_t; +/** dhcp type */ +typedef enum +{ + DHCPv4_TYPE, + DHCPv6_TYPE, +} dhcp_type_t; + /** dhcp check type */ typedef enum { @@ -67,32 +96,64 @@ typedef enum DHCP_MON_CHECK_POSITIVE, /** Validate that received DORA packets are relayed */ } dhcp_mon_check_t; +typedef struct +{ + uint64_t v4counters[DHCP_COUNTERS_COUNT][DHCP_DIR_COUNT][DHCPv4_MESSAGE_TYPE_COUNT]; + /** current/snapshot counters of DHCPv4 packets */ + uint64_t v6counters[DHCP_COUNTERS_COUNT][DHCP_DIR_COUNT][DHCPv6_MESSAGE_TYPE_COUNT]; + /** current/snapshot counters of DHCPv6 packets */ +} 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) */ + in_addr_t ipv4; /** ipv4 network address of this device (interface) */ + struct in6_addr ipv6; /** ipv6 network address of this device (interface) */ uint8_t mac[ETHER_ADDR_LEN]; /** hardware address of this device (interface) */ - in_addr_t vlan_ip; /** Vlan IP address */ + in_addr_t giaddr_ip; /** Gateway IPv4 address */ + struct in6_addr v6_vlan_ip; /** Vlan IPv6 address */ 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 */ - uint64_t counters[DHCP_COUNTERS_COUNT][DHCP_DIR_COUNT][DHCP_MESSAGE_TYPE_COUNT]; - /** current/snapshot counters of DHCP packets */ + counters_t counters; /** counters for DHCPv4/6 packets */ } dhcp_device_context_t; /** - * @code dhcp_device_get_ip(context, ip); + * @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 + */ +int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context); + +/** + * @code dhcp_device_get_ipv4(context, ip); * * @brief Accessor method * * @param context pointer to device (interface) context - * @param ip(out) pointer to device IP + * @param ip(out) pointer to device IPv4 * * @return 0 on success, otherwise for failure */ -int dhcp_device_get_ip(dhcp_device_context_t *context, in_addr_t *ip); +int dhcp_device_get_ipv4(dhcp_device_context_t *context, in_addr_t *ip); + +/** + * @code dhcp_device_get_ipv6(context, ip); + * + * @brief Accessor method + * + * @param context pointer to device (interface) context + * @param ip(out) pointer to device IPv6 + * + * @return 0 on success, otherwise for failure + */ +int dhcp_device_get_ipv6(dhcp_device_context_t *context, struct in6_addr *ip); /** * @code dhcp_device_get_aggregate_context(); @@ -119,21 +180,23 @@ int dhcp_device_init(dhcp_device_context_t **context, uint8_t is_uplink); /** - * @code dhcp_device_start_capture(context, snaplen, base, vlan_ip); + * @code dhcp_device_start_capture(context, snaplen, base, giaddr_ip, v6_vlan_ip); * * @brief starts packet capture on this interface * * @param context pointer to device (interface) context * @param snaplen length of packet capture * @param base pointer to libevent base - * @param vlan_ip vlan IP address + * @param giaddr_ip gateway IP address + * @param v6_vlan_ip vlan IPv6 address * * @return 0 on success, otherwise for failure */ int dhcp_device_start_capture(dhcp_device_context_t *context, size_t snaplen, struct event_base *base, - in_addr_t vlan_ip); + in_addr_t giaddr_ip, + struct in6_addr v6_vlan_ip); /** * @code dhcp_device_shutdown(context); @@ -147,17 +210,18 @@ int dhcp_device_start_capture(dhcp_device_context_t *context, void dhcp_device_shutdown(dhcp_device_context_t *context); /** - * @code dhcp_device_get_status(check_type, context); + * @code dhcp_device_get_status(check_type, context, type); * * @brief collects DHCP relay status info for a given interface. If context is null, it will report aggregate * status * * @param check_type Type of validation * @param context Device (interface) context + * @param type DHCP type * * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE */ -dhcp_mon_status_t dhcp_device_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context); +dhcp_mon_status_t dhcp_device_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context, dhcp_type_t type); /** * @code dhcp_device_update_snapshot(context); @@ -174,10 +238,32 @@ void dhcp_device_update_snapshot(dhcp_device_context_t *context); * @brief prints status counters to syslog. If context is null, it will print aggregate status * * @param context Device (interface) context - * @param counters_type Counter type to be printed + * @param type Counter type to be printed * * @return none */ void dhcp_device_print_status(dhcp_device_context_t *context, dhcp_counters_type_t type); +/** + * @code dhcp_device_active_types(dhcpv4, dhcpv6); + * + * @brief update local variables with active protocols + * + * @param dhcpv4 DHCPv4 enable flag + * @param dhcpv6 DHCPv6 enable flag + * + * @return none + */ +void dhcp_device_active_types(bool dhcpv4, bool dhcpv6); + +/** + * @code dhcp_device_init_mgmt_intf(mgmt_intf_context); + * + * @brief assign context address of mgmt interface + * + * @param mgmt_intf_context MGMT interface context struct address + * + * @return none + */ +void dhcp_device_init_mgmt_intf(dhcp_device_context_t *mgmt_intf_context); #endif /* DHCP_DEVICE_H_ */ diff --git a/src/dhcpmon/src/dhcp_devman.c b/src/dhcpmon/src/dhcp_devman.c index 35378a631ca..b36d926c1d5 100644 --- a/src/dhcpmon/src/dhcp_devman.c +++ b/src/dhcpmon/src/dhcp_devman.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "dhcp_devman.h" @@ -35,7 +37,14 @@ static uint32_t dhcp_num_mgmt_intf = 0; /** On Device vlan interface IP address corresponding vlan downlink IP * This IP is used to filter Offer/Ack packet coming from DHCP server */ -static in_addr_t vlan_ip = 0; +static in_addr_t v4_vlan_ip = 0; +static struct in6_addr v6_vlan_ip = {0}; + +/* Device loopback interface ip, which will be used as the giaddr in dual tor setup. */ +static in_addr_t loopback_ip = 0; + +/* Whether the device is in dual tor mode, 0 as default for single tor mode. */ +static int dual_tor_mode = 0; /** mgmt interface */ static struct intf *mgmt_intf = NULL; @@ -130,7 +139,8 @@ int dhcp_devman_add_intf(const char *name, char intf_type) rv = dhcp_device_init(&dev->dev_context, dev->name, dev->is_uplink); if (rv == 0 && intf_type == 'd') { - rv = dhcp_device_get_ip(dev->dev_context, &vlan_ip); + rv = dhcp_device_get_ipv4(dev->dev_context, &v4_vlan_ip); + rv = dhcp_device_get_ipv6(dev->dev_context, &v6_vlan_ip); dhcp_device_context_t *agg_dev = dhcp_device_get_aggregate_context(); @@ -138,6 +148,9 @@ int dhcp_devman_add_intf(const char *name, char intf_type) strncpy(agg_dev->intf + sizeof(AGG_DEV_PREFIX) - 1, name, sizeof(agg_dev->intf) - sizeof(AGG_DEV_PREFIX)); agg_dev->intf[sizeof(agg_dev->intf) - 1] = '\0'; } + else if (rv == 0 && intf_type == 'm') { + dhcp_device_init_mgmt_intf(dev->dev_context); + } LIST_INSERT_HEAD(&intfs, dev, entry); } @@ -148,6 +161,37 @@ int dhcp_devman_add_intf(const char *name, char intf_type) return rv; } +/** + * @code dhcp_devman_setup_dual_tor_mode(name); + * + * @brief set up dual tor mode: 1) set dual_tor_mode flag and 2) retrieve loopback_ip. + */ +int dhcp_devman_setup_dual_tor_mode(const char *name) +{ + int rv = -1; + + dhcp_device_context_t loopback_intf_context; + + if (strlen(name) < sizeof(loopback_intf_context.intf)) { + strncpy(loopback_intf_context.intf, name, sizeof(loopback_intf_context.intf) - 1); + loopback_intf_context.intf[sizeof(loopback_intf_context.intf) - 1] = '\0'; + } else { + syslog(LOG_ALERT, "loopback interface name (%s) is too long", name); + return rv; + } + + if (initialize_intf_mac_and_ip_addr(&loopback_intf_context) == 0 && + dhcp_device_get_ipv4(&loopback_intf_context, &loopback_ip) == 0) { + dual_tor_mode = 1; + } else { + syslog(LOG_ALERT, "failed to retrieve ip addr for loopback interface (%s)", name); + return rv; + } + + rv = 0; + return rv; +} + /** * @code dhcp_devman_start_capture(snaplen, base); * @@ -160,11 +204,13 @@ int dhcp_devman_start_capture(size_t snaplen, struct event_base *base) if ((dhcp_num_south_intf == 1) && (dhcp_num_north_intf >= 1)) { LIST_FOREACH(int_ptr, &intfs, entry) { - rv = dhcp_device_start_capture(int_ptr->dev_context, snaplen, base, vlan_ip); + rv = dhcp_device_start_capture(int_ptr->dev_context, snaplen, base, dual_tor_mode ? loopback_ip : v4_vlan_ip, v6_vlan_ip); if (rv == 0) { + char ipv6_addr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &int_ptr->dev_context->ipv6, ipv6_addr, sizeof(ipv6_addr)); 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], + "Capturing DHCP packets on interface %s, ipv4: 0x%08x, ipv6: %s, mac [%02x:%02x:%02x:%02x:%02x:%02x] \n", + int_ptr->name, int_ptr->dev_context->ipv4, ipv6_addr, 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]); } @@ -182,13 +228,13 @@ int dhcp_devman_start_capture(size_t snaplen, struct event_base *base) } /** - * @code dhcp_devman_get_status(check_type, context); + * @code dhcp_devman_get_status(check_type, context, type); * * @brief collects DHCP relay status info. */ -dhcp_mon_status_t dhcp_devman_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context) +dhcp_mon_status_t dhcp_devman_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context, dhcp_type_t type) { - return dhcp_device_get_status(check_type, context); + return dhcp_device_get_status(check_type, context, type); } /** @@ -230,3 +276,13 @@ void dhcp_devman_print_status(dhcp_device_context_t *context, dhcp_counters_type dhcp_device_print_status(context, type); } } + +/** + * @code dhcp_devman_active_types(dhcpv4, dhcpv6); + * + * @brief update local variables with active protocols + */ +void dhcp_devman_active_types(bool dhcpv4, bool dhcpv6) +{ + dhcp_device_active_types(dhcpv4, dhcpv6); +} diff --git a/src/dhcpmon/src/dhcp_devman.h b/src/dhcpmon/src/dhcp_devman.h index bba7076902c..6e30c654f69 100644 --- a/src/dhcpmon/src/dhcp_devman.h +++ b/src/dhcpmon/src/dhcp_devman.h @@ -63,6 +63,17 @@ dhcp_device_context_t* dhcp_devman_get_mgmt_dev(); */ int dhcp_devman_add_intf(const char *name, char intf_type); +/** + * @code dhcp_devman_setup_dual_tor_mode(name); + * + * @brief set up dual tor mode: 1) set dual_tor_mode flag and 2) retrieve loopback_ip. + * + * @param name interface name + * + * @return 0 on success, nonzero otherwise + */ +int dhcp_devman_setup_dual_tor_mode(const char *name); + /** * @code dhcp_devman_start_capture(snaplen, base); * @@ -76,16 +87,17 @@ int dhcp_devman_add_intf(const char *name, char intf_type); int dhcp_devman_start_capture(size_t snaplen, struct event_base *base); /** - * @code dhcp_devman_get_status(check_type, context); + * @code dhcp_devman_get_status(check_type, context, type); * * @brief collects DHCP relay status info. * * @param check_type Type of validation * @param context pointer to device (interface) context + * @param type DHCP type * * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE */ -dhcp_mon_status_t dhcp_devman_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context); +dhcp_mon_status_t dhcp_devman_get_status(dhcp_mon_check_t check_type, dhcp_device_context_t *context, dhcp_type_t type); /** * @code dhcp_devman_update_snapshot(context); @@ -108,4 +120,16 @@ void dhcp_devman_update_snapshot(dhcp_device_context_t *context); */ void dhcp_devman_print_status(dhcp_device_context_t *context, dhcp_counters_type_t type); +/** + * @code dhcp_devman_active_types(dhcpv4, dhcpv6); + * + * @brief update local variables with active protocols + * + * @param dhcpv4 flag indicating dhcpv4 is enabled + * @param dhcpv6 flag indicating dhcpv6 is enabled + * + * @return none + */ +void dhcp_devman_active_types(bool dhcpv4, bool dhcpv6); + #endif /* DHCP_DEVMAN_H_ */ diff --git a/src/dhcpmon/src/dhcp_mon.c b/src/dhcpmon/src/dhcp_mon.c index 74d9869741d..2777b648408 100644 --- a/src/dhcpmon/src/dhcp_mon.c +++ b/src/dhcpmon/src/dhcp_mon.c @@ -12,16 +12,24 @@ #include #include #include +#include #include "dhcp_mon.h" #include "dhcp_devman.h" +/** DHCP device/interface state counters */ +typedef struct +{ + int v4_count; /** count in the number of DHCPv4 unhealthy checks */ + int v6_count; /** count in the number of DHCPv6 unhealthy checks */ +} dhcp_mon_count_t; + /** DHCP device/interface state */ typedef struct { dhcp_mon_check_t check_type; /** check type */ dhcp_device_context_t* (*get_context)(); /** functor to a device context accessor function */ - int count; /** count in the number of unhealthy checks */ + dhcp_mon_count_t counters; /** count in the number of unhealthy checks */ const char *msg; /** message to be printed if unhealthy state is determined */ } dhcp_mon_state_t; @@ -29,6 +37,10 @@ typedef struct static int window_interval_sec = 18; /** dhcp_unhealthy_max_count max count of consecutive unhealthy statuses before reporting to syslog */ static int dhcp_unhealthy_max_count = 10; +/** DHCP versions flags */ +static bool dhcpv4_enabled; +static bool dhcpv6_enabled; + /** libevent base struct */ static struct event_base *base; /** libevent timeout event struct */ @@ -45,13 +57,15 @@ static dhcp_mon_state_t state_data[] = { [0] = { .check_type = DHCP_MON_CHECK_POSITIVE, .get_context = dhcp_devman_get_agg_dev, - .count = 0, + .counters.v4_count = 0, + .counters.v6_count = 0, .msg = "dhcpmon detected disparity in DHCP Relay behavior. Duration: %d (sec) for vlan: '%s'\n" }, [1] = { .check_type = DHCP_MON_CHECK_NEGATIVE, .get_context = dhcp_devman_get_mgmt_dev, - .count = 0, + .counters.v4_count = 0, + .counters.v6_count = 0, .msg = "dhcpmon detected DHCP packets traveling through mgmt interface (please check BGP routes.)" " Duration: %d (sec) for intf: '%s'\n" } @@ -78,36 +92,89 @@ static void signal_callback(evutil_socket_t fd, short event, void *arg) } /** - * @code check_dhcp_relay_health(state_data); + * @code check_dhcp_relay_health(state_data, dhcp_type); * * @brief check DHCP relay overall health * * @param state_data pointer to dhcpmon state data * + * @param type DHCP type + * * @return none */ -static void check_dhcp_relay_health(dhcp_mon_state_t *state_data) +static void check_dhcp_relay_health(dhcp_mon_state_t *state_data, dhcp_type_t type) { dhcp_device_context_t *context = state_data->get_context(); - dhcp_mon_status_t dhcp_mon_status = dhcp_devman_get_status(state_data->check_type, context); + dhcp_mon_status_t dhcp_mon_status = dhcp_devman_get_status(state_data->check_type, context, type); switch (dhcp_mon_status) { case DHCP_MON_STATUS_UNHEALTHY: - if (++state_data->count > dhcp_unhealthy_max_count) { - syslog(LOG_ALERT, state_data->msg, state_data->count * window_interval_sec, context->intf); - dhcp_devman_print_status(context, DHCP_COUNTERS_SNAPSHOT); - dhcp_devman_print_status(context, DHCP_COUNTERS_CURRENT); + switch (type) + { + case DHCPv4_TYPE: + if (++state_data->counters.v4_count > dhcp_unhealthy_max_count) { + syslog(LOG_ALERT, state_data->msg, state_data->counters.v4_count * window_interval_sec, context->intf); + dhcp_devman_print_status(context, DHCP_COUNTERS_SNAPSHOT); + dhcp_devman_print_status(context, DHCP_COUNTERS_CURRENT); + } + break; + case DHCPv6_TYPE: + if (++state_data->counters.v6_count > dhcp_unhealthy_max_count) { + syslog(LOG_ALERT, state_data->msg, state_data->counters.v6_count * window_interval_sec, context->intf); + dhcp_devman_print_status(context, DHCP_COUNTERS_SNAPSHOT); + dhcp_devman_print_status(context, DHCP_COUNTERS_CURRENT); + } + break; + + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; } break; + case DHCP_MON_STATUS_HEALTHY: - state_data->count = 0; + switch (type) + { + case DHCPv4_TYPE: + state_data->counters.v4_count = 0; + break; + case DHCPv6_TYPE: + state_data->counters.v6_count = 0; + break; + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; + } break; + case DHCP_MON_STATUS_INDETERMINATE: - if (state_data->count) { - state_data->count++; + switch (type) + { + case DHCPv4_TYPE: + if (state_data->counters.v4_count) { + if (++state_data->counters.v4_count > dhcp_unhealthy_max_count) { + syslog(LOG_ALERT, state_data->msg, state_data->counters.v4_count * window_interval_sec, context->intf); + dhcp_devman_print_status(context, DHCP_COUNTERS_SNAPSHOT); + dhcp_devman_print_status(context, DHCP_COUNTERS_CURRENT); + } + } + break; + case DHCPv6_TYPE: + if (state_data->counters.v6_count) { + if (++state_data->counters.v6_count > dhcp_unhealthy_max_count) { + syslog(LOG_ALERT, state_data->msg, state_data->counters.v6_count * window_interval_sec, context->intf); + dhcp_devman_print_status(context, DHCP_COUNTERS_SNAPSHOT); + dhcp_devman_print_status(context, DHCP_COUNTERS_CURRENT); + } + } + break; + default: + syslog(LOG_ERR, "Unknown DHCP type %d\n", type); + break; } break; + default: syslog(LOG_ERR, "DHCP Relay returned unknown status %d\n", dhcp_mon_status); break; @@ -128,7 +195,12 @@ static void check_dhcp_relay_health(dhcp_mon_state_t *state_data) static void timeout_callback(evutil_socket_t fd, short event, void *arg) { for (uint8_t i = 0; i < sizeof(state_data) / sizeof(*state_data); i++) { - check_dhcp_relay_health(&state_data[i]); + if (dhcpv4_enabled) { + check_dhcp_relay_health(&state_data[i], DHCPv4_TYPE); + } + if (dhcpv6_enabled) { + check_dhcp_relay_health(&state_data[i], DHCPv6_TYPE); + } } dhcp_devman_update_snapshot(NULL); @@ -141,13 +213,17 @@ static void timeout_callback(evutil_socket_t fd, short event, void *arg) * 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 dhcp_mon_init(int window_sec, int max_count, bool dhcpv4, bool dhcpv6) { int rv = -1; do { window_interval_sec = window_sec; dhcp_unhealthy_max_count = max_count; + dhcpv4_enabled = dhcpv4; + dhcpv6_enabled = dhcpv6; + + dhcp_devman_active_types(dhcpv4, dhcpv6); base = event_base_new(); if (base == NULL) { diff --git a/src/dhcpmon/src/dhcp_mon.h b/src/dhcpmon/src/dhcp_mon.h index ae8911ab51f..facd46da686 100644 --- a/src/dhcpmon/src/dhcp_mon.h +++ b/src/dhcpmon/src/dhcp_mon.h @@ -17,10 +17,12 @@ * * @param window_sec time interval between health checks * @param max_count max count of consecutive unhealthy statuses before reporting to syslog + * @param dhcpv4 flag indicating dhcpv4 is enabled + * @param dhcpv6 flag indicating dhcpv6 is enabled * * @return 0 upon success, otherwise upon failure */ -int dhcp_mon_init(int window_sec, int max_count); +int dhcp_mon_init(int window_sec, int max_count, bool dhcpv4, bool dhcpv6); /** * @code dhcp_mon_shutdown(); diff --git a/src/dhcpmon/src/main.c b/src/dhcpmon/src/main.c index bb8c45867f6..1cf93182149 100644 --- a/src/dhcpmon/src/main.c +++ b/src/dhcpmon/src/main.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "dhcp_mon.h" #include "dhcp_devman.h" @@ -40,11 +41,13 @@ static const uint32_t dhcpmon_default_unhealthy_max_count = 10; */ static void usage(const char *prog) { - printf("Usage: %s -id {-iu }+ -im [-w ]" - "[-c ] [-s ] [-d]\n", prog); + printf("Usage: %s -id {-iu }+ -im [-u ]" + "[-w ] [-c ] [-s ]" + "[-4 ] [-6 ] [-d]\n", prog); printf("where\n"); printf("\tsouth interface: is a vlan interface,\n"); printf("\tnorth interface: is a TOR-T1 interface,\n"); + printf("\tloopback interface: is the loopback interface for dual tor setup,\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 " @@ -111,6 +114,8 @@ int main(int argc, char **argv) int max_unhealthy_count = dhcpmon_default_unhealthy_max_count; size_t snaplen = dhcpmon_default_snaplen; int make_daemon = 0; + bool dhcpv4_enabled = false; + bool dhcpv6_enabled = false; setlogmask(LOG_UPTO(LOG_INFO)); openlog(basename(argv[0]), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON); @@ -132,6 +137,12 @@ int main(int argc, char **argv) } i += 2; break; + case 'u': + if (dhcp_devman_setup_dual_tor_mode(argv[i + 1]) != 0) { + usage(basename(argv[0])); + } + i += 2; + break; case 'd': make_daemon = 1; i++; @@ -148,6 +159,14 @@ int main(int argc, char **argv) max_unhealthy_count = atoi(argv[i + 1]); i += 2; break; + case '4': + dhcpv4_enabled = true; + i++; + break; + case '6': + dhcpv6_enabled = true; + i++; + break; default: fprintf(stderr, "%s: %c: Unknown option\n", basename(argv[0]), argv[i][1]); usage(basename(argv[0])); @@ -158,7 +177,11 @@ int main(int argc, char **argv) dhcpmon_daemonize(); } - if ((dhcp_mon_init(window_interval, max_unhealthy_count) == 0) && + if (!dhcpv4_enabled && !dhcpv6_enabled) { + dhcpv4_enabled = true; + } + + if ((dhcp_mon_init(window_interval, max_unhealthy_count, dhcpv4_enabled, dhcpv6_enabled) == 0) && (dhcp_mon_start(snaplen) == 0)) { rv = EXIT_SUCCESS; diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 295f80ecb2c..97848782b9f 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -204,6 +204,7 @@ def parse_dpg(dpg, hname): vlan_intfs = [] vlans = {} vlan_members = {} + dhcp_relay_table = {} for vintf in vlanintfs.findall(str(QName(ns, "VlanInterface"))): vintfname = vintf.find(str(QName(ns, "Name"))).text vlanid = vintf.find(str(QName(ns, "VlanID"))).text @@ -215,6 +216,7 @@ def parse_dpg(dpg, hname): vlan_members[(sonic_vlan_member_name, vmbr_list[i])] = {'tagging_mode': 'untagged'} vlan_attributes = {'vlanid': vlanid} + dhcp_attributes = {} # If this VLAN requires a DHCP relay agent, it will contain a element # containing a list of DHCP server IPs @@ -229,12 +231,15 @@ def parse_dpg(dpg, hname): vintfdhcpservers = vintf_node.text vdhcpserver_list = vintfdhcpservers.split(';') vlan_attributes['dhcpv6_servers'] = vdhcpserver_list + dhcp_attributes['dhcpv6_servers'] = vdhcpserver_list + sonic_vlan_member_name = "Vlan%s" % (vlanid) + dhcp_relay_table[sonic_vlan_member_name] = dhcp_attributes sonic_vlan_name = "Vlan%s" % vlanid if sonic_vlan_name != vintfname: vlan_attributes['alias'] = vintfname vlans[sonic_vlan_name] = vlan_attributes - + aclintfs = child.find(str(QName(ns, "AclInterfaces"))) acls = {} for aclintf in aclintfs.findall(str(QName(ns, "AclInterface"))): @@ -320,7 +325,7 @@ def parse_dpg(dpg, hname): except: print >> sys.stderr, "Warning: Ignoring Control Plane ACL %s without type" % aclname - return intfs, lo_intfs, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls + return intfs, lo_intfs, mgmt_intf, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls return None, None, None, None, None, None, None @@ -532,6 +537,7 @@ def parse_xml(filename, platform=None, port_config_file=None): pc_intfs = None vlans = None vlan_members = None + dhcp_relay_table = None pcs = None mgmt_intf = None lo_intf = None @@ -569,7 +575,7 @@ def parse_xml(filename, platform=None, port_config_file=None): port_alias_map.update(alias_map) for child in root: if child.tag == str(QName(ns, "DpgDec")): - (intfs, lo_intfs, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls) = parse_dpg(child, hostname) + (intfs, lo_intfs, mgmt_intf, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls) = parse_dpg(child, hostname) elif child.tag == str(QName(ns, "CpgDec")): (bgp_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname) elif child.tag == str(QName(ns, "PngDec")): @@ -743,7 +749,7 @@ def parse_xml(filename, platform=None, port_config_file=None): results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key.lower() != hostname.lower() } results['SYSLOG_SERVER'] = dict((item, {}) for item in syslog_servers) results['DHCP_SERVER'] = dict((item, {}) for item in dhcp_servers) - results['DHCPv6_SERVER'] = dict((item, {}) for item in dhcpv6_servers) + results['DHCP_RELAY'] = dhcp_relay_table results['NTP_SERVER'] = dict((item, {}) for item in ntp_servers) results['TACPLUS_SERVER'] = dict((item, {'priority': '1', 'tcp_port': '49'}) for item in tacacs_servers) diff --git a/src/sonic-config-engine/tests/sample_output/docker-dhcp-relay.supervisord.conf b/src/sonic-config-engine/tests/sample_output/docker-dhcp-relay.supervisord.conf index d63d7af15ac..0ac7d8f5cb0 100644 --- a/src/sonic-config-engine/tests/sample_output/docker-dhcp-relay.supervisord.conf +++ b/src/sonic-config-engine/tests/sample_output/docker-dhcp-relay.supervisord.conf @@ -19,10 +19,10 @@ autorestart=false stdout_logfile=syslog stderr_logfile=syslog -[group:isc-dhcp-relay] -programs=isc-dhcp-relay-Vlan1000 +[group:dhcp-relay] +programs=isc-dhcpv4-relay-Vlan1000,dhcp6relay -[program:isc-dhcp-relay-Vlan1000] +[program:isc-dhcpv4-relay-Vlan1000] command=/usr/sbin/dhcrelay -d -m discard -a %%h:%%p %%P --name-alias-map-file /tmp/port-name-alias-map.txt -id Vlan1000 -iu PortChannel01 -iu PortChannel02 -iu PortChannel03 -iu PortChannel04 192.0.0.1 192.0.0.2 priority=3 autostart=false @@ -30,6 +30,15 @@ autorestart=false stdout_logfile=syslog stderr_logfile=syslog +[program:dhcp6relay] +command=/usr/sbin/dhcp6relay + +priority=3 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog + [group:dhcpmon] programs=dhcpmon-Vlan1000 diff --git a/src/sonic-config-engine/tests/sample_output/wait_for_intf.sh b/src/sonic-config-engine/tests/sample_output/wait_for_intf.sh index 918f0b6e164..4d530a6c13e 100644 --- a/src/sonic-config-engine/tests/sample_output/wait_for_intf.sh +++ b/src/sonic-config-engine/tests/sample_output/wait_for_intf.sh @@ -31,3 +31,7 @@ wait_until_iface_ready PortChannel02 10.0.0.58/31 wait_until_iface_ready PortChannel03 10.0.0.60/31 wait_until_iface_ready PortChannel04 10.0.0.62/31 +# Wait 10 seconds for the rest of interfaces to get added/populated. +# dhcrelay listens on each of the interfaces (in addition to the port +# channels and vlan interfaces) +sleep 10 diff --git a/src/sonic-config-engine/tests/simple-sample-graph-case.xml b/src/sonic-config-engine/tests/simple-sample-graph-case.xml index ed4efa905a2..3074e1cabee 100644 --- a/src/sonic-config-engine/tests/simple-sample-graph-case.xml +++ b/src/sonic-config-engine/tests/simple-sample-graph-case.xml @@ -131,6 +131,7 @@ ab1 fortyGigE0/8 192.0.0.1;192.0.0.2 + fc02:2000::1;fc02:2000::2 1000 1000 192.168.0.0/27 diff --git a/src/sonic-config-engine/tests/test_cfggen.py b/src/sonic-config-engine/tests/test_cfggen.py index 54fd47308d0..0556f219ad3 100644 --- a/src/sonic-config-engine/tests/test_cfggen.py +++ b/src/sonic-config-engine/tests/test_cfggen.py @@ -10,6 +10,7 @@ def setUp(self): self.sample_graph = os.path.join(self.test_dir, 'sample_graph.xml') self.sample_graph_t0 = os.path.join(self.test_dir, 't0-sample-graph.xml') self.sample_graph_simple = os.path.join(self.test_dir, 'simple-sample-graph.xml') + self.sample_graph_simple_case = os.path.join(self.test_dir, 'simple-sample-graph-case.xml') self.sample_graph_metadata = os.path.join(self.test_dir, 'simple-sample-graph-metadata.xml') self.sample_graph_pc_test = os.path.join(self.test_dir, 'pc-test-graph.xml') self.sample_graph_bgp_speaker = os.path.join(self.test_dir, 't0-sample-bgp-speaker.xml') @@ -333,3 +334,12 @@ def test_minigraph_bgp_mon(self): argument = '-m "' + self.sample_graph_simple + '" -p "' + self.port_config + '" -v "BGP_MONITORS"' output = self.run_script(argument) self.assertEqual(output.strip(), "{'10.20.30.40': {'rrclient': 0, 'name': 'BGPMonitor', 'local_addr': '10.1.0.32', 'nhopself': 0, 'holdtime': '10', 'asn': '0', 'keepalive': '3'}}") + + def test_minigraph_dhcp(self): + argument = '-m "' + self.sample_graph_simple_case + '" -p "' + self.port_config + '" -v DHCP_RELAY' + output = self.run_script(argument) + self.assertEqual( + output.strip(), + "{'Vlan1000': {'dhcpv6_servers': ['fc02:2000::1', 'fc02:2000::2']}}" + ) + diff --git a/src/sonic-config-engine/tests/test_minigraph_case.py b/src/sonic-config-engine/tests/test_minigraph_case.py index 222b3571923..a89747ae54d 100644 --- a/src/sonic-config-engine/tests/test_minigraph_case.py +++ b/src/sonic-config-engine/tests/test_minigraph_case.py @@ -73,7 +73,7 @@ def test_minigraph_interfaces(self): def test_minigraph_vlans(self): argument = '-m "' + self.sample_graph + '" -p "' + self.port_config + '" -v VLAN' output = self.run_script(argument) - self.assertEqual(output.strip(), "{'Vlan1000': {'alias': 'ab1', 'dhcp_servers': ['192.0.0.1', '192.0.0.2'], 'vlanid': '1000'}}") + self.assertEqual(output.strip(), "{'Vlan1000': {'alias': 'ab1', 'dhcp_servers': ['192.0.0.1', '192.0.0.2'], 'vlanid': '1000', 'dhcpv6_servers': ['fc02:2000::1', 'fc02:2000::2']}}") def test_minigraph_vlan_members(self): argument = '-m "' + self.sample_graph + '" -p "' + self.port_config + '" -v VLAN_MEMBER' @@ -138,3 +138,13 @@ def test_minigraph_mirror_dscp(self): everflow_dscp_entry['ports'].sort(), expected_ports.sort() ) + + def test_dhcp_table(self): + argument = '-m "' + self.sample_graph + '" -p "' + self.port_config + '" -v "DHCP_RELAY"' + output = self.run_script(argument) + self.assertEqual( + output.strip(), + "{'Vlan1000': {'dhcpv6_servers': ['fc02:2000::1', 'fc02:2000::2']}}" + ) + +