Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102845793
D26420.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
81 KB
Referenced Files
None
Subscribers
None
D26420.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D26420: New netgraph nodetype: ng_antispoof
Attached
Detach File
Event Timeline
Log In to Comment