Page MenuHomeFreeBSD

D26420.diff
No OneTemporary

D26420.diff

Index: sys/modules/netgraph/antispoof/Makefile
===================================================================
--- sys/modules/netgraph/antispoof/Makefile
+++ sys/modules/netgraph/antispoof/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# $Whistle: Makefile,v 1.2 1999/01/19 19:39:22 archie Exp $
+
+KMOD= ng_antispoof
+SRCS= ng_antispoof.c ng_parse.c
+
+.include <bsd.kmod.mk>
Index: sys/netgraph/ng_antispoof.h
===================================================================
--- sys/netgraph/ng_antispoof.h
+++ sys/netgraph/ng_antispoof.h
@@ -0,0 +1,232 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Markus Stoff <markus@stoffdv.at>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _NETGRAPH_NG_ANTISPOOF_H_
+#define _NETGRAPH_NG_ANTISPOOF_H_
+
+/*
+ * IP MASK TYPE
+ *
+ * Default value: 255.255.255.255
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_ipmask_type;
+
+/*
+ * ETHERNET PREFIX TYPE
+ *
+ * Default value: 00:00:00:00:00:00/UINT_MAX
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_enaddr_prefix_type;
+
+/*
+ * IP PREFIX TYPE
+ *
+ * Default value: 0.0.0.0/UINT_MAX
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_ipaddr_cidr_type;
+
+/*
+ * IP PREFIX TYPE
+ *
+ * Default value: ::/UINT_MAX
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_ip6addr_cidr_type;
+
+/*
+ * IP PREFIX TYPE
+ *
+ * Default value: 0.0.0.0/UINT_MAX
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_rules_array_type;
+
+/* Node type name and magic cookie */
+#define NG_ANTISPOOF_NODE_TYPE "antispoof"
+#define NGM_ANTISPOOF_COOKIE 1596357443
+
+/* Hook names */
+#define NG_ANTISPOOF_HOOK_FILTER "filter"
+#define NG_ANTISPOOF_HOOK_DOWNSTREAM "downstream"
+#define NG_ANTISPOOF_HOOK_NOMATCH "nomatch"
+
+/*
+ * CIDR structures (RFC 4632, prefix notation)
+ *
+ * Pair of IP/IPv6 address ('prefix') and an implied network mask with the
+ * 'length' most significant bits set to 1.
+ *
+ * Textual representation: prefix/length (e.g.: 127.0.0.1/8, ff02::/16)
+ *
+ * Examples:
+ * 192.168.42.0/24 --> (192.168.42.0, 255.255.255.0)
+ * 127.0.0.0/8 --> (127.0.0.0, 255.0.0.0)
+ *
+ * dead:beef::/32 --> (dead:beef::, ffff:ffff::)
+ * ::1/128 --> (::1, ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff)
+ */
+struct ng_antispoof_ether_prefix {
+ u_char addr[6]; /* ethernet address */
+ uint8_t length; /* number of most significant bits set to 1 */
+};
+struct ng_antispoof_cidr_ip {
+ struct in_addr addr; /* ip address */
+ uint8_t length; /* number of most significant bits set to 1 */
+};
+struct ng_antispoof_cidr_ipv6 {
+ struct in6_addr addr; /* ipv6 address */
+ uint8_t length; /* number of most significant bits set to 1 */
+};
+
+/* Node configuration structure */
+struct ng_antispoof_config {
+ uint8_t debugLevel; /* debug level */
+};
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_CONFIG_TYPE_INFO \
+ { \
+ { "debugLevel", &ng_parse_uint8_type }, \
+ { \
+ NULL \
+ } \
+ }
+/* clang-format on */
+
+/* IP Filter rule */
+struct ng_antispoof_rule_ip {
+ struct ng_antispoof_ether_prefix ether; /* mac in prefix notation */
+ struct ng_antispoof_cidr_ip ip_addr; /* ip in prefix notation */
+ struct in_addr ip_mask; /* ip network mask */
+};
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_RULE_IP_TYPE_INFO \
+ { \
+ { "ether", &ng_parse_enaddr_prefix_type }, \
+ { "ip_addr", &ng_parse_ipaddr_cidr_type }, \
+ { "ip_mask", &ng_parse_ipmask_type }, \
+ { \
+ NULL \
+ } \
+ }
+/* clang-format on */
+
+/* IPv6 Filter rule */
+struct ng_antispoof_rule_ipv6 {
+ struct ng_antispoof_ether_prefix ether; /* mac in prefix notation */
+ struct ng_antispoof_cidr_ipv6 ip6_addr; /* ipv6 in prefix notation */
+};
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_RULE_IPV6_TYPE_INFO \
+ { \
+ { "ether", &ng_parse_enaddr_prefix_type }, \
+ { "ip6_addr", &ng_parse_ip6addr_cidr_type }, \
+ { \
+ NULL \
+ } \
+ }
+/* clang-format on */
+
+/* Statistics structure for one hook */
+struct ng_antispoof_hookstat {
+ u_int64_t inOctets;
+ u_int64_t inFrames;
+ u_int64_t outOctets;
+ u_int64_t outFrames;
+ u_int64_t spoofedMac; /* pkts rec'd with spoofed source mac */
+ u_int64_t spoofedIp; /* pkts rec'd with spoofed source ip */
+ u_int64_t spoofedIpv6; /* pkts rec'd with spoofed v6 source ip */
+ u_int64_t recvRunts; /* pkts rec'd to small for protocol headers */
+ u_int64_t memoryFailures; /* times couldn't get mem or mbuf */
+};
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_HOOKSTAT_INFO \
+ { \
+ { "inOctets", &ng_parse_uint64_type }, \
+ { "inFrames", &ng_parse_uint64_type }, \
+ { "outOctets", &ng_parse_uint64_type }, \
+ { "outFrames", &ng_parse_uint64_type }, \
+ { "spoofedMac", &ng_parse_uint64_type }, \
+ { "spoofedIp", &ng_parse_uint64_type }, \
+ { "spoofedIpv6", &ng_parse_uint64_type }, \
+ { "recvRunts", &ng_parse_uint64_type }, \
+ { "memoryFailures", &ng_parse_uint64_type }, \
+ { \
+ NULL \
+ } \
+ }
+/* clang-format on */
+
+/* Statistics structure returned by NGM_ANTISPOOF_GET_STATS */
+struct ng_antispoof_stats {
+ struct ng_antispoof_hookstat filter;
+ struct ng_antispoof_hookstat downstream;
+ struct ng_antispoof_hookstat nomatch;
+};
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_STATS_INFO(hstype) \
+ { \
+ { "filter", (hstype) }, \
+ { "downstream", (hstype) }, \
+ { "nomatch", (hstype) }, \
+ { NULL } \
+ }
+/* clang-format on */
+
+/* Netgraph commands */
+/* clang-format off */
+enum {
+ NGM_ANTISPOOF_SET_CONFIG = 1, /* set node configuration */
+ NGM_ANTISPOOF_GET_CONFIG, /* get node configuration */
+ NGM_ANTISPOOF_ADD_INET, /* add ip rule */
+ NGM_ANTISPOOF_ADD_INET6, /* add ipv6 rule */
+ NGM_ANTISPOOF_DEL_INET, /* delete ip rule */
+ NGM_ANTISPOOF_DEL_INET6, /* delete ipv6 rule */
+ NGM_ANTISPOOF_GET_RULES, /* get rules */
+ NGM_ANTISPOOF_RESET, /* reset (forget) all information */
+ NGM_ANTISPOOF_GET_STATS, /* get stats */
+ NGM_ANTISPOOF_CLR_STATS, /* clear stats */
+ NGM_ANTISPOOF_GETCLR_STATS, /* atomically get and clear stats */
+};
+/* clang-format on */
+
+#endif /* _NETGRAPH_NG_ANTISPOOF_H_ */
Index: sys/netgraph/ng_antispoof.c
===================================================================
--- sys/netgraph/ng_antispoof.c
+++ sys/netgraph/ng_antispoof.c
@@ -0,0 +1,2241 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Markus Stoff <markus@stoffdv.at>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * This node prevents spoofing on the filter hook.
+ * It has 3 hooks: filter, downstream and nomatch. Packets entering or leaving
+ * the filter hook ar only passed if the upstream address is matched by a filter
+ * rule. Traffic flows between the downstream and the upstream hook, unless a
+ * packet is blocked, which is diverted to the nomatch hook (if connected).
+ */
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+
+/* clang-format off */
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/netgraph.h>
+#include <netgraph/ng_antispoof.h>
+#include <netgraph/ng_parse.h>
+/* clang-format on */
+
+/*
+ * Remove following includes when moving parsing functions to netgraph/parse.c
+ */
+#include <sys/ctype.h>
+
+#include <machine/stdarg.h>
+
+/* struct and enum prototypes */
+struct packet_info;
+struct packet_info_conv;
+struct ng_antispoof_private;
+struct ng_antispoof_filter;
+enum as_flow_direction;
+
+/* Function prototypes */
+static int ng_parse_append(
+ char **cbufp, size_t *cbuflenp, const char *fmt, ...);
+static int ng_parse_append_type(const struct ng_parse_type *type,
+ const u_char *data, char **cbuf, size_t *cbuflenp);
+
+static ng_parse_t ng_ipaddr_cidr_parse;
+static ng_unparse_t ng_ipaddr_cidr_unparse;
+static ng_getDefault_t ng_ipaddr_cidr_getDefault;
+static ng_getAlign_t ng_ipaddr_cidr_getAlign;
+
+static ng_getDefault_t ng_ipmask_getDefault;
+
+static ng_parse_t ng_ip6addr_cidr_parse;
+static ng_unparse_t ng_ip6addr_cidr_unparse;
+static ng_getDefault_t ng_ip6addr_cidr_getDefault;
+static ng_getAlign_t ng_ip6addr_cidr_getAlign;
+
+static ng_parse_t ng_enaddr_prefix_parse;
+static ng_unparse_t ng_enaddr_prefix_unparse;
+static ng_getDefault_t ng_enaddr_prefix_getDefault;
+static ng_getAlign_t ng_enaddr_prefix_getAlign;
+
+static bool is_prefix(const void *data, size_t len);
+static uint8_t get_prefix_length(const void *data, size_t len);
+
+static ng_parse_t ng_antispoof_filter_parse;
+static ng_unparse_t ng_antispoof_filter_unparse;
+static ng_getAlign_t ng_antispoof_filter_getAlign;
+
+static inline int mkipmask(struct in_addr *addr, uint8_t n_sigbits);
+static inline int mkipv6mask(struct in6_addr *addr, uint8_t n_sigbits);
+static inline void mkipv6mask_or_default128(
+ struct in6_addr *addr, uint8_t n_sigbits);
+static inline int mkethernetmask(u_char *addr, uint8_t n_sigbits);
+static inline void mkethernetmask_or_default48(u_char *addr, uint8_t n_sigbits);
+
+static ng_parse_array_getLength_t ng_antispoof_getTableLength;
+
+/* Netgraph methods */
+static ng_constructor_t ng_antispoof_constructor;
+static ng_newhook_t ng_antispoof_newhook;
+static ng_rcvmsg_t ng_antispoof_rcvmsg;
+static ng_rcvdata_t ng_antispoof_rcvdata;
+static ng_close_t ng_antispoof_close;
+static ng_shutdown_t ng_antispoof_shutdown;
+static ng_disconnect_t ng_antispoof_disconnect;
+
+/* Other internal functions */
+static int ng_antispoof_getstats(struct ng_antispoof_private *privdata,
+ const struct ng_mesg *request, struct ng_mesg **response);
+static void ng_antispoof_clearstats(struct ng_antispoof_private *privdata);
+
+static uint16_t ng_antispoof_setconfig_find(
+ struct ng_antispoof_private * privdata,
+ const struct ng_antispoof_filter *filter);
+static void ng_antispoof_setconfig_insert(struct ng_antispoof_private *privdata,
+ uint16_t pos, const struct ng_antispoof_filter *filter);
+static void ng_antispoof_setconfig_remove(
+ struct ng_antispoof_private *privdata, uint16_t pos);
+
+static void ng_antispoof_setconfig_mkfilter_ip(
+ struct ng_antispoof_filter * new_filter,
+ const struct ng_antispoof_rule_ip *rule);
+static void ng_antispoof_setconfig_mkfilter_ipv6(
+ struct ng_antispoof_filter * new_filter,
+ const struct ng_antispoof_rule_ipv6 *rule);
+
+static void ng_antispoof_setconfig_addrule_ip(
+ struct ng_antispoof_private * privdata,
+ const struct ng_antispoof_rule_ip *rule);
+static void ng_antispoof_setconfig_addrule_ipv6(
+ struct ng_antispoof_private * privdata,
+ const struct ng_antispoof_rule_ipv6 *rule);
+static void ng_antispoof_setconfig_delrule_ip(
+ struct ng_antispoof_private * privdata,
+ const struct ng_antispoof_rule_ip *rule);
+static void ng_antispoof_setconfig_delrule_ipv6(
+ struct ng_antispoof_private * privdata,
+ const struct ng_antispoof_rule_ipv6 *rule);
+
+static int ng_antispoof_rcvmsg_impl(struct ng_antispoof_private *privdata,
+ const struct ng_mesg *request, struct ng_mesg **response);
+
+static enum as_error pullup(struct mbuf **m, size_t len);
+
+static inline enum as_error ng_antispoof_skip_vlan(
+ struct mbuf **m, struct packet_info *pkt_info, size_t *offset);
+static inline enum as_error ng_antispoof_collect_layer2(struct mbuf **m,
+ struct packet_info *pkt_info, enum as_flow_direction flow, size_t *offset);
+static inline enum as_error ng_antispoof_collect_arp(struct mbuf **m,
+ struct packet_info *pkt_info, enum as_flow_direction flow, size_t *offset);
+static inline enum as_error ng_antispoof_collect_ip(struct mbuf **m,
+ struct packet_info *pkt_info, enum as_flow_direction flow, size_t *offset);
+static inline enum as_error ng_antispoof_collect_ipv6(struct mbuf **m,
+ struct packet_info *pkt_info, enum as_flow_direction flow, size_t *offset);
+static inline enum as_error ng_antispoof_collect_layer3(struct mbuf **m,
+ struct packet_info *pkt_info, enum as_flow_direction flow, size_t *offset);
+static enum as_error ng_antispoof_collect_data(
+ struct mbuf **m, struct packet_info *pkt_info, enum as_flow_direction flow);
+
+static enum as_error ng_antispoof_packet_matches(
+ const struct packet_info_conv * pkt_info,
+ const struct ng_antispoof_filter *filters, uint16_t begin, uint16_t end);
+
+static const char *ng_antispoof_nodename(node_p node);
+
+/*
+ * Compute alignment of type T
+ *
+ * Examples:
+ * x = ALIGNMENT_OF(int8_t); // x = 1
+ * x = ALIGNMENT_OF(int32_t); // x = 4
+ * x = ALIGNMENT_OF(struct in6_addr); // x = 4
+ */
+/* clang-format off */
+#define ALIGNMENT_OF(T) ((size_t)&((struct { char x; T y; } *)0)->y)
+/* clang-format on */
+
+/*
+ * Compute offset of member M in struct S
+ *
+ * Examples:
+ * struct demo {
+ * int8_t x;
+ * int8_t y;
+ * int32_t z;
+ * };
+ * x = OFFSET(demo, x); // x = 0
+ * x = OFFSET(demo, y); // y = 1
+ * x = OFFSET(demo, z); // x = 4
+ */
+#define OFFSET(S, M) ((size_t) & ((S *)0)->M)
+
+/*
+ * How many fields of type F are required to cover type T in memory?
+ *
+ * Examples:
+ * x = NFIELDS(char, char); // x = 1
+ * x = NFIELDS(int64_t, int64_t); // x = 1
+ * x = NFIELDS(char, int64_t); // x = 1
+ * x = NFIELDS(int64_t, char); // x = 8
+ * x = NFIELDS(char[7], int32_t); // x = 2
+ */
+#define NFIELDS(T, F) ((sizeof(T) - 1) / sizeof(F) + 1)
+
+/*
+ * Immutable VLAN header struct (IEEE 802.1Q and 802.1ad)
+ */
+struct ng_antispoof_vlanhdr {
+ /*
+ * TCI (Tag Control Information) format:
+ *
+ * uint16_t pcp : 3; // IEEE P802.1p Priority Code Point
+ * uint16_t dei : 1; // Drop Eligible Indicator (former CF)
+ * uint16_t vid : 12; // VLAN ID
+ *
+ * How to access individual TCI components:
+ *
+ * #include <net/ethernet.h>
+ * uint16_t pcp = EVL_PRIOFTAG (ntohs(tag));
+ * uint16_t dei = EVL_CFIOFTAG (ntohs(tag));
+ * uint16_t vid = EVL_VLANOFTAG (noths(tag));
+ *
+ */
+ const uint16_t tag; /* TCI (Tag Control Information) */
+ const uint16_t proto; /* TPID (Tag Protocoll Identifier) */
+};
+
+/*
+ * Packet information required for filtering
+ */
+struct packet_info {
+ uint16_t ether_type; /* protocol (ETHERTYPE_IP or ETHERTYPE_IPV6) */
+ u_char en_addr[ETHER_ADDR_LEN]; /* ethernet address */
+ // uint16_t qinq_tag; /* IEEE 802.1ad vlan tag (QinQ) */
+ // uint16_t vlan_tag; /* IEEE 802.1Q vlan tag */
+ union {
+ struct in_addr ip_addr; /* IPv4 address */
+ struct in6_addr ip6_addr; /* IPv6 address */
+ };
+};
+
+/*
+ * Size of uintmax_t array that completely covers a packet_info struct.
+ */
+#define FILTER_RAW_SZ (NFIELDS(struct packet_info, uintmax_t))
+
+/*
+ * Convert packet_info into an array of integers for efficient filtering
+ */
+struct packet_info_conv {
+ union {
+ struct packet_info pinfo;
+ uintmax_t raw[FILTER_RAW_SZ];
+ };
+};
+
+/*
+ * Antispoof filter consisting of address and network mask
+ */
+struct ng_antispoof_filter {
+ struct packet_info_conv addr; /* address to filter */
+ struct packet_info_conv mask; /* mask to apply */
+};
+
+/* Parse type for struct ng_antispoof_rule_ip */
+static const struct ng_parse_struct_field ng_antispoof_rule_ip_type_fields[] =
+ NG_ANTISPOOF_RULE_IP_TYPE_INFO;
+static const struct ng_parse_type ng_antispoof_rule_ip_type = {
+ &ng_parse_struct_type, &ng_antispoof_rule_ip_type_fields
+};
+
+/* Parse type for struct ng_antispoof_rule_ipv6 */
+static const struct ng_parse_struct_field ng_antispoof_rule_ipv6_type_fields[] =
+ NG_ANTISPOOF_RULE_IPV6_TYPE_INFO;
+static const struct ng_parse_type ng_antispoof_rule_ipv6_type = {
+ &ng_parse_struct_type, &ng_antispoof_rule_ipv6_type_fields
+};
+
+/*
+ * Append to a fixed length string buffer.
+ */
+static int
+ng_parse_append(char **cbufp, size_t *cbuflenp, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ int len = vsnprintf(*cbufp, *cbuflenp, fmt, args);
+ va_end(args);
+
+ if (len >= *cbuflenp)
+ return (ERANGE);
+
+ *cbufp += len;
+ *cbuflenp -= len;
+ return (0);
+}
+
+/*
+ * Unparse type to a fixed length string buffer.
+ */
+static int
+ng_parse_append_type(const struct ng_parse_type *type, const u_char *data,
+ char **cbuf, size_t *cbuflenp)
+{
+ /* Protect against overflow when converting to int calling ng_unparse */
+ if (*cbuflenp > INT_MAX)
+ return (ERANGE);
+
+ int error = ng_unparse(type, data, *cbuf, *cbuflenp);
+ if (error == 0) {
+ size_t len = strnlen(*cbuf, *cbuflenp);
+ *cbuf += len;
+ *cbuflenp -= len;
+ }
+ return (error);
+}
+
+/************************************************************************
+ IP PREFIX TYPE
+ ************************************************************************/
+
+/*
+ * Parse an IP address prefix in CIDR notation
+ *
+ * Format: IP [ '/' LENGTH ]
+ *
+ * If provided, LENGTH must be in the range 0-32.
+ * If LENGTH is omitted, it is set to UINT8_MAX to indicate an unset value.
+ *
+ * Examples:
+ * 1.2.3.4 // addr=1.2.3.4, length=UINT_MAX
+ * 1.2.3.4/32 // addr=1.2.3.4, length=32
+ * 192.168.1.0/24 // addr=192.168.1.0, length=24
+ * 10.0.0.0/8 // addr=10.0.0.0, length=8
+ */
+static int
+ng_ipaddr_cidr_parse(const struct ng_parse_type *type, const char *s, int *off,
+ const u_char *const start, u_char *const buf, int *buflen)
+{
+ struct ng_antispoof_cidr_ip *cidr = (struct ng_antispoof_cidr_ip *)buf;
+
+ /* Make sure the buffer is big enough to hold the result */
+ if (*buflen < sizeof(*cidr))
+ return (ERANGE);
+
+ /* Parse IP address */
+ int len = sizeof(cidr->addr);
+ int error = ng_parse(
+ &ng_parse_ipaddr_type, s, off, (u_char *)&cidr->addr, &len);
+ if (error != 0)
+ return (error);
+
+ /* Parse prefix length (number of most significant bits set to 1) */
+ if (*(s + *off) == '/') {
+ ++(*off);
+ len = sizeof(cidr->length);
+ error = ng_parse(&ng_parse_uint8_type, s, off,
+ (u_char *)&cidr->length, &len);
+ if (error != 0)
+ return (error);
+ if (cidr->length > 32)
+ return (EINVAL);
+ } else {
+ /* Set to high value to indicate an unset value */
+ cidr->length = UINT8_MAX;
+ }
+
+ /* Report amount of data written to buffer */
+ *buflen = sizeof(*cidr);
+
+ return (0);
+}
+
+/*
+ * Unparse prefix in its canonical form
+ *
+ * The following normalizations are implemented:
+ * - if the prefix length is greater than 32, LENGTH is omitted
+ */
+static int
+ng_ipaddr_cidr_unparse(const struct ng_parse_type *type, const u_char *data,
+ int *off, char *cbuf, int cbuflen)
+{
+ if (cbuflen < 0)
+ return (ERANGE);
+ size_t buflen = cbuflen;
+
+ const struct ng_antispoof_cidr_ip *const cidr =
+ (const struct ng_antispoof_cidr_ip *)(data + *off);
+
+ int error = ng_parse_append_type(
+ &ng_parse_ipaddr_type, (const u_char *)&cidr->addr, &cbuf, &buflen);
+ if (error != 0)
+ return (error);
+
+ if (cidr->length <= 32) {
+ error = ng_parse_append(&cbuf, &buflen, "/%d", cidr->length);
+ if (error != 0)
+ return (error);
+ }
+
+ /* Increase offset by the amount of data read */
+ *off += sizeof(*cidr);
+
+ return (0);
+}
+
+static int
+ng_ipaddr_cidr_getDefault(const struct ng_parse_type *type,
+ const u_char *const start, u_char *buf, int *buflen)
+{
+ int error = 0;
+
+ /*
+ * Zero out memory so ng_unparse_composite can check default values via
+ * bcmp().
+ */
+ struct ng_antispoof_cidr_ip cidr;
+ bzero(&cidr, sizeof(cidr));
+ cidr.addr = (struct in_addr) { 0 };
+ cidr.length = UINT8_MAX;
+
+ if (*buflen >= sizeof(cidr)) {
+ bcopy(&cidr, buf, sizeof(cidr));
+ *buflen = sizeof(cidr);
+ } else
+ error = ERANGE;
+
+ return (error);
+}
+
+static int
+ng_ipaddr_cidr_getAlign(const struct ng_parse_type *type)
+{
+ return (ALIGNMENT_OF(struct ng_antispoof_cidr_ip));
+}
+
+/* clang-format off */
+const struct ng_parse_type ng_parse_ipaddr_cidr_type = {
+ NULL,
+ NULL,
+ NULL,
+ ng_ipaddr_cidr_parse,
+ ng_ipaddr_cidr_unparse,
+ ng_ipaddr_cidr_getDefault,
+ ng_ipaddr_cidr_getAlign
+};
+/* clang-format on */
+
+/************************************************************************
+ IP MASK TYPE
+ ************************************************************************/
+
+static int
+ng_ipmask_getDefault(const struct ng_parse_type *type,
+ const u_char *const start, u_char *buf, int *buflen)
+{
+ struct in_addr mask = { 0xffffffff };
+ int error = 0;
+
+ if (*buflen >= sizeof(mask)) {
+ bcopy(&mask, buf, sizeof(mask));
+ *buflen = sizeof(mask);
+ } else
+ error = ERANGE;
+
+ return (error);
+}
+
+/*
+ * IPv4 network mask
+ *
+ * This is a specialization of an IPv4 address that provides a different default
+ * value (255.255.255.255 instead of 0.0.0.0).
+ */
+/* clang-format off */
+const struct ng_parse_type ng_parse_ipmask_type = {
+ &ng_parse_ipaddr_type,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ng_ipmask_getDefault,
+ NULL
+};
+/* clang-format on */
+
+/************************************************************************
+ IPv6 PREFIX TYPE
+ ************************************************************************/
+
+/*
+ * Parse an IPv6 address prefix in CIDR notation
+ *
+ * Format: IPv6 [ '/' LENGTH ]
+ *
+ * If provided, LENGTH must be in the range 0-128.
+ * If LENGTH is omitted, it is set to UINT8_MAX to indicate an unset value.
+ *
+ * Examples:
+ * ::1 // addr=::1, length=UINT_MAX
+ * ::1/128 // addr=::1, length=128
+ * ff02::/16 // addr=ff02::, length=16
+ */
+static int
+ng_ip6addr_cidr_parse(const struct ng_parse_type *type, const char *s, int *off,
+ const u_char *const start, u_char *const buf, int *buflen)
+{
+ struct ng_antispoof_cidr_ipv6 *cidr =
+ (struct ng_antispoof_cidr_ipv6 *)buf;
+
+ /* Make sure the buffer is big enough to hold the result */
+ if (*buflen < sizeof(*cidr))
+ return (ERANGE);
+
+ int len = sizeof(cidr->addr);
+ int error = ng_parse(
+ &ng_parse_ip6addr_type, s, off, (u_char *)&cidr->addr, &len);
+ if (error != 0)
+ return (error);
+
+ /* Parse addr length (number of most significant bits) */
+ if (*(s + *off) == '/') {
+ ++(*off);
+ len = sizeof(cidr->length);
+ error = ng_parse(&ng_parse_uint8_type, s, off,
+ (u_char *)&cidr->length, &len);
+ if (error != 0)
+ return (error);
+ if (cidr->length > 128)
+ return (EINVAL);
+ } else {
+ /* Set to high value to indicate an unset value */
+ cidr->length = UINT8_MAX;
+ }
+
+ /* Report amount of data written to buffer */
+ *buflen = sizeof(*cidr);
+
+ return (0);
+}
+
+/*
+ * Unparse prefix in its canonical form
+ *
+ * The following normalizations are implemented:
+ * - if the prefix length is greater than 128, LENGTH is omitted
+ */
+static int
+ng_ip6addr_cidr_unparse(const struct ng_parse_type *type, const u_char *data,
+ int *off, char *cbuf, int cbuflen)
+{
+ if (cbuflen < 0)
+ return (ERANGE);
+ size_t buflen = cbuflen;
+
+ const struct ng_antispoof_cidr_ipv6 *const cidr =
+ (const struct ng_antispoof_cidr_ipv6 *)(data + *off);
+
+ int error = ng_parse_append_type(&ng_parse_ip6addr_type,
+ (const u_char *)&cidr->addr, &cbuf, &buflen);
+ if (error != 0)
+ return (error);
+
+ if (cidr->length <= 128) {
+ error = ng_parse_append(&cbuf, &buflen, "/%d", cidr->length);
+ if (error != 0)
+ return (error);
+ }
+
+ /* Increase offset by the amount of data read */
+ *off += sizeof(*cidr);
+
+ return (0);
+}
+
+static int
+ng_ip6addr_cidr_getDefault(const struct ng_parse_type *type,
+ const u_char *const start, u_char *buf, int *buflen)
+{
+ int error = 0;
+
+ /*
+ * Zero out memory so ng_unparse_composite can check default values via
+ * bcmp().
+ */
+ struct ng_antispoof_cidr_ipv6 cidr;
+ bzero(&cidr, sizeof(cidr));
+ cidr.addr = (struct in6_addr)IN6ADDR_ANY_INIT;
+ cidr.length = UINT8_MAX;
+
+ if (*buflen >= sizeof(cidr)) {
+ bcopy(&cidr, buf, sizeof(cidr));
+ *buflen = sizeof(cidr);
+ } else
+ error = ERANGE;
+
+ return (error);
+}
+
+static int
+ng_ip6addr_cidr_getAlign(const struct ng_parse_type *type)
+{
+ return (ALIGNMENT_OF(struct ng_antispoof_cidr_ipv6));
+}
+
+/* clang-format off */
+const struct ng_parse_type ng_parse_ip6addr_cidr_type = {
+ NULL,
+ NULL,
+ NULL,
+ ng_ip6addr_cidr_parse,
+ ng_ip6addr_cidr_unparse,
+ ng_ip6addr_cidr_getDefault,
+ ng_ip6addr_cidr_getAlign
+};
+/* clang-format on */
+
+/************************************************************************
+ ETHERNET PREFIX TYPE
+ ************************************************************************/
+
+/*
+ * Parse an ethernet address prefix in a CIDR-like notation
+ *
+ * Format: ETHER [ '/' LENGTH ]
+ *
+ * If provided, LENGTH must be in the range 0-48.
+ * If LENGTH is omitted, it is set to UINT8_MAX to indicate an unset value.
+ *
+ * Examples:
+ * 0a:00:00:00:00:01 // addr=0a:00:00:00:00:01, length=UINT_MAX
+ * 0a:00:00:00:00:01/48 // addr=0a:00:00:00:00:01, length=48
+ * 33:33:00:00:00:00/16 // addr=33:33:00:00:00:00, length=16
+ */
+static int
+ng_enaddr_prefix_parse(const struct ng_parse_type *type, const char *s,
+ int *off, const u_char *const start, u_char *const buf, int *buflen)
+{
+ struct ng_antispoof_ether_prefix *prefix =
+ (struct ng_antispoof_ether_prefix *)buf;
+
+ /* Make sure the buffer is big enough to hold the result */
+ if (*buflen < sizeof(*prefix))
+ return (ERANGE);
+
+ int len = sizeof(prefix->addr);
+ int error = ng_parse(
+ &ng_parse_enaddr_type, s, off, (u_char *)&prefix->addr, &len);
+ if (error != 0)
+ return (error);
+
+ /* Parse prefix length (number of most significant bits) */
+ if (*(s + *off) == '/') {
+ ++(*off);
+ len = sizeof(prefix->length);
+ error = ng_parse(&ng_parse_uint8_type, s, off,
+ (u_char *)&prefix->length, &len);
+ if (error != 0)
+ return (error);
+ if (prefix->length > 48)
+ return (EINVAL);
+ } else {
+ /* Set to high value to indicate an unset value */
+ prefix->length = UINT8_MAX;
+ }
+
+ /* Report amount of data written to buffer */
+ *buflen = sizeof(*prefix);
+
+ return (0);
+}
+
+/*
+ * Unparse prefix in its canonical form
+ *
+ * The following normalizations are implemented:
+ * - if the prefix length is greater than 48, LENGTH is omitted
+ */
+static int
+ng_enaddr_prefix_unparse(const struct ng_parse_type *type, const u_char *data,
+ int *off, char *cbuf, int cbuflen)
+{
+ if (cbuflen < 0)
+ return (ERANGE);
+ size_t buflen = cbuflen;
+
+ const struct ng_antispoof_ether_prefix *const prefix =
+ (const struct ng_antispoof_ether_prefix *)(data + *off);
+
+ int error = ng_parse_append_type(&ng_parse_enaddr_type,
+ (const u_char *)&prefix->addr, &cbuf, &buflen);
+ if (error != 0)
+ return (error);
+
+ if (prefix->length <= 48) {
+ error = ng_parse_append(&cbuf, &buflen, "/%d", prefix->length);
+ if (error != 0)
+ return (error);
+ }
+
+ /* Increase offset by the amount of data read */
+ *off += sizeof(*prefix);
+
+ return (0);
+}
+
+static int
+ng_enaddr_prefix_getDefault(const struct ng_parse_type *type,
+ const u_char *const start, u_char *buf, int *buflen)
+{
+ struct ng_antispoof_ether_prefix prefix = { { 0 }, UINT8_MAX };
+ int error = 0;
+
+ if (*buflen >= sizeof(prefix)) {
+ bcopy(&prefix, buf, sizeof(prefix));
+ *buflen = sizeof(prefix);
+ } else
+ error = ERANGE;
+
+ return (error);
+}
+
+static int
+ng_enaddr_prefix_getAlign(const struct ng_parse_type *type)
+{
+ return (ALIGNMENT_OF(struct ng_antispoof_ether_prefix));
+}
+
+/* clang-format off */
+const struct ng_parse_type ng_parse_enaddr_prefix_type = {
+ NULL,
+ NULL,
+ NULL,
+ ng_enaddr_prefix_parse,
+ ng_enaddr_prefix_unparse,
+ ng_enaddr_prefix_getDefault,
+ ng_enaddr_prefix_getAlign
+};
+/* clang-format on */
+
+/************************************************************************
+ ANTISPOOF FILTER TYPE
+ ************************************************************************/
+
+/*
+ * Is given mask a prefix?
+ *
+ * Returns true if most significant bits are all 1 and least significant bits
+ * are all 0.
+ *
+ * Examples:
+ * 1111 0000 // true, prefix of length 4
+ * 0000 0000 // true, prefix of length 0
+ * 1111 1111 // true, prefix of length 8
+ * 0000 1111 // false
+ * 1100 1100 // false
+ */
+static bool
+is_prefix(const void *data, size_t len)
+{
+ const u_char *_data = data;
+ bool set_left_most_bits_parsed = false;
+ for (const u_char *end = _data + len; _data < end; ++_data) {
+ u_char c = 1 << (CHAR_BIT - 1);
+ for (uint_fast8_t i = 0; i < CHAR_BIT; ++i, c >>= 1) {
+ if ((*_data & c) == 0)
+ set_left_most_bits_parsed = true;
+ else if (set_left_most_bits_parsed)
+ return (false);
+ }
+ }
+ return (true);
+}
+
+/*
+ * Length of prefix in given mask.
+ *
+ * Precondition: is_prefix(data, len) == true
+ *
+ * Examples:
+ * 1111 0000 // length=4
+ * 0000 0000 // length=0
+ * 1111 1111 // length=8
+ */
+static uint8_t
+get_prefix_length(const void *data, size_t len)
+{
+ const u_char *_data = data;
+ uint8_t prefix_length = 0;
+ for (const u_char *end = _data + len; _data < end; ++_data) {
+ u_char c = 1 << (CHAR_BIT - 1);
+ for (uint_fast8_t i = 0; i < CHAR_BIT; ++i, c >>= 1) {
+ if ((*_data & c) == 0)
+ return (prefix_length);
+ ++prefix_length;
+ }
+ }
+ return (prefix_length);
+}
+
+/* Parsing filters into a data structure is not supported */
+static int
+ng_antispoof_filter_parse(const struct ng_parse_type *type, const char *s,
+ int *off, const u_char *const start, u_char *const buf, int *buflen)
+{
+ /* Unparse-only data structure */
+ return (EOPNOTSUPP);
+}
+
+/*
+ * Unparse filter rule in its canonical form.
+ *
+ * The following normalizations are implemented:
+ * - IP rules are provided in CIDR notation if possible (ip_addr=1.2.3.4/24)
+ * - If the IP mask is not a prefix, the IP address along with the mask is
+ * provided (ip_addr=1.2.3.4 ip_mask=255.255.0.255)
+ * - The length part of a prefix (ethernet, IP and IPv6) is omitted if it is
+ * greater or equal the maximum length of the prefix
+ *
+ * Examples:
+ * 192.168.1.0/255.255.255.0 -> 192.168.1.0/24
+ * 192.168.0.1/255.255.0.255 -> 192.168.0.1/255.255.0.255
+ * 192.168.1.1/32 -> 192.168.1.1
+ * ::1/128 -> ::1
+ * 0a:00:00:00:00:01/48 -> 0a:00:00:00:00:01
+ *
+ * { ether=0a:00:00:00:00:01 ip_addr=192.168.1.0/24 }
+ * { ether=0a:00:00:00:00:01 ip_addr=192.168.0.1 ip_mask=255.255.0.255 }
+ * { ether=0a:00:00:00:00:01 ip_addr=192.168.1.1 }
+ * { ether=0a:00:00:00:00:01 ip6_addr=::1 }
+ */
+static int
+ng_antispoof_filter_unparse(const struct ng_parse_type *type,
+ const u_char *data, int *off, char *cbuf, int cbuflen)
+{
+ if (cbuflen < 0)
+ return (ERANGE);
+ size_t buflen = cbuflen;
+
+ int error = 0;
+ const struct ng_antispoof_filter *const filter =
+ (const struct ng_antispoof_filter *)(data + *off);
+
+ switch (ntohs(filter->addr.pinfo.ether_type)) {
+ case ETHERTYPE_IP: {
+ /*
+ * Zero out memory so ng_unparse_composite can check default
+ * values via bcmp().
+ */
+ struct ng_antispoof_rule_ip rule;
+ bzero(&rule, sizeof(rule));
+
+ bcopy(filter->addr.pinfo.en_addr, rule.ether.addr,
+ sizeof(rule.ether.addr));
+ rule.ether.length =
+ get_prefix_length(filter->mask.pinfo.en_addr,
+ sizeof(filter->mask.pinfo.en_addr));
+ /* Omit maximum prefix length */
+ if (rule.ether.length >= 48)
+ rule.ether.length = UINT8_MAX;
+
+ bcopy(&filter->addr.pinfo.ip_addr, &rule.ip_addr.addr,
+ sizeof(rule.ip_addr.addr));
+
+ if (is_prefix(&filter->mask.pinfo.ip_addr,
+ sizeof(filter->mask.pinfo.ip_addr))) {
+ rule.ip_addr.length =
+ get_prefix_length(&filter->mask.pinfo.ip_addr,
+ sizeof(filter->mask.pinfo.ip_addr));
+ /* Omit maximum prefix length */
+ if (rule.ip_addr.length >= 32)
+ rule.ip_addr.length = UINT8_MAX;
+ /* Omit ip_mask (set to default) */
+ int len = sizeof(rule.ip_mask);
+ error = ng_parse_getDefault(&ng_parse_ipmask_type,
+ (u_char *)&rule.ip_mask, &len);
+ } else {
+ /* Omit prefix length */
+ rule.ip_addr.length = UINT8_MAX;
+ bcopy(&filter->mask.pinfo.ip_addr, &rule.ip_mask,
+ sizeof(rule.ip_mask));
+ }
+
+ error = ng_parse_append_type(&ng_antispoof_rule_ip_type,
+ (const u_char *)&rule, &cbuf, &buflen);
+ break;
+ }
+ case ETHERTYPE_IPV6: {
+ /*
+ * Zero out memory so ng_unparse_composite can check default
+ * values via bcmp().
+ */
+ struct ng_antispoof_rule_ipv6 rule;
+ bzero(&rule, sizeof(rule));
+
+ bcopy(filter->addr.pinfo.en_addr, rule.ether.addr,
+ sizeof(rule.ether.addr));
+ rule.ether.length =
+ get_prefix_length(filter->mask.pinfo.en_addr,
+ sizeof(filter->mask.pinfo.en_addr));
+ /* Omit maximum prefix length */
+ if (rule.ether.length >= 48)
+ rule.ether.length = UINT8_MAX;
+
+ rule.ip6_addr.addr = filter->addr.pinfo.ip6_addr;
+ rule.ip6_addr.length =
+ get_prefix_length(&filter->mask.pinfo.ip6_addr,
+ sizeof(filter->mask.pinfo.ip6_addr));
+ /* Omit maximum prefix length */
+ if (rule.ip6_addr.length >= 128)
+ rule.ip6_addr.length = UINT8_MAX;
+
+ error = ng_parse_append_type(&ng_antispoof_rule_ipv6_type,
+ (const u_char *)&rule, &cbuf, &buflen);
+ break;
+ }
+ default: {
+ error = EINVAL;
+ break;
+ }
+ }
+
+ /* Increase offset by the amount of data read */
+ if (error == 0)
+ *off += sizeof(*filter);
+
+ return (error);
+}
+
+static int
+ng_antispoof_filter_getAlign(const struct ng_parse_type *type)
+{
+ return (ALIGNMENT_OF(struct ng_antispoof_filter));
+}
+
+/*
+ * Antispoof Filter
+ *
+ * Unparses a filter rule.
+ */
+/* clang-format off */
+const struct ng_parse_type ng_parse_antispoof_filter_type = {
+ NULL,
+ NULL,
+ NULL,
+ &ng_antispoof_filter_parse,
+ &ng_antispoof_filter_unparse,
+ NULL,
+ &ng_antispoof_filter_getAlign
+};
+/* clang-format on */
+
+/************************************************************************
+ ANTISPOOF NODE IMPLEMENTATION
+ ************************************************************************/
+
+#ifdef NG_SEPARATE_MALLOC
+static MALLOC_DEFINE(
+ M_NETGRAPH_ANTISPOOF, "netgraph_antispoof", "netgraph antispoof node");
+#else
+#define M_NETGRAPH_ANTISPOOF M_NETGRAPH
+#endif
+
+/*
+ * Generate mask for IP prefix
+ *
+ * Set the n_sigbits most significant bits in addr and return 0.
+ * If n_sigbits > 32, addr is lef unchanged and ERANGE is returned.
+ * If addr is NULL, EINVAL is returned.
+ */
+static inline int
+mkipmask(struct in_addr *addr, uint8_t n_sigbits)
+{
+ if (addr == NULL)
+ return (EINVAL);
+ if (n_sigbits > 32)
+ return (ERANGE);
+
+ /* clang-format off */
+ addr->s_addr = n_sigbits >= 32
+ ? 0xffffffff
+ : htonl(~((uint32_t)~0 >> n_sigbits));
+ /* clang-format on */
+
+ return (0);
+}
+
+/*
+ * Generate mask for IPv6 prefix
+ *
+ * Set the n_sigbits most significant bits in addr and return 0.
+ * If n_sigbits > 128, addr is lef unchanged and ERANGE is returned.
+ * If addr is NULL, EINVAL is returned.
+ */
+static inline int
+mkipv6mask(struct in6_addr *addr, uint8_t n_sigbits)
+{
+ if (addr == NULL)
+ return (EINVAL);
+ if (n_sigbits > 128)
+ return (ERANGE);
+
+ /*
+ * Use f to iterate through the four 32-bit fields of the IPv6 address.
+ * Decrement n_sigbits by 32 (the bits processed) with each iteration.
+ * As sigbit is <= 128 bits, we remain within addr's bounds.
+ */
+ *addr = (struct in6_addr)IN6MASK0;
+ uint32_t *f = addr->__u6_addr.__u6_addr32;
+ for (; n_sigbits >= 32; n_sigbits -= 32) {
+ *(f++) = 0xffffffff;
+ }
+ /* Make sure we remain within the bounds of addr */
+ if (n_sigbits > 0)
+ *f = htonl(~((uint32_t)~0 >> n_sigbits));
+
+ return (0);
+}
+
+/*
+ * Generate mask for IPv6 prefix (default to /128 if n_sigbits > 128)
+ */
+static inline void
+mkipv6mask_or_default128(struct in6_addr *addr, uint8_t n_sigbits)
+{
+ mkipv6mask(addr, n_sigbits < 128 ? n_sigbits : 128);
+}
+
+/*
+ * Generate mask for ethernet prefix
+ *
+ * Set the n_sigbits most significant bits in addr and return 0.
+ * If n_sigbits > 48, addr is lef unchanged and ERANGE is returned.
+ * If addr is NULL, EINVAL is returned.
+ */
+static inline int
+mkethernetmask(u_char *addr, uint8_t n_sigbits)
+{
+ if (addr == NULL)
+ return (EINVAL);
+ if (n_sigbits > 48)
+ return (ERANGE);
+
+ /*
+ * Use f to iterate through the four 8-bit fields of the ethernet
+ * address. Decrement n_sigbits by 8 (the bits processed) with each
+ * iteration. As sigbit is <= 48 bits, we remain within addr's bounds.
+ */
+ bzero(addr, ETHER_ADDR_LEN);
+ u_char *f = addr;
+ for (; n_sigbits >= 8; n_sigbits -= 8) {
+ *(f++) = 0xff;
+ }
+ /* Make sure we remain within the bounds of addr */
+ if (n_sigbits > 0)
+ *f = ~((u_char)~0 >> n_sigbits);
+
+ return (0);
+}
+
+/*
+ * Generate mask for ethernet prefix (default to /48 if n_sigbits > 48)
+ */
+static inline void
+mkethernetmask_or_default48(u_char *addr, uint8_t n_sigbits)
+{
+ mkethernetmask(addr, n_sigbits < 48 ? n_sigbits : 48);
+}
+
+/* Error constants */
+enum as_error {
+ AS_NOERROR = 0,
+ AS_E_PKT_TO_SMALL, /* Packet smaller than reguested m_pullup size */
+ AS_E_ENOBUFS, /* m_pullup was unable to allocate new memory */
+ AS_E_UNSUPPORTED_ET, /* Ethertype not supported */
+ AS_E_UNSUPPORTED_ARP, /* ARP type not supported */
+ AS_E_NOMATCH, /* Packet didn't match any rule */
+ AS_E_MATCH, /* Packet matched a rule */
+ AS_E_MALFORMED_VLAN_STACK,
+};
+
+/* Flow direction */
+enum as_flow_direction {
+ AS_FLOW_TO_FILTER = 0, /* Packet flows towards filter hook */
+ AS_FLOW_FROM_FILTER, /* Packet arrived on filter hook */
+ AS_NOFLOW, /* Packet is immediately discarded */
+};
+
+/*
+ * Per hook info
+ *
+ * Each hook keeps track of its 'stats', carries a reference to itself ('hook')
+ * and is aware of its 'flow_direction' (to decide if in- or out-filters must be
+ * applied). The possible output hooks are prewired ('dest' and 'nomatch').
+ */
+/* clang-format off */
+struct hookinfo {
+ struct ng_antispoof_hookstat stats;
+ hook_p hook;
+ struct hookinfo * dest;
+ struct hookinfo * nomatch;
+ enum as_flow_direction flow_direction;
+};
+
+/*
+ * Per node info
+ *
+ * 'filters' contains 'filter_count' elements. The 'in_filter_count' left most
+ * (starting at index zero) elements are only validated for inbound traffic
+ * (originating at the 'downstream' hook). The 'out_filter_count' right most (at
+ * the end of the array) elements are only validated for outbound traffic
+ * (originating at the 'filter' hook). Packets that did not match any filter
+ * rule are diverted to the 'nomatch' hook.
+ */
+struct ng_antispoof_private {
+ struct hookinfo downstream;
+ struct hookinfo filter;
+ struct hookinfo nomatch;
+ struct ng_antispoof_filter *filters; /* filter array */
+ uint16_t filter_count; /* size of filter array */
+ uint16_t in_filter_count; /* number of inbound only filters */
+ uint16_t out_filter_count; /* number of outbound only filters */
+ struct ng_antispoof_config conf; /* node configuration */
+};
+
+struct ng_antispoof_filters_ary {
+ uint16_t n;
+ struct ng_antispoof_filter filters[];
+};
+/* clang-format on */
+
+/* Keep this in sync with the above structure definition */
+/* clang-format off */
+#define NG_ANTISPOOF_FILTERS_ARY_INFO(harytype) { \
+ { "n", &ng_parse_uint16_type }, \
+ { "filters", (harytype) }, \
+ { \
+ NULL \
+ } \
+}
+/* clang-format on */
+
+/*
+ * How to determine the length of the table returned by NGM_ANTISPOOF_GET_RULES
+ */
+static int
+ng_antispoof_getTableLength(
+ const struct ng_parse_type *type, const u_char *start, const u_char *buf)
+{
+ const struct ng_antispoof_filters_ary *const table =
+ (const struct ng_antispoof_filters_ary *)(buf -
+ OFFSET(struct ng_antispoof_filters_ary, filters));
+
+ return (table->n);
+}
+
+/* Parse type for struct ng_antispoof_config */
+static const struct ng_parse_struct_field ng_antispoof_config_type_fields[] =
+ NG_ANTISPOOF_CONFIG_TYPE_INFO;
+static const struct ng_parse_type ng_antispoof_config_type = {
+ &ng_parse_struct_type, &ng_antispoof_config_type_fields
+};
+
+/* Parse type for struct ng_antispoof_hookstat */
+static const struct ng_parse_struct_field ng_antispoof_hookstat_type_fields[] =
+ NG_ANTISPOOF_HOOKSTAT_INFO;
+static const struct ng_parse_type ng_antispoof_hookstat_type = {
+ &ng_parse_struct_type, &ng_antispoof_hookstat_type_fields
+};
+
+/* Parse type for struct ng_antispoof_stats */
+static const struct ng_parse_struct_field ng_antispoof_stats_type_fields[] =
+ NG_ANTISPOOF_STATS_INFO(&ng_antispoof_hookstat_type);
+static const struct ng_parse_type ng_antispoof_stats_type = {
+ &ng_parse_struct_type, &ng_antispoof_stats_type_fields
+};
+
+/* Parse type for struct ng_antispoof_filters_ary */
+static const struct ng_parse_array_info ng_antispoof_hary_type_info = {
+ &ng_parse_antispoof_filter_type, ng_antispoof_getTableLength
+};
+static const struct ng_parse_type ng_antispoof_hary_type = {
+ &ng_parse_array_type, &ng_antispoof_hary_type_info
+};
+static const struct ng_parse_struct_field ng_antispoof_host_ary_type_fields[] =
+ NG_ANTISPOOF_FILTERS_ARY_INFO(&ng_antispoof_hary_type);
+static const struct ng_parse_type ng_antispoof_host_ary_type = {
+ &ng_parse_struct_type, &ng_antispoof_host_ary_type_fields
+};
+
+/* List of commands and how to convert arguments to/from ASCII */
+static const struct ng_cmdlist ng_antispoof_cmds[] = {
+ /* clang-format off */
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_SET_CONFIG,
+ "setconfig",
+ &ng_antispoof_config_type,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_GET_CONFIG,
+ "getconfig",
+ NULL,
+ &ng_antispoof_config_type
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_ADD_INET,
+ "addinet",
+ &ng_antispoof_rule_ip_type,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_ADD_INET6,
+ "addinet6",
+ &ng_antispoof_rule_ipv6_type,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_DEL_INET,
+ "delinet",
+ &ng_antispoof_rule_ip_type,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_DEL_INET6,
+ "delinet6",
+ &ng_antispoof_rule_ipv6_type,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_GET_RULES,
+ "getrules",
+ NULL,
+ &ng_antispoof_host_ary_type
+ //&ng_antispoof_rules_type
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_RESET,
+ "reset",
+ NULL,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_GET_STATS,
+ "getstats",
+ NULL,
+ &ng_antispoof_stats_type
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_CLR_STATS,
+ "clrstats",
+ NULL,
+ NULL
+ },
+ {
+ NGM_ANTISPOOF_COOKIE,
+ NGM_ANTISPOOF_GETCLR_STATS,
+ "getclrstats",
+ NULL,
+ &ng_antispoof_stats_type
+ },
+ { 0 }
+ /* clang-format on */
+};
+
+/* Netgraph type descriptor */
+static struct ng_type ng_antispoof_typestruct = {
+ /* clang-format off */
+ .version = NG_ABI_VERSION,
+ .name = NG_ANTISPOOF_NODE_TYPE,
+ .constructor = ng_antispoof_constructor,
+ .rcvmsg = ng_antispoof_rcvmsg,
+ .close = ng_antispoof_close,
+ .shutdown = ng_antispoof_shutdown,
+ .newhook = ng_antispoof_newhook,
+ .rcvdata = ng_antispoof_rcvdata,
+ .disconnect = ng_antispoof_disconnect,
+ .cmdlist = ng_antispoof_cmds,
+ /* clang-format on */
+};
+NETGRAPH_INIT(antispoof, &ng_antispoof_typestruct);
+
+/*
+ * Node constructor
+ */
+static int
+ng_antispoof_constructor(node_p node)
+{
+ struct ng_antispoof_private *const privdata =
+ malloc(sizeof(*privdata), M_NETGRAPH_ANTISPOOF, M_WAITOK | M_ZERO);
+
+ privdata->filter.flow_direction = AS_FLOW_FROM_FILTER;
+ privdata->downstream.flow_direction = AS_FLOW_TO_FILTER;
+ privdata->nomatch.flow_direction = AS_NOFLOW;
+
+ NG_NODE_SET_PRIVATE(node, privdata);
+ return (0);
+}
+
+/*
+ * Add a hook
+ */
+static int
+ng_antispoof_newhook(node_p node, hook_p hook, const char *name)
+{
+ struct ng_antispoof_private *const privdata = NG_NODE_PRIVATE(node);
+ struct hookinfo * hinfo;
+
+ /* Precalculate internal paths. */
+ if (strcmp(name, NG_ANTISPOOF_HOOK_FILTER) == 0) {
+ hinfo = &privdata->filter;
+ privdata->downstream.dest = hinfo;
+ } else if (strcmp(name, NG_ANTISPOOF_HOOK_DOWNSTREAM) == 0) {
+ hinfo = &privdata->downstream;
+ privdata->filter.dest = hinfo;
+ } else if (strcmp(name, NG_ANTISPOOF_HOOK_NOMATCH) == 0) {
+ hinfo = &privdata->nomatch;
+ privdata->filter.nomatch = hinfo;
+ privdata->downstream.nomatch = hinfo;
+ privdata->nomatch.nomatch = NULL;
+ } else
+ return (EINVAL);
+
+ hinfo->hook = hook;
+ bzero(&hinfo->stats, sizeof(hinfo->stats));
+
+ NG_HOOK_SET_PRIVATE(hook, hinfo);
+ return (0);
+}
+
+static inline int
+ng_antispoof_getstats(struct ng_antispoof_private *privdata,
+ const struct ng_mesg *request, struct ng_mesg **response)
+{
+ int error = 0;
+
+ NG_MKRESPONSE(
+ *response, request, sizeof(struct ng_antispoof_stats), M_NOWAIT);
+
+ if (*response != NULL) {
+ struct ng_antispoof_stats *const stats =
+ (struct ng_antispoof_stats *)(*response)->data;
+ bcopy(&privdata->filter.stats, &stats->filter,
+ sizeof(stats->filter));
+ bcopy(&privdata->downstream.stats, &stats->downstream,
+ sizeof(stats->downstream));
+ bcopy(&privdata->nomatch.stats, &stats->nomatch,
+ sizeof(stats->nomatch));
+ } else
+ error = ENOMEM;
+
+ return (error);
+}
+
+static inline void
+ng_antispoof_clearstats(struct ng_antispoof_private *privdata)
+{
+ bzero(&privdata->filter.stats, sizeof(privdata->filter.stats));
+ bzero(&privdata->downstream.stats, sizeof(privdata->downstream.stats));
+ bzero(&privdata->nomatch.stats, sizeof(privdata->nomatch.stats));
+}
+
+/* Don't inline - not in the hot path */
+static uint16_t
+ng_antispoof_setconfig_find(struct ng_antispoof_private *privdata,
+ const struct ng_antispoof_filter * filter)
+{
+ for (uint_fast16_t i = 0; i < privdata->filter_count; ++i) {
+ if (memcmp(filter, &privdata->filters[i], sizeof(*filter)) == 0)
+ return (i);
+ }
+ return (UINT16_MAX);
+}
+
+/* TODO: Prevent uint16_t overflow (range check on privdata->filter_count) */
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_insert(struct ng_antispoof_private *privdata,
+ uint16_t pos, const struct ng_antispoof_filter *filter)
+{
+ pos = pos <= privdata->filter_count ? pos : privdata->filter_count;
+
+ struct ng_antispoof_filter *filters = mallocarray(
+ 1 + privdata->filter_count, sizeof(struct ng_antispoof_filter),
+ M_NETGRAPH_ANTISPOOF, M_WAITOK | M_ZERO);
+
+ if (privdata->filters != NULL) {
+ bcopy(privdata->filters, filters,
+ pos * sizeof(struct ng_antispoof_filter));
+ bcopy(privdata->filters + pos, filters + pos + 1,
+ (privdata->filter_count - pos) *
+ sizeof(struct ng_antispoof_filter));
+ free(privdata->filters, M_NETGRAPH_ANTISPOOF);
+ }
+
+ filters[pos] = *filter;
+
+ privdata->filters = filters;
+ ++privdata->filter_count;
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_remove(
+ struct ng_antispoof_private *privdata, uint16_t pos)
+{
+ if ((privdata->filters != NULL) && (pos < privdata->filter_count)) {
+ size_t tpos =
+ pos + 1; /* Index of first element after removed one */
+ bcopy(privdata->filters + tpos, privdata->filters + pos,
+ (privdata->filter_count - tpos) *
+ sizeof(*privdata->filters));
+
+ if (pos < privdata->in_filter_count)
+ --privdata->in_filter_count;
+ else if (pos >=
+ privdata->filter_count - privdata->out_filter_count)
+ --privdata->out_filter_count;
+
+ --privdata->filter_count;
+ }
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_mkfilter_ip(struct ng_antispoof_filter *new_filter,
+ const struct ng_antispoof_rule_ip * rule)
+{
+ if (new_filter == NULL)
+ return;
+ if (rule == NULL)
+ return;
+
+ bzero(new_filter, sizeof(*new_filter));
+
+ struct packet_info *filter = &new_filter->addr.pinfo;
+ struct packet_info *mask = &new_filter->mask.pinfo;
+
+ /* Filter IPv4 traffic */
+ filter->ether_type = htons(ETHERTYPE_IP);
+ mask->ether_type = 0xffff;
+
+ /* Ethernet address to match */
+ bcopy(&rule->ether.addr, filter->en_addr, sizeof(filter->en_addr));
+ mkethernetmask_or_default48(mask->en_addr, rule->ether.length);
+
+ /* IP address to match */
+ filter->ip_addr = rule->ip_addr.addr;
+ mask->ip_addr = rule->ip_mask;
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_mkfilter_ipv6(struct ng_antispoof_filter *new_filter,
+ const struct ng_antispoof_rule_ipv6 * rule)
+{
+ if (new_filter == NULL)
+ return;
+ if (rule == NULL)
+ return;
+
+ bzero(new_filter, sizeof(*new_filter));
+
+ struct packet_info *filter = &new_filter->addr.pinfo;
+ struct packet_info *mask = &new_filter->mask.pinfo;
+
+ /* Filter IPv6 traffic */
+ filter->ether_type = htons(ETHERTYPE_IPV6);
+ mask->ether_type = 0xffff;
+
+ /* Ethernet address to match */
+ bcopy(&rule->ether.addr, filter->en_addr, sizeof(filter->en_addr));
+ mkethernetmask_or_default48(mask->en_addr, rule->ether.length);
+
+ /* IPv6 address to match */
+ filter->ip6_addr = rule->ip6_addr.addr;
+ mkipv6mask_or_default128(&mask->ip6_addr, rule->ip6_addr.length);
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_addrule_ip(struct ng_antispoof_private *privdata,
+ const struct ng_antispoof_rule_ip * rule)
+{
+ struct ng_antispoof_filter filter;
+ ng_antispoof_setconfig_mkfilter_ip(&filter, rule);
+
+ /* Insert rule */
+ uint16_t slot = privdata->filter_count - privdata->out_filter_count;
+ ng_antispoof_setconfig_insert(privdata, slot, &filter);
+
+ /* Create rule to accept incoming traffic on ether ff:ff:ff:ff:ff:ff */
+ mkethernetmask_or_default48(filter.addr.pinfo.en_addr, 48);
+ mkethernetmask_or_default48(filter.mask.pinfo.en_addr, 48);
+
+ /* Insert rule */
+ slot = privdata->in_filter_count;
+ ng_antispoof_setconfig_insert(privdata, slot, &filter);
+ ++privdata->in_filter_count;
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_addrule_ipv6(struct ng_antispoof_private *privdata,
+ const struct ng_antispoof_rule_ipv6 * rule)
+{
+ struct ng_antispoof_filter filter;
+ ng_antispoof_setconfig_mkfilter_ipv6(&filter, rule);
+
+ /* Insert rule */
+ uint16_t slot = privdata->filter_count - privdata->out_filter_count;
+ ng_antispoof_setconfig_insert(privdata, slot, &filter);
+
+ /* Create rule to accept incoming traffic on ether 33:33:00:00:00:00/16
+ */
+ filter.addr.pinfo.en_addr[0] = 0x33;
+ filter.addr.pinfo.en_addr[1] = 0x33;
+ filter.addr.pinfo.en_addr[2] = 0;
+ filter.addr.pinfo.en_addr[3] = 0;
+ filter.addr.pinfo.en_addr[4] = 0;
+ filter.addr.pinfo.en_addr[5] = 0;
+ mkethernetmask_or_default48(filter.mask.pinfo.en_addr, 16);
+
+ /* Insert rule */
+ slot = privdata->in_filter_count;
+ ng_antispoof_setconfig_insert(privdata, slot, &filter);
+ ++privdata->in_filter_count;
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_delrule_ip(struct ng_antispoof_private *privdata,
+ const struct ng_antispoof_rule_ip * rule)
+{
+ struct ng_antispoof_filter filter;
+ ng_antispoof_setconfig_mkfilter_ip(&filter, rule);
+
+ /* Remove rule */
+ uint16_t slot;
+ while ((slot = ng_antispoof_setconfig_find(privdata, &filter)) !=
+ UINT16_MAX)
+ ng_antispoof_setconfig_remove(privdata, slot);
+
+ /* Remove rule to accept incoming traffic on ether ff:ff:ff:ff:ff:ff */
+ mkethernetmask_or_default48(filter.addr.pinfo.en_addr, 48);
+ mkethernetmask_or_default48(filter.mask.pinfo.en_addr, 48);
+
+ /* Remove rule */
+ while ((slot = ng_antispoof_setconfig_find(privdata, &filter)) !=
+ UINT16_MAX)
+ ng_antispoof_setconfig_remove(privdata, slot);
+}
+
+/* Don't inline - not in the hot path */
+static void
+ng_antispoof_setconfig_delrule_ipv6(struct ng_antispoof_private *privdata,
+ const struct ng_antispoof_rule_ipv6 * rule)
+{
+ struct ng_antispoof_filter filter;
+ ng_antispoof_setconfig_mkfilter_ipv6(&filter, rule);
+
+ /* Remove rule */
+ uint16_t slot;
+ while ((slot = ng_antispoof_setconfig_find(privdata, &filter)) !=
+ UINT16_MAX)
+ ng_antispoof_setconfig_remove(privdata, slot);
+
+ /* Remove rule to accept incoming traffic on ether 33:33:00:00:00:00/16
+ */
+ filter.addr.pinfo.en_addr[0] = 0x33;
+ filter.addr.pinfo.en_addr[1] = 0x33;
+ filter.addr.pinfo.en_addr[2] = 0;
+ filter.addr.pinfo.en_addr[3] = 0;
+ filter.addr.pinfo.en_addr[4] = 0;
+ filter.addr.pinfo.en_addr[5] = 0;
+ mkethernetmask_or_default48(filter.mask.pinfo.en_addr, 16);
+
+ /* Remove rule */
+ while ((slot = ng_antispoof_setconfig_find(privdata, &filter)) !=
+ UINT16_MAX)
+ ng_antispoof_setconfig_remove(privdata, slot);
+}
+
+static int
+ng_antispoof_rcvmsg_impl(struct ng_antispoof_private *privdata,
+ const struct ng_mesg *request, struct ng_mesg **response)
+{
+ int error = 0;
+
+ switch (request->header.cmd) {
+ case NGM_ANTISPOOF_GET_CONFIG: {
+ NG_MKRESPONSE(*response, request,
+ sizeof(struct ng_antispoof_config), M_NOWAIT);
+ if (*response == NULL) {
+ error = ENOMEM;
+ break;
+ }
+ struct ng_antispoof_config *const conf =
+ (struct ng_antispoof_config *)(*response)->data;
+ *conf = privdata->conf; /* no sanity checking needed */
+ break;
+ }
+ case NGM_ANTISPOOF_SET_CONFIG: {
+ if (request->header.arglen ==
+ sizeof(struct ng_antispoof_config))
+ privdata->conf =
+ *(const struct ng_antispoof_config *)request->data;
+ else
+ error = EINVAL;
+ break;
+ }
+ case NGM_ANTISPOOF_ADD_INET: {
+ if (request->header.arglen ==
+ sizeof(struct ng_antispoof_rule_ip)) {
+ struct ng_antispoof_rule_ip rule =
+ *(const struct ng_antispoof_rule_ip *)request->data;
+
+ /*
+ * If IP address came in prefix notation, ignore subnet
+ * mask
+ */
+ if (rule.ip_addr.length <= 32) {
+ error = mkipmask(
+ &rule.ip_mask, rule.ip_addr.length);
+ }
+
+ if (error == 0) {
+ ng_antispoof_setconfig_addrule_ip(
+ privdata, &rule);
+ }
+ } else
+ error = EINVAL;
+ break;
+ }
+ case NGM_ANTISPOOF_ADD_INET6: {
+ if (request->header.arglen ==
+ sizeof(struct ng_antispoof_rule_ipv6)) {
+ const struct ng_antispoof_rule_ipv6 *const rule =
+ (const struct ng_antispoof_rule_ipv6 *)
+ request->data;
+
+ if (error == 0) {
+ ng_antispoof_setconfig_addrule_ipv6(
+ privdata, rule);
+ }
+ } else
+ error = EINVAL;
+ break;
+ }
+ case NGM_ANTISPOOF_DEL_INET: {
+ if (request->header.arglen ==
+ sizeof(struct ng_antispoof_rule_ip)) {
+ struct ng_antispoof_rule_ip rule =
+ *(const struct ng_antispoof_rule_ip *)request->data;
+
+ /*
+ * If IP address came in prefix notation, ignore subnet
+ * mask
+ */
+ if (rule.ip_addr.length <= 32) {
+ error = mkipmask(
+ &rule.ip_mask, rule.ip_addr.length);
+ }
+
+ if (error == 0) {
+ ng_antispoof_setconfig_delrule_ip(
+ privdata, &rule);
+ }
+ } else
+ error = EINVAL;
+ break;
+ }
+ case NGM_ANTISPOOF_DEL_INET6: {
+ if (request->header.arglen ==
+ sizeof(struct ng_antispoof_rule_ipv6)) {
+ const struct ng_antispoof_rule_ipv6 *const rule =
+ (const struct ng_antispoof_rule_ipv6 *)
+ request->data;
+
+ if (error == 0) {
+ ng_antispoof_setconfig_delrule_ipv6(
+ privdata, rule);
+ }
+ } else
+ error = EINVAL;
+ break;
+ }
+ case NGM_ANTISPOOF_GET_RULES: {
+ uint16_t num_filters = privdata->filter_count -
+ privdata->in_filter_count - privdata->out_filter_count;
+
+ struct ng_antispoof_filters_ary *ary;
+ NG_MKRESPONSE(*response, request,
+ sizeof(*ary) + (num_filters * sizeof(*ary->filters)),
+ M_NOWAIT);
+ if (*response != NULL) {
+ ary = (struct ng_antispoof_filters_ary *)(*response)
+ ->data;
+ bcopy(privdata->filters + privdata->in_filter_count,
+ ary->filters, num_filters * sizeof(*ary->filters));
+ ary->n = num_filters;
+ } else
+ error = ENOMEM;
+ break;
+ }
+ case NGM_ANTISPOOF_RESET: {
+ free(privdata->filters, M_NETGRAPH_ANTISPOOF);
+ privdata->filters = NULL;
+ privdata->filter_count = 0;
+ privdata->in_filter_count = 0;
+ privdata->out_filter_count = 0;
+ bzero(&privdata->conf, sizeof(privdata->conf));
+ ng_antispoof_clearstats(privdata);
+ break;
+ }
+ case NGM_ANTISPOOF_GET_STATS: {
+ error = ng_antispoof_getstats(privdata, request, response);
+ break;
+ }
+ case NGM_ANTISPOOF_CLR_STATS: {
+ ng_antispoof_clearstats(privdata);
+ break;
+ }
+ case NGM_ANTISPOOF_GETCLR_STATS: {
+ error = ng_antispoof_getstats(privdata, request, response);
+ if (error == 0)
+ ng_antispoof_clearstats(privdata);
+ break;
+ }
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ return (error);
+}
+
+/*
+ * Receive a control request
+ */
+static int
+ng_antispoof_rcvmsg(node_p node, item_p item, hook_p lasthook)
+{
+ struct ng_antispoof_private *const privdata = NG_NODE_PRIVATE(node);
+ struct ng_mesg * response = NULL;
+ int error = 0;
+
+ struct ng_mesg *request;
+ NGI_GET_MSG(item, request);
+
+ switch (request->header.typecookie) {
+ case NGM_ANTISPOOF_COOKIE:
+ error = ng_antispoof_rcvmsg_impl(privdata, request, &response);
+ break;
+ case NGM_FLOW_COOKIE:
+ if (lasthook == privdata->downstream.hook ||
+ lasthook == privdata->filter.hook) {
+ const struct hookinfo *const hinfo =
+ NG_HOOK_PRIVATE(lasthook);
+ if (hinfo && hinfo->dest) {
+ NGI_MSG(item) = request;
+ NG_FWD_ITEM_HOOK(
+ error, item, hinfo->dest->hook);
+ return (error);
+ }
+ }
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ NG_RESPOND_MSG(error, node, item, response);
+ NG_FREE_MSG(request);
+ return (error);
+}
+
+/*
+ * Puts the first 'len' byte of the mbuf '*m' in contiguous memory and lets '*m'
+ * point to this memory.
+ *
+ * If 'len' is greater than the size of the packet, the pullup is not attempted
+ * and AS_E_PKT_TO_SMALL is returned. If m_pullup was unable to allocate memory,
+ * the mbuf is freed and AS_E_ENOBUFS is returned. Otherwise returns AS_NOERROR.
+ */
+static enum as_error
+pullup(struct mbuf **m, size_t len)
+{
+ enum as_error error = AS_NOERROR;
+
+ /* Sanity check packet and pull up header */
+ if ((*m)->m_pkthdr.len < len)
+ error = AS_E_PKT_TO_SMALL;
+ else if ((*m)->m_len < len && (*m = m_pullup(*m, len)) == NULL)
+ error = AS_E_ENOBUFS;
+
+ return (error);
+}
+
+/*
+ * Skips past an optional well-formed VLAN stack.
+ *
+ * Skips any number of IEEE 802.1ad (QinQ) headers followed by a single 802.1Q
+ * header. If a stack of QinQ headers is not terminated by a 802.1Q header, the
+ * frame is malformed and AS_E_MALFORMED_VLAN_STACK is returned.
+ */
+static inline enum as_error
+ng_antispoof_skip_vlan(
+ struct mbuf **m, struct packet_info *pkt_info, size_t *offset)
+{
+ typedef struct ng_antispoof_vlanhdr IEEE802_1Q;
+
+ bool isMultiTagged = false;
+ enum as_error error = AS_NOERROR;
+
+ /* htons() on a constant value will be optimized away by the compiler */
+ while (pkt_info->ether_type == htons(ETHERTYPE_QINQ)) {
+ isMultiTagged = true;
+ error = pullup(m, *offset + sizeof(IEEE802_1Q));
+
+ if (error == AS_NOERROR) {
+ const IEEE802_1Q *const evl =
+ (IEEE802_1Q *)(mtod(*m, u_char *) + *offset);
+ *offset += sizeof(IEEE802_1Q);
+ pkt_info->ether_type = evl->proto;
+ } else
+ return (error);
+ }
+
+ /* htons() on a constant value will be optimized away by the compiler */
+ if (pkt_info->ether_type == htons(ETHERTYPE_VLAN)) {
+ error = pullup(m, *offset + sizeof(IEEE802_1Q));
+
+ if (error == AS_NOERROR) {
+ const IEEE802_1Q *const evl =
+ (IEEE802_1Q *)(mtod(*m, u_char *) + *offset);
+ *offset += sizeof(IEEE802_1Q);
+ pkt_info->ether_type = evl->proto;
+ }
+ } else if (isMultiTagged)
+ /* In a multi-tagged frame, the last tag MUST be IEEE 802.1Q */
+ error = AS_E_MALFORMED_VLAN_STACK;
+
+ return (error);
+}
+
+/*
+ * Collects source and destination ethernet addresses and skips the optional
+ * VLAN stack.
+ */
+static inline enum as_error
+ng_antispoof_collect_layer2(struct mbuf **m, struct packet_info *pkt_info,
+ enum as_flow_direction flow, size_t *offset)
+{
+ enum as_error error = pullup(m, *offset + ETHER_HDR_LEN);
+
+ if (error == AS_NOERROR) {
+ const struct ether_header *const eh =
+ mtod(*m, struct ether_header *);
+ *offset += ETHER_HDR_LEN;
+
+ pkt_info->ether_type = eh->ether_type;
+ bcopy((flow == AS_FLOW_FROM_FILTER) ? eh->ether_shost :
+ eh->ether_dhost,
+ pkt_info->en_addr, ETHER_ADDR_LEN);
+
+ error = ng_antispoof_skip_vlan(m, pkt_info, offset);
+ }
+
+ return (error);
+}
+
+/* Collects source and destination IP addresses from an ARP frame */
+static inline enum as_error
+ng_antispoof_collect_arp(struct mbuf **m, struct packet_info *pkt_info,
+ enum as_flow_direction flow, size_t *offset)
+{
+ enum as_error error = pullup(m, *offset + sizeof(struct ether_arp));
+
+ if (error == AS_NOERROR) {
+ const struct ether_arp *arp =
+ (struct ether_arp *)(mtod(*m, u_char *) + *offset);
+ *offset += sizeof(struct ether_arp);
+
+ /* htons() on a constant value will be optimized away by the
+ * compiler */
+ if ((arp->ea_hdr.ar_pro == htons(ETHERTYPE_IP)) &&
+ (arp->ea_hdr.ar_hln == ETHER_ADDR_LEN)) {
+ pkt_info->ether_type = arp->ea_hdr.ar_pro;
+ bcopy((flow == AS_FLOW_FROM_FILTER) ? arp->arp_spa :
+ arp->arp_tpa,
+ &pkt_info->ip_addr, sizeof(pkt_info->ip_addr));
+ } else
+ error = AS_E_UNSUPPORTED_ARP;
+ }
+
+ return (error);
+}
+
+/* Collects source and destination IP addresses */
+static inline enum as_error
+ng_antispoof_collect_ip(struct mbuf **m, struct packet_info *pkt_info,
+ enum as_flow_direction flow, size_t *offset)
+{
+ int error = pullup(m, *offset + sizeof(struct ip));
+
+ if (error == AS_NOERROR) {
+ const struct ip *const ip =
+ (struct ip *)(mtod(*m, u_char *) + *offset);
+ *offset += sizeof(struct ether_arp);
+
+ pkt_info->ip_addr =
+ (flow == AS_FLOW_FROM_FILTER) ? ip->ip_src : ip->ip_dst;
+ }
+
+ return (error);
+}
+
+/* Collects source and destination IPv6 addresses */
+static inline enum as_error
+ng_antispoof_collect_ipv6(struct mbuf **m, struct packet_info *pkt_info,
+ enum as_flow_direction flow, size_t *offset)
+{
+ int error = pullup(m, *offset + sizeof(struct ip6_hdr));
+
+ if (error == AS_NOERROR) {
+ const struct ip6_hdr *const ipv6 =
+ (struct ip6_hdr *)(mtod(*m, u_char *) + *offset);
+ *offset += sizeof(struct ether_arp);
+
+ pkt_info->ip6_addr = (flow == AS_FLOW_FROM_FILTER) ?
+ ipv6->ip6_src :
+ ipv6->ip6_dst;
+ }
+
+ return (error);
+}
+
+/* Collects L3 based information (ARP, IP, IPv6) */
+static inline enum as_error
+ng_antispoof_collect_layer3(struct mbuf **m, struct packet_info *pkt_info,
+ enum as_flow_direction flow, size_t *offset)
+{
+ enum as_error error = AS_E_UNSUPPORTED_ET;
+
+ switch (ntohs(pkt_info->ether_type)) {
+ case ETHERTYPE_IP:
+ error = ng_antispoof_collect_ip(m, pkt_info, flow, offset);
+ break;
+ case ETHERTYPE_IPV6:
+ error = ng_antispoof_collect_ipv6(m, pkt_info, flow, offset);
+ break;
+ case ETHERTYPE_ARP:
+ error = ng_antispoof_collect_arp(m, pkt_info, flow, offset);
+ break;
+ default:
+ break;
+ }
+
+ return (error);
+}
+
+static inline enum as_error
+ng_antispoof_collect_data(
+ struct mbuf **m, struct packet_info *pkt_info, enum as_flow_direction flow)
+{
+ size_t offset = 0;
+ enum as_error error =
+ ng_antispoof_collect_layer2(m, pkt_info, flow, &offset);
+ if (error == AS_NOERROR)
+ error = ng_antispoof_collect_layer3(m, pkt_info, flow, &offset);
+ return (error);
+}
+
+/*
+ * Match given packet against ruleset
+ *
+ * A rule matches if 'filter == packet & mask' is true.
+ *
+ * 'begin' and 'end' mark the range of rules to be validated.
+ * 'begin' is inclusive, 'end' is exclusive (past the last element to be
+ * processed).
+ *
+ * Note: No range checking is performed by this function!
+ */
+static inline enum as_error
+ng_antispoof_packet_matches(const struct packet_info_conv *pkt_info,
+ const struct ng_antispoof_filter *filters, uint16_t begin, uint16_t end)
+{
+ bool is_match = false;
+ for (const struct ng_antispoof_filter *filter = filters + begin,
+ *filters_end = filters + end;
+ filter != filters_end && !is_match; ++filter) {
+ is_match = true;
+ for (uint_fast8_t e = 0; e < FILTER_RAW_SZ; ++e) {
+ is_match = is_match &&
+ ((pkt_info->raw[e] & filter->mask.raw[e]) ==
+ (filter->addr.raw[e] & filter->mask.raw[e]));
+ }
+ }
+ return (is_match ? AS_E_MATCH : AS_E_NOMATCH);
+}
+
+/*
+ * Receive data on a hook
+ *
+ * If data comes in the filter link check the source fields in the protocol
+ * headers. If they are valid, forward on the downstream link. Divert to
+ * nomatch link otherwise.
+ *
+ * If data comes in the downstream link check the target fields in the protocol
+ * headers. If they are valid, forward on the filter link. Divert to nomatch
+ * link otherwise.
+ *
+ * If data comes in the nomatch link drop packet on the floor (don't forward any
+ * data from nomatch).
+ */
+static int
+ng_antispoof_rcvdata(hook_p hook, item_p item)
+{
+ const struct ng_antispoof_private *const privdata =
+ NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+ struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook);
+ int ng_error = 0;
+
+ struct mbuf *m;
+ NGI_GET_M(item, m);
+
+ /* Update stats on incoming hook */
+ hinfo->stats.inOctets += m->m_pkthdr.len;
+ hinfo->stats.inFrames++;
+
+ /* Traffic entering at the nomatch hook is discarded immediately */
+ if (hinfo->flow_direction == AS_NOFLOW) {
+ NG_FREE_ITEM(item);
+ NG_FREE_M(m);
+ return (ng_error);
+ }
+
+ /* Collect information for filtering */
+ struct packet_info_conv pkt_info = { .raw = { 0 } };
+ enum as_error error = ng_antispoof_collect_data(
+ &m, &pkt_info.pinfo, hinfo->flow_direction);
+
+ if (error == AS_NOERROR) {
+ /* Calculate range of rules to be applied */
+ uint16_t begin = (hinfo->flow_direction == AS_FLOW_TO_FILTER) ?
+ 0 :
+ privdata->in_filter_count;
+ uint16_t end = (hinfo->flow_direction == AS_FLOW_FROM_FILTER) ?
+ privdata->filter_count :
+ privdata->filter_count - privdata->out_filter_count;
+
+ /* Match packet */
+ error = ng_antispoof_packet_matches(
+ &pkt_info, privdata->filters, begin, end);
+ }
+
+ switch (error) {
+ case AS_E_MATCH: {
+ /*
+ * Deliver frame out destination hook
+ * Update stats on outgoing hook
+ */
+ if (hinfo->dest != NULL) {
+ hinfo->dest->stats.outOctets += m->m_pkthdr.len;
+ hinfo->dest->stats.outFrames++;
+ NG_FWD_NEW_DATA(ng_error, item, hinfo->dest->hook, m);
+ }
+ /* Drop packet if there is no outgoing hook */
+ else {
+ NG_FREE_ITEM(item);
+ NG_FREE_M(m);
+ }
+ break;
+ }
+ case AS_E_PKT_TO_SMALL: {
+ /* Update statistics */
+ hinfo->stats.recvRunts++;
+ /* FALLTHROUGH */
+ }
+ case AS_NOERROR: /* FALLTHROUGH */
+ case AS_E_UNSUPPORTED_ET: /* FALLTHROUGH */
+ case AS_E_UNSUPPORTED_ARP: /* FALLTHROUGH */
+ case AS_E_MALFORMED_VLAN_STACK: /* FALLTHROUGH */
+ case AS_E_NOMATCH: {
+ /*
+ * Packet did not match any filter
+ * Update stats and deliver frame on nomatch hook if attached
+ */
+ if (hinfo->nomatch != NULL) {
+ hinfo->nomatch->stats.outOctets += m->m_pkthdr.len;
+ hinfo->nomatch->stats.outFrames++;
+ NG_FWD_NEW_DATA(
+ ng_error, item, hinfo->nomatch->hook, m);
+ } else {
+ NG_FREE_ITEM(item);
+ NG_FREE_M(m);
+ }
+ break;
+ }
+ case AS_E_ENOBUFS: {
+ /*
+ * m_pullup was unable to allocate memory and freed the mbuf
+ * Update stats and drop packet
+ */
+ hinfo->stats.memoryFailures++;
+ NG_FREE_ITEM(item);
+ ng_error = ENOBUFS;
+ break;
+ }
+ default: {
+ /*
+ * This should never be reached. Log an error and drop
+ * everything.
+ */
+ const node_p node = NG_HOOK_NODE(hook);
+ log(LOG_ERR,
+ "ng_antispoof: %s: %s(%d): Unreachable code. Please file a "
+ "bug report at https://bugs.freebsd.org/",
+ ng_antispoof_nodename(node), __func__, __LINE__);
+ NG_FREE_ITEM(item);
+ NG_FREE_M(m);
+ ng_error = EDOOFUS;
+ break;
+ }
+ }
+
+ /* We should not be here. Return a programming error. */
+ return (ng_error);
+}
+
+/*
+ * We are going to be shut down soon
+ *
+ * If we have both a filter and downstream hook, then we probably want to
+ * extricate ourselves and leave the two peers still linked to each other.
+ * Otherwise we should just shut down as a normal node would.
+ *
+ * No special treatment for the nomatch hook. Let it die with the node.
+ */
+static int
+ng_antispoof_close(node_p node)
+{
+ const struct ng_antispoof_private *const privdata =
+ NG_NODE_PRIVATE(node);
+
+ if (privdata->downstream.hook && privdata->filter.hook)
+ ng_bypass(privdata->downstream.hook, privdata->filter.hook);
+
+ return (0);
+}
+
+/*
+ * Shutdown processing
+ */
+static int
+ng_antispoof_shutdown(node_p node)
+{
+ struct ng_antispoof_private *const privdata = NG_NODE_PRIVATE(node);
+
+ NG_NODE_SET_PRIVATE(node, NULL);
+ free(privdata->filters, M_NETGRAPH_ANTISPOOF);
+ free(privdata, M_NETGRAPH_ANTISPOOF);
+ NG_NODE_UNREF(node);
+
+ return (0);
+}
+
+/*
+ * Hook disconnection
+ */
+static int
+ng_antispoof_disconnect(hook_p hook)
+{
+ struct ng_antispoof_private *const privdata =
+ NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+ struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook);
+
+ KASSERT(hinfo != NULL, ("%s: null info", __func__));
+ hinfo->hook = NULL;
+
+ /* Recalculate internal paths. */
+ if (privdata->downstream.dest == hinfo)
+ privdata->downstream.dest = NULL;
+ if (privdata->filter.dest == hinfo)
+ privdata->filter.dest = NULL;
+ if (privdata->downstream.nomatch == hinfo)
+ privdata->downstream.nomatch = NULL;
+ if (privdata->filter.nomatch == hinfo)
+ privdata->filter.nomatch = NULL;
+
+ /* Die when last hook disconnected. */
+ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
+ NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))
+ ng_rmnode_self(NG_HOOK_NODE(hook));
+
+ return (0);
+}
+
+/*
+ * Return node's "name", even if it doesn't have one.
+ */
+static const char *
+ng_antispoof_nodename(node_p node)
+{
+ static char name[NG_NODESIZ];
+
+ if (NG_NODE_HAS_NAME(node))
+ snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node));
+ else
+ snprintf(name, sizeof(name), "[%x]", ng_node2ID(node));
+
+ return (name);
+}
Index: sys/netgraph/ng_parse.h
===================================================================
--- sys/netgraph/ng_parse.h
+++ sys/netgraph/ng_parse.h
@@ -442,6 +442,14 @@
extern const struct ng_parse_type ng_parse_ipaddr_type;
/*
+ * IPv6 ADDRESS TYPE
+ *
+ * Default value: ::
+ * Additional info: None required
+ */
+extern const struct ng_parse_type ng_parse_ip6addr_type;
+
+/*
* ETHERNET ADDRESS TYPE
*
* Default value: None
Index: sys/netgraph/ng_parse.c
===================================================================
--- sys/netgraph/ng_parse.c
+++ sys/netgraph/ng_parse.c
@@ -67,6 +67,16 @@
#define M_NETGRAPH_PARSE M_NETGRAPH
#endif
+/*
+ * Compute alignment of type T
+ *
+ * Examples:
+ * x = ALIGNMENT_OF(int8_t); // x = 1
+ * x = ALIGNMENT_OF(int32_t); // x = 4
+ * x = ALIGNMENT_OF(struct in6_addr); // x = 4
+ */
+#define ALIGNMENT_OF(T) ((size_t)&((struct { char x; T y; } *)0)->y)
+
/* Compute alignment for primitive integral types */
struct int16_temp {
char x;
@@ -960,9 +970,11 @@
if ((error = ng_int8_parse(&ng_parse_int8_type,
s, off, start, buf + i, buflen)) != 0)
return (error);
- if (i < 3 && s[*off] != '.')
- return (EINVAL);
- (*off)++;
+ if (i < 3) {
+ if (s[*off] != '.')
+ return (EINVAL);
+ (*off)++;
+ }
}
*buflen = 4;
return (0);
@@ -1008,6 +1020,235 @@
};
/************************************************************************
+ IPV6 ADDRESS TYPE
+ ************************************************************************/
+
+static inline bool
+isipv6char(u_char ch)
+{
+ return (isxdigit(ch) || ch == ':');
+}
+
+/*
+ * Output IPv6 segment (range of 16-bit fields)
+ *
+ * start: begin of range (inclusive)
+ * end: end of range (exclusive)
+ *
+ * Example: begin=2 end=5 will output fields 2, 3 and 4.
+ */
+static int
+ng_ip6addr_unparse_segment(char **cbufp, int *cbuflenp,
+ const struct in6_addr *ip, size_t begin, size_t end)
+{
+ int error = end <= 8 ? 0 : ERANGE;
+ for (size_t i = begin; error == 0 && i < end; ++i) {
+ error = ng_parse_append(cbufp, cbuflenp, "%s%x",
+ i > begin ? ":" : "", ntohs(ip->__u6_addr.__u6_addr16[i]));
+ }
+ return (error);
+}
+
+/*
+ * Parse IPv6 address (RFC 4291)
+ *
+ * This parser supports the following forms of text representation:
+ * - The preferred form x:x:x:x:x:x:x:x (RFC 4291 2.2 (1))
+ * - Compressed zeros: :: (RFC 4291 2.2 (2))
+ *
+ * It does NOT support the following forms:
+ * - Mixed form with IPv4 representation of the last 32 bits (RFC 4291 2.2 (3))
+ * x:x:x:x:x:x:d.d.d.d or ::d.d.d.d
+ */
+static int
+ng_ip6addr_parse(const struct ng_parse_type *type,
+ const char *s, int *off, const u_char *const start,
+ u_char *const buf, int *buflen)
+{
+ struct in6_addr *ip = (struct in6_addr *)buf;
+
+ /* Make sure the buffer is big enough to hold the result */
+ if (*buflen < sizeof(*ip))
+ return (ERANGE);
+
+ int runpos = -1; /* Position of run of zeros (-1: no run exists) */
+ int field = 0; /* Number of parsed fields / index of next field */
+
+ /* :: (run of zeros) may occur at the beginning of address */
+ if (*(s + *off) == ':')
+ if (*(s + *off) == ':') {
+ runpos = field;
+ (*off) += 2;
+ }
+
+ /*
+ * As long as there is a hex digit or a colon on the input,
+ * 1. parse field
+ * 2. parse field separator or ::
+ */
+ while (isipv6char(*(s + *off))) {
+ /* Boundary check index of next field */
+ if (field >= 8)
+ return (EINVAL);
+
+ /* Parse field */
+ char *eptr = 0;
+ unsigned long val = strtoul(s + *off, &eptr, 16);
+ if (val <= 0xffff && eptr != s + *off) {
+ ip->__u6_addr.__u6_addr16[field] = htons((uint16_t)val);
+ *off = (eptr - s);
+ ++field;
+ } else
+ return (EINVAL);
+
+ /* Parse field separator (:) or run of zeros (::) */
+ if (*(s + *off) == ':') {
+ if (*(s + *off + 1) == ':') {
+ /* :: is allowed only once */
+ if (runpos == -1) {
+ runpos = field;
+ (*off) += 2;
+ } else
+ return (EINVAL);
+ }
+ /* A field separator must be followed by another field
+ */
+ else if (isxdigit(*(s + *off + 1)))
+ ++(*off);
+ else
+ return (EINVAL);
+ }
+ }
+
+ /* If a run of zeros exists, insert it at runpos */
+ if (runpos != -1) {
+ /* There must be room for at least one more field to expand ::
+ */
+ if (field >= 8)
+ return (EINVAL);
+
+ /* Not sure if memmove_s is always available in kernel space */
+ int fields2move = field - runpos;
+ for (int i = 1; i <= fields2move; ++i) {
+ ip->__u6_addr.__u6_addr16[8 - i] =
+ ip->__u6_addr.__u6_addr16[field - i];
+ }
+ /* Zero out run of zeros */
+ int runlen = 8 - field;
+ bzero(&ip->__u6_addr.__u6_addr16[runpos],
+ runlen * sizeof(ip->__u6_addr.__u6_addr16[runpos]));
+ }
+ /* Without ::, the number of parsed fields must be exactly 8 */
+ else if (field != 8)
+ return (EINVAL);
+
+ /* Report amount of data written to buffer */
+ *buflen = sizeof(*ip);
+
+ return (0);
+}
+
+/*
+ * Output address in its canonical form (RFC 5952)
+ *
+ * The following normalizations are implemented:
+ * - no leading zeroes in 16-bit fields
+ * - :: must not be used to shorten a single 16-bit field
+ * - :: replaces the longest run
+ * - :: replaces the first of the longest runs (if multiple)
+ * - lower case hex digits
+ *
+ * The following normalizations are NOT implemented:
+ * - embedded IPv4 addresses should be printed in mixed form
+ */
+static int
+ng_ip6addr_unparse(const struct ng_parse_type *type,
+ const u_char *data, int *off, char *cbuf, int cbuflen)
+{
+ const struct in6_addr *const ip =
+ (const struct in6_addr *)(data + *off);
+ int error = 0;
+
+ int r_start = 0; /* Result: start of first longest run */
+ int r_len = 0; /* Result: length of first longest run */
+ int p_start = 0; /* Start of currently parsed run */
+ int p_len = 0; /* Length of currently parsed run */
+
+ /* Find the first (left most) of the longest runs of zeroes */
+ for (int i = 0; i < 8; i++) {
+ if (ip->__u6_addr.__u6_addr16[i] == 0) {
+ /* Beginning of a new run? */
+ if (p_len == 0)
+ p_start = i;
+ ++p_len;
+ } else {
+ /* Use of LT makes sure we stick with the left most
+ * candidate */
+ if (r_len < p_len) {
+ r_start = p_start;
+ r_len = p_len;
+ }
+ p_len = 0;
+ }
+ }
+ /* Use of LT makes sure we stick with the left most candidate */
+ if (r_len < p_len) {
+ r_start = p_start;
+ r_len = p_len;
+ }
+
+ /* Don't use :: to shorten a single 16-bit field */
+ if (r_len > 1) {
+ error =
+ ng_ip6addr_unparse_segment(&cbuf, &cbuflen, ip, 0, r_start);
+ if (error == 0)
+ error = ng_parse_append(&cbuf, &cbuflen, "::");
+ if (error == 0)
+ error = ng_ip6addr_unparse_segment(
+ &cbuf, &cbuflen, ip, r_start + r_len, 8);
+ } else
+ error = ng_ip6addr_unparse_segment(&cbuf, &cbuflen, ip, 0, 8);
+
+ /* Increase offset by the amount of data read */
+ if (error == 0)
+ *off += sizeof(*ip);
+
+ return (error);
+}
+
+static int
+ng_ip6addr_getDefault(const struct ng_parse_type *type,
+ const u_char *const start, u_char *buf, int *buflen)
+{
+ struct in6_addr ip = IN6ADDR_ANY_INIT;
+ int error = 0;
+
+ if (*buflen >= sizeof(ip)) {
+ bcopy(&ip, buf, sizeof(ip));
+ *buflen = sizeof(ip);
+ } else
+ error = ERANGE;
+
+ return (error);
+}
+
+static int
+ng_ip6addr_getAlign(const struct ng_parse_type *type)
+{
+ return (ALIGNMENT_OF(struct in6_addr));
+}
+
+const struct ng_parse_type ng_parse_ip6addr_type = {
+ NULL,
+ NULL,
+ NULL,
+ ng_ip6addr_parse,
+ ng_ip6addr_unparse,
+ ng_ip6addr_getDefault,
+ ng_ip6addr_getAlign
+};
+
+/************************************************************************
ETHERNET ADDRESS TYPE
************************************************************************/

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 18, 10:29 PM (22 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14705780
Default Alt Text
D26420.diff (81 KB)

Event Timeline