[MERGED] openggsn[master]: IPv6: Implement IPv6 prefix assignment via ICMPv6 router adv...

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Harald Welte gerrit-no-reply at lists.osmocom.org
Fri Aug 11 11:13:28 UTC 2017


Harald Welte has submitted this change and it was merged.

Change subject: IPv6: Implement IPv6 prefix assignment via ICMPv6 router advertisement
......................................................................


IPv6: Implement IPv6 prefix assignment via ICMPv6 router advertisement

The 3GPP specs are quite strange when it comes to how an IPv6 address
or rather prefix is assigned to an IPv6 PDP context.  The designated
method for allocating the IPv6 address via the PDP EUA (End User
Address) Information Element in the GTP signalling plane is *not*
used to allocate the address/prefix.  Instead, the EUA is used to
allocate an "interface identifier" to the MS, which it the uses
to derive its link-local source address to send a router solicitation.

The GGSN subsequently answers witha router advertisement, advertising
a single/64 prefix, whihcthe MS then uses to generate it's real IPv6
source address for subsequent communication.

Change-Id: Icddf7d30e01d76a4784bcef5787b36f52f703a9f
---
M ggsn/Makefile.am
A ggsn/checksum.c
A ggsn/checksum.h
M ggsn/ggsn.c
A ggsn/icmpv6.c
A ggsn/icmpv6.h
M lib/debug.c
M lib/syserr.h
8 files changed, 510 insertions(+), 2 deletions(-)

Approvals:
  Harald Welte: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am
index 3ad3a6e..c945f0b 100644
--- a/ggsn/Makefile.am
+++ b/ggsn/Makefile.am
@@ -12,7 +12,7 @@
 endif
 
 ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
-ggsn_SOURCES = ggsn.c cmdline.c cmdline.h gtp-kernel.h
+ggsn_SOURCES = ggsn.c cmdline.c cmdline.h gtp-kernel.h icmpv6.c icmpv6.h checksum.c checksum.h
 
 if ENABLE_GTP_KERNEL
 ggsn_SOURCES += gtp-kernel.c
diff --git a/ggsn/checksum.c b/ggsn/checksum.c
new file mode 100644
index 0000000..4b23897
--- /dev/null
+++ b/ggsn/checksum.c
@@ -0,0 +1,211 @@
+/*
+ *
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		IP/TCP/UDP checksumming routines
+ *
+ * Authors:	Jorge Cwik, <jorge at laser.satlink.net>
+ *		Arnt Gulbrandsen, <agulbra at nvg.unit.no>
+ *		Tom May, <ftom at netcom.com>
+ *		Andreas Schwab, <schwab at issan.informatik.uni-dortmund.de>
+ *		Lots of code moved from tcp.c and ip.c; see those files
+ *		for more names.
+ *
+ * 03/02/96	Jes Sorensen, Andreas Schwab, Roman Hodek:
+ *		Fixed some nasty bugs, causing some horrible crashes.
+ *		A: At some points, the sum (%0) was used as
+ *		length-counter instead of the length counter
+ *		(%1). Thanks to Roman Hodek for pointing this out.
+ *		B: GCC seems to mess up if one uses too many
+ *		data-registers to hold input values and one tries to
+ *		specify d0 and d1 as scratch registers. Letting gcc
+ *		choose these registers itself solves the problem.
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+
+/* Revised by Kenneth Albanowski for m68knommu. Basic problem: unaligned access
+ kills, so most of the assembly has to go. */
+
+#if defined(__FreeBSD__)
+#define _KERNEL	/* needed on FreeBSD 10.x for s6_addr32 */
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/endian.h>
+#endif
+
+#include "checksum.h"
+#include <arpa/inet.h>
+
+static inline unsigned short from32to16(unsigned int x)
+{
+	/* add up 16-bit and 16-bit for 16+c bit */
+	x = (x & 0xffff) + (x >> 16);
+	/* add up carry.. */
+	x = (x & 0xffff) + (x >> 16);
+	return x;
+}
+
+static unsigned int do_csum(const unsigned char *buff, int len)
+{
+	int odd;
+	unsigned int result = 0;
+
+	if (len <= 0)
+		goto out;
+	odd = 1 & (unsigned long) buff;
+	if (odd) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+		result += (*buff << 8);
+#else
+		result = *buff;
+#endif
+		len--;
+		buff++;
+	}
+	if (len >= 2) {
+		if (2 & (unsigned long) buff) {
+			result += *(unsigned short *) buff;
+			len -= 2;
+			buff += 2;
+		}
+		if (len >= 4) {
+			const unsigned char *end = buff + ((unsigned)len & ~3);
+			unsigned int carry = 0;
+			do {
+				unsigned int w = *(unsigned int *) buff;
+				buff += 4;
+				result += carry;
+				result += w;
+				carry = (w > result);
+			} while (buff < end);
+			result += carry;
+			result = (result & 0xffff) + (result >> 16);
+		}
+		if (len & 2) {
+			result += *(unsigned short *) buff;
+			buff += 2;
+		}
+	}
+	if (len & 1)
+#if BYTE_ORDER == LITTLE_ENDIAN
+		result += *buff;
+#else
+		result += (*buff << 8);
+#endif
+	result = from32to16(result);
+	if (odd)
+		result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
+out:
+	return result;
+}
+
+/*
+ *	This is a version of ip_compute_csum() optimized for IP headers,
+ *	which always checksum on 4 octet boundaries.
+ */
+uint16_t ip_fast_csum(const void *iph, unsigned int ihl)
+{
+	return (uint16_t)~do_csum(iph, ihl*4);
+}
+
+/*
+ * computes the checksum of a memory block at buff, length len,
+ * and adds in "sum" (32-bit)
+ *
+ * returns a 32-bit number suitable for feeding into itself
+ * or csum_tcpudp_magic
+ *
+ * this function must be called with even lengths, except
+ * for the last fragment, which may be odd
+ *
+ * it's best to have buff aligned on a 32-bit boundary
+ */
+uint32_t csum_partial(const void *buff, int len, uint32_t wsum)
+{
+	unsigned int sum = (unsigned int)wsum;
+	unsigned int result = do_csum(buff, len);
+
+	/* add in old sum, and carry.. */
+	result += sum;
+	if (sum > result)
+		result += 1;
+	return (uint32_t)result;
+}
+
+/*
+ * this routine is used for miscellaneous IP-like checksums, mainly
+ * in icmp.c
+ */
+uint16_t ip_compute_csum(const void *buff, int len)
+{
+	return (uint16_t)~do_csum(buff, len);
+}
+
+uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
+			const struct in6_addr *daddr,
+			uint32_t len, uint8_t proto, uint32_t csum)
+{
+	int carry;
+	uint32_t ulen;
+	uint32_t uproto;
+	uint32_t sum = (uint32_t)csum;
+
+	sum += (uint32_t)saddr->s6_addr32[0];
+	carry = (sum < (uint32_t)saddr->s6_addr32[0]);
+	sum += carry;
+
+	sum += (uint32_t)saddr->s6_addr32[1];
+	carry = (sum < (uint32_t)saddr->s6_addr32[1]);
+	sum += carry;
+
+	sum += (uint32_t)saddr->s6_addr32[2];
+	carry = (sum < (uint32_t)saddr->s6_addr32[2]);
+	sum += carry;
+
+	sum += (uint32_t)saddr->s6_addr32[3];
+	carry = (sum < (uint32_t)saddr->s6_addr32[3]);
+	sum += carry;
+
+	sum += (uint32_t)daddr->s6_addr32[0];
+	carry = (sum < (uint32_t)daddr->s6_addr32[0]);
+	sum += carry;
+
+	sum += (uint32_t)daddr->s6_addr32[1];
+	carry = (sum < (uint32_t)daddr->s6_addr32[1]);
+	sum += carry;
+
+	sum += (uint32_t)daddr->s6_addr32[2];
+	carry = (sum < (uint32_t)daddr->s6_addr32[2]);
+	sum += carry;
+
+	sum += (uint32_t)daddr->s6_addr32[3];
+	carry = (sum < (uint32_t)daddr->s6_addr32[3]);
+	sum += carry;
+
+	ulen = (uint32_t)htonl((uint32_t) len);
+	sum += ulen;
+	carry = (sum < ulen);
+	sum += carry;
+
+	uproto = (uint32_t)htonl(proto);
+	sum += uproto;
+	carry = (sum < uproto);
+	sum += carry;
+
+	return csum_fold((uint32_t)sum);
+}
+
+/* fold a partial checksum */
+uint16_t csum_fold(uint32_t csum)
+{
+	uint32_t sum = (uint32_t)csum;
+	sum = (sum & 0xffff) + (sum >> 16);
+	sum = (sum & 0xffff) + (sum >> 16);
+	return (uint16_t)~sum;
+}
diff --git a/ggsn/checksum.h b/ggsn/checksum.h
new file mode 100644
index 0000000..4b22431
--- /dev/null
+++ b/ggsn/checksum.h
@@ -0,0 +1,13 @@
+#pragma once
+#include <stdint.h>
+#include <netinet/in.h>
+
+uint16_t ip_fast_csum(const void *iph, unsigned int ihl);
+uint32_t csum_partial(const void *buff, int len, uint32_t wsum);
+uint16_t ip_compute_csum(const void *buff, int len);
+
+uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
+			const struct in6_addr *daddr,
+			uint32_t len, uint8_t proto, uint32_t csum);
+
+uint16_t csum_fold(uint32_t csum);
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 9b11884..c307177 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -64,6 +64,7 @@
 #include "../gtp/gtp.h"
 #include "cmdline.h"
 #include "gtp-kernel.h"
+#include "icmpv6.h"
 
 int end = 0;
 int maxfd = 0;			/* For select()            */
@@ -206,11 +207,23 @@
 		return 0;	/* Allready in use, or no more available */
 	}
 
-	in46a_to_eua(&member->addr, &pdp->eua);
+	if (addr.len == sizeof(struct in6_addr)) {
+		struct in46_addr tmp;
+		/* IPv6 doesn't really send the real/allocated address at this point, but just
+		 * the link-identifier which the MS shall use for router solicitation */
+		tmp.len = addr.len;
+		/* initialize upper 64 bits to prefix, they are discarded by MS anyway */
+		memcpy(tmp.v6.s6_addr, &member->addr.v6, 8);
+		/* use allocated 64bit prefix as lower 64bit, used as link id by MS */
+		memcpy(tmp.v6.s6_addr+8, &member->addr.v6, 8);
+		in46a_to_eua(&tmp, &pdp->eua);
+	} else
+		in46a_to_eua(&member->addr, &pdp->eua);
 	pdp->peer = member;
 	pdp->ipif = tun;	/* TODO */
 	member->peer = pdp;
 
+	/* TODO: In IPv6, EUA doesn't contain the actual IP addr/prefix! */
 	if (gtp_kernel_tunnel_add(pdp) < 0) {
 		SYS_ERR(DGGSN, LOGL_ERROR, 0,
 			"Cannot add tunnel to kernel: %s\n", strerror(errno));
@@ -264,9 +277,30 @@
 	return 0;
 }
 
+/* RFC3307 link-local scope multicast address */
+static const struct in6_addr all_router_mcast_addr = {
+	.s6_addr = { 0xff,0x02,0,0,  0,0,0,0, 0,0,0,0,  0,0,0,2 }
+};
+
 int encaps_tun(struct pdp_t *pdp, void *pack, unsigned len)
 {
+	struct iphdr *iph = (struct iphdr *)pack;
+	struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
+
 	DEBUGP(DGGSN, "encaps_tun. Packet received: forwarding to tun\n");
+
+	switch (iph->version) {
+	case 6:
+		/* daddr: all-routers multicast addr */
+		if (IN6_ARE_ADDR_EQUAL(&ip6h->ip6_dst, &all_router_mcast_addr))
+			return handle_router_mcast(gsn, pdp, pack, len);
+		break;
+	case 4:
+		break;
+	default:
+		LOGP(DGGSN, LOGL_ERROR, "Packet from MS is neither IPv4 nor IPv6\n");
+		return -1;
+	}
 	return tun_encaps((struct tun_t *)pdp->ipif, pack, len);
 }
 
diff --git a/ggsn/icmpv6.c b/ggsn/icmpv6.c
new file mode 100644
index 0000000..ac51bb1
--- /dev/null
+++ b/ggsn/icmpv6.c
@@ -0,0 +1,238 @@
+/* Minimal ICMPv6 code for generating router advertisements as required by
+ * relevant 3GPP specs for a GGSN with IPv6 PDP contexts */
+
+/* (C) 2017 by Harald Welte <laforge at gnumonks.org> 
+ *
+ * The contents of this file may be used under the terms of the GNU
+ * General Public License Version 2, provided that the above copyright
+ * notice and this permission notice is included in all copies or
+ * substantial portions of the software.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <netinet/in.h>
+#if defined(__FreeBSD__)
+#include <sys/types.h>	/* FreeBSD 10.x needs this before ip6.h */
+#include <sys/endian.h>
+#endif
+#include <netinet/ip6.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include "checksum.h"
+
+#include "../gtp/gtp.h"
+#include "../gtp/pdp.h"
+#include "../lib/ippool.h"
+#include "../lib/syserr.h"
+#include "config.h"
+
+/* 29.061 11.2.1.3.4 IPv6 Router Configuration Variables in GGSN */
+#define GGSN_MaxRtrAdvInterval	21600		/* 6 hours */
+#define GGSN_MinRtrAdvInterval 16200		/* 4.5 hours */
+#define GGSN_AdvValidLifetime	0xffffffff	/* infinite */
+#define GGSN_AdvPreferredLifetime 0xffffffff	/* infinite */
+
+struct icmpv6_hdr {
+	uint8_t type;
+	uint8_t code;
+	uint16_t csum;
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.2 */
+struct icmpv6_radv_hdr {
+	struct icmpv6_hdr hdr;
+	uint8_t cur_ho_limit;
+#if BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t res:6,
+		m:1,
+		o:1;
+#elif BYTE_ORDER == BIG_ENDIAN
+	uint8_t m:1,
+		o:1,
+		res:6;
+#else
+# error	"Please fix <bits/endian.h>"
+#endif
+	uint16_t router_lifetime;
+	uint32_t reachable_time;
+	uint32_t retrans_timer;
+	uint8_t options[0];
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.6 */
+struct icmpv6_opt_hdr {
+	uint8_t type;
+	/* length in units of 8 octets, including type+len! */
+	uint8_t len;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.6.2 */
+struct icmpv6_opt_prefix {
+	struct icmpv6_opt_hdr hdr;
+	uint8_t prefix_len;
+#if BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t res:6,
+		a:1,
+		l:1;
+#elif BYTE_ORDER == BIG_ENDIAN
+	uint8_t l:1,
+		a:1,
+		res:6;
+#else
+# error	"Please fix <bits/endian.h>"
+#endif
+	uint32_t valid_lifetime;
+	uint32_t preferred_lifetime;
+	uint32_t res2;
+	uint8_t prefix[16];
+} __attribute__ ((packed));
+
+
+/*! construct a 3GPP 29.061 compliant router advertisement for a given prefix
+ *  \param[in] saddr Source IPv6 address for router advertisement
+ *  \param[in] daddr Destination IPv6 address for router advertisement IPv6 header
+ *  \param[in] prefix The single prefix to be advertised (/64 implied!)i
+ *  \returns callee-allocated message buffer containing router advertisement */
+struct msgb *icmpv6_construct_ra(const struct in6_addr *saddr,
+				 const struct in6_addr *daddr,
+				 const struct in6_addr *prefix)
+{
+	struct msgb *msg = msgb_alloc_headroom(512,128, "IPv6 RA");
+	struct icmpv6_radv_hdr *ra;
+	struct icmpv6_opt_prefix *ra_opt_pref;
+	struct ip6_hdr *i6h;
+	uint32_t len;
+	uint16_t skb_csum;
+
+	OSMO_ASSERT(msg);
+
+	ra = (struct icmpv6_radv_hdr *) msgb_put(msg, sizeof(*ra));
+	ra->hdr.type = 134;	/* see RFC4861 4.2 */
+	ra->hdr.code = 0;	/* see RFC4861 4.2 */
+	ra->hdr.csum = 0;	/* updated below */
+	ra->cur_ho_limit = 64;	/* seems reasonable? */
+	/* the GGSN shall leave the M-flag cleared in the Router
+	 * Advertisement messages */
+	ra->m = 0;
+	/* The GGSN may set the O-flag if there are additional
+	 * configuration parameters that need to be fetched by the MS */
+	ra->o = 0;		/* no DHCPv6 */
+	ra->res = 0;
+	/* RFC4861 Default: 3 * MaxRtrAdvInterval */
+	ra->router_lifetime = htons(3*GGSN_MaxRtrAdvInterval);
+	ra->reachable_time = 0;	/* Unspecified */
+
+	/* RFC4861 Section 4.6.2 */
+	ra_opt_pref = (struct icmpv6_opt_prefix *) msgb_put(msg, sizeof(*ra_opt_pref));
+	ra_opt_pref->hdr.type = 3;	/* RFC4861 4.6.2 */
+	ra_opt_pref->hdr.len = 4;	/* RFC4861 4.6.2 */
+	ra_opt_pref->prefix_len = 64;	/* only prefix length as per 3GPP */
+	/* The Prefix is contained in the Prefix Information Option of
+	 * the Router Advertisements and shall have the A-flag set
+	 * and the L-flag cleared */
+	ra_opt_pref->a = 1;
+	ra_opt_pref->l = 0;
+	ra_opt_pref->res = 0;
+	/*  The lifetime of the prefix shall be set to infinity */
+	ra_opt_pref->valid_lifetime = htonl(GGSN_AdvValidLifetime);
+	ra_opt_pref->preferred_lifetime = htonl(GGSN_AdvPreferredLifetime);
+	ra_opt_pref->res2 = 0;
+	memcpy(ra_opt_pref->prefix, prefix, sizeof(ra_opt_pref->prefix));
+
+	/* checksum */
+	skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0);
+	len = msgb_length(msg);
+	ra->hdr.csum = csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, skb_csum);
+
+	/* Push IPv6 header in front of ICMPv6 packet */
+	i6h = (struct ip6_hdr *) msgb_push(msg, sizeof(*i6h));
+	/* 4 bits version, 8 bits TC, 20 bits flow-ID */
+	i6h->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
+	i6h->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len);
+	i6h->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
+	i6h->ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;
+	i6h->ip6_src = *saddr;
+	i6h->ip6_dst = *daddr;
+
+	return msg;
+}
+
+/* Walidate an ICMPv6 router solicitation according to RFC4861 6.1.1 */
+static bool icmpv6_validate_router_solicit(const uint8_t *pack, unsigned len)
+{
+	const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
+	//const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h));
+
+	/* Hop limit field must have 255 */
+	if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_hlim != 255)
+		return false;
+	/* FIXME: ICMP checksum is valid */
+	/* ICMP length (derived from IP length) is 8 or more octets */
+	if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_plen < 8)
+		return false;
+	/* FIXME: All included options have a length > 0 */
+	/* FIXME: If IP source is unspecified, no source link-layer addr option */
+	return true;
+}
+
+/* RFC3307 link-local scope multicast address */
+static const struct in6_addr my_local_addr = {
+	.s6_addr = { 0x01,0x02,0,0,  0,0,0,0, 0,0,0,0,  0,0,0,0xff }
+};
+
+/* handle incoming packets to the all-routers multicast address */
+int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp, const uint8_t *pack, unsigned len)
+{
+	struct ippoolm_t *member = pdp->peer;
+	const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
+	const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h));
+	struct msgb *msg;
+
+	OSMO_ASSERT(pdp);
+	OSMO_ASSERT(member);
+
+	if (len < sizeof(*ip6h)) {
+		LOGP(DICMP6, LOGL_NOTICE, "Packet too short: %u bytes\n", len);
+		return -1;
+	}
+
+	/* we only treat ICMPv6 here */
+	if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_ICMPV6) {
+		LOGP(DICMP6, LOGL_DEBUG, "Ignoring non-ICMP to all-routers mcast\n");
+		return 0;
+	}
+
+	if (len < sizeof(*ip6h) + sizeof(*ic6h)) {
+		LOGP(DICMP6, LOGL_NOTICE, "Short ICMPv6 packet: %s\n", osmo_hexdump(pack, len));
+		return -1;
+	}
+
+	switch (ic6h->type) {
+	case 133:	/* router solicitation */
+		if (ic6h->code != 0) {
+			LOGP(DICMP6, LOGL_NOTICE, "ICMPv6 type 133 but code %d\n", ic6h->code);
+			return -1;
+		}
+		if (!icmpv6_validate_router_solicit(pack, len)) {
+			LOGP(DICMP6, LOGL_NOTICE, "Invalid Router Solicitation: %s\n",
+				osmo_hexdump(pack, len));
+			return -1;
+		}
+		/* FIXME: Send router advertisement from GGSN link-local
+		 * address to MS link-local address, including prefix
+		 * allocated to this PDP context */
+		msg = icmpv6_construct_ra(&my_local_addr, &ip6h->ip6_src, &member->addr.v6);
+		/* Send the constructed RA to the MS */
+		gtp_data_req(gsn, pdp, msgb_data(msg), msgb_length(msg));
+		msgb_free(msg);
+		break;
+	default:
+		LOGP(DICMP6, LOGL_DEBUG, "Unknown ICMPv6 type %u\n", ic6h->type);
+		break;
+	}
+	return 0;
+}
diff --git a/ggsn/icmpv6.h b/ggsn/icmpv6.h
new file mode 100644
index 0000000..ebff04e
--- /dev/null
+++ b/ggsn/icmpv6.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "../gtp/gtp.h"
+#include "../gtp/pdp.h"
+
+int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp, const uint8_t *pack, unsigned len);
diff --git a/lib/debug.c b/lib/debug.c
index b3850f9..83423dc 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -26,6 +26,11 @@
 		.description = "SGSN Emulator",
 		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
+	[DICMP6] = {
+		.name = "DICMP6",
+		.description = "ICMPv6",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
 };
 
 const struct log_info log_info = {
diff --git a/lib/syserr.h b/lib/syserr.h
index 0c50a5f..adc5840 100644
--- a/lib/syserr.h
+++ b/lib/syserr.h
@@ -19,6 +19,7 @@
 	DTUN,
 	DGGSN,
 	DSGSN,
+	DICMP6,
 };
 
 #define SYS_ERR(sub, pri, en, fmt, args...)				\

-- 
To view, visit https://gerrit.osmocom.org/3458
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Icddf7d30e01d76a4784bcef5787b36f52f703a9f
Gerrit-PatchSet: 3
Gerrit-Project: openggsn
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Jenkins Builder



More information about the gerrit-log mailing list