The branch main has been updated by pouria: URL: https://cgit.FreeBSD.org/src/commit/?id=e1e18cc12e68762b641646b203d9ac42d10e3b1f
commit e1e18cc12e68762b641646b203d9ac42d10e3b1f Author: Pouria Mousavizadeh Tehrani <[email protected]> AuthorDate: 2026-02-18 18:12:35 +0000 Commit: Pouria Mousavizadeh Tehrani <[email protected]> CommitDate: 2026-02-18 19:42:29 +0000 if_gre: Add netlink support with tests Migrate to new if_clone KPI and implement netlink support for gre(4). Also refactor some of the gre specific ioctls. Reviewed by: glebius, zlei Differential Revision: https://reviews.freebsd.org/D54443 --- sys/net/if_gre.c | 473 ++++++++++++++++++++++++++++++++------ sys/net/if_gre.h | 1 + sys/netlink/route/interface.h | 23 ++ tests/sys/netlink/Makefile | 1 + tests/sys/netlink/test_rtnl_gre.c | 173 ++++++++++++++ 5 files changed, 605 insertions(+), 66 deletions(-) diff --git a/sys/net/if_gre.c b/sys/net/if_gre.c index ca9c4835daf6..758f25ccb859 100644 --- a/sys/net/if_gre.c +++ b/sys/net/if_gre.c @@ -90,6 +90,12 @@ #include <net/bpf.h> #include <net/if_gre.h> +#include <netlink/netlink.h> +#include <netlink/netlink_ctl.h> +#include <netlink/netlink_var.h> +#include <netlink/netlink_route.h> +#include <netlink/route/route_var.h> + #include <machine/in_cksum.h> #include <security/mac/mac_framework.h> @@ -100,9 +106,16 @@ MALLOC_DEFINE(M_GRE, grename, "Generic Routing Encapsulation"); static struct sx gre_ioctl_sx; SX_SYSINIT(gre_ioctl_sx, &gre_ioctl_sx, "gre_ioctl"); +#define GRE_LOCK_ASSERT() sx_assert(&gre_ioctl_sx, SA_XLOCKED); -static int gre_clone_create(struct if_clone *, int, caddr_t); -static void gre_clone_destroy(struct ifnet *); +static int gre_clone_create(struct if_clone *, char *, size_t, + struct ifc_data *, struct ifnet **); +static int gre_clone_destroy(struct if_clone *, struct ifnet *, + uint32_t); +static int gre_clone_create_nl(struct if_clone *, char *, size_t, + struct ifc_data_nl *); +static int gre_clone_modify_nl(struct ifnet *, struct ifc_data_nl *); +static void gre_clone_dump_nl(struct ifnet *, struct nl_writer *); VNET_DEFINE_STATIC(struct if_clone *, gre_cloner); #define V_gre_cloner VNET(gre_cloner) @@ -115,6 +128,18 @@ static int gre_ioctl(struct ifnet *, u_long, caddr_t); static int gre_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); static void gre_delete_tunnel(struct gre_softc *); +static int gre_set_addr_nl(struct gre_softc *, struct nl_pstate *, + struct sockaddr *, struct sockaddr *); + +static int gre_set_flags(struct gre_softc *, uint32_t); +static int gre_set_key(struct gre_softc *, uint32_t); +static int gre_set_udp_sport(struct gre_softc *, uint16_t); +static int gre_setopts(struct gre_softc *, u_long, uint32_t); + +static int gre_set_flags_nl(struct gre_softc *, struct nl_pstate *, uint32_t); +static int gre_set_key_nl(struct gre_softc *, struct nl_pstate *, uint32_t); +static int gre_set_encap_nl(struct gre_softc *, struct nl_pstate *, uint32_t); +static int gre_set_udp_sport_nl(struct gre_softc *, struct nl_pstate *, uint16_t); SYSCTL_DECL(_net_link); static SYSCTL_NODE(_net_link, IFT_TUNNEL, gre, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, @@ -136,12 +161,46 @@ VNET_DEFINE_STATIC(int, max_gre_nesting) = MAX_GRE_NEST; SYSCTL_INT(_net_link_gre, OID_AUTO, max_nesting, CTLFLAG_RW | CTLFLAG_VNET, &VNET_NAME(max_gre_nesting), 0, "Max nested tunnels"); +struct nl_parsed_gre { + struct sockaddr *ifla_local; + struct sockaddr *ifla_remote; + uint32_t ifla_flags; + uint32_t ifla_okey; + uint32_t ifla_encap_type; + uint16_t ifla_encap_sport; +}; + +#define _OUT(_field) offsetof(struct nl_parsed_gre, _field) +static const struct nlattr_parser nla_p_gre[] = { + { .type = IFLA_GRE_LOCAL, .off = _OUT(ifla_local), .cb = nlattr_get_ip }, + { .type = IFLA_GRE_REMOTE, .off = _OUT(ifla_remote), .cb = nlattr_get_ip }, + { .type = IFLA_GRE_FLAGS, .off = _OUT(ifla_flags), .cb = nlattr_get_uint32 }, + { .type = IFLA_GRE_OKEY, .off = _OUT(ifla_okey), .cb = nlattr_get_uint32 }, + { .type = IFLA_GRE_ENCAP_TYPE, .off = _OUT(ifla_encap_type), .cb = nlattr_get_uint32 }, + { .type = IFLA_GRE_ENCAP_SPORT, .off = _OUT(ifla_encap_sport), .cb = nlattr_get_uint16 }, +}; +#undef _OUT +NL_DECLARE_ATTR_PARSER(gre_modify_parser, nla_p_gre); + +static const struct nlhdr_parser *all_parsers[] = { + &gre_modify_parser, +}; + + static void vnet_gre_init(const void *unused __unused) { - - V_gre_cloner = if_clone_simple(grename, gre_clone_create, - gre_clone_destroy, 0); + struct if_clone_addreq_v2 req = { + .version = 2, + .flags = IFC_F_AUTOUNIT, + .match_f = NULL, + .create_f = gre_clone_create, + .destroy_f = gre_clone_destroy, + .create_nl_f = gre_clone_create_nl, + .modify_nl_f = gre_clone_modify_nl, + .dump_nl_f = gre_clone_dump_nl, + }; + V_gre_cloner = ifc_attach_cloner(grename, (struct if_clone_addreq *)&req); #ifdef INET in_gre_init(); #endif @@ -156,7 +215,7 @@ static void vnet_gre_uninit(const void *unused __unused) { - if_clone_detach(V_gre_cloner); + ifc_detach_cloner(V_gre_cloner); #ifdef INET in_gre_uninit(); #endif @@ -169,7 +228,129 @@ VNET_SYSUNINIT(vnet_gre_uninit, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY, vnet_gre_uninit, NULL); static int -gre_clone_create(struct if_clone *ifc, int unit, caddr_t params) +gre_clone_create_nl(struct if_clone *ifc, char *name, size_t len, + struct ifc_data_nl *ifd) +{ + struct ifc_data ifd_new = { + .flags = IFC_F_SYSSPACE, + .unit = ifd->unit, + }; + + return (gre_clone_create(ifc, name, len, &ifd_new, &ifd->ifp)); +} + +static int +gre_clone_modify_nl(struct ifnet *ifp, struct ifc_data_nl *ifd) +{ + struct gre_softc *sc = ifp->if_softc; + struct nl_parsed_link *lattrs = ifd->lattrs; + struct nl_pstate *npt = ifd->npt; + struct nl_parsed_gre params; + struct nlattr *attrs = lattrs->ifla_idata; + struct nlattr_bmask bm; + int error = 0; + + if ((attrs == NULL) || + (nl_has_attr(ifd->bm, IFLA_LINKINFO) == 0)) { + error = nl_modify_ifp_generic(ifp, lattrs, ifd->bm, npt); + return (error); + } + + error = priv_check(curthread, PRIV_NET_GRE); + if (error) + return (error); + + /* make sure ignored attributes by nl_parse will not cause panics */ + memset(¶ms, 0, sizeof(params)); + + nl_get_attrs_bmask_raw(NLA_DATA(attrs), NLA_DATA_LEN(attrs), &bm); + if ((error = nl_parse_nested(attrs, &gre_modify_parser, npt, ¶ms)) != 0) + return (error); + + if (nl_has_attr(&bm, IFLA_GRE_LOCAL) && nl_has_attr(&bm, IFLA_GRE_REMOTE)) + error = gre_set_addr_nl(sc, npt, params.ifla_local, params.ifla_remote); + else if (nl_has_attr(&bm, IFLA_GRE_LOCAL) || nl_has_attr(&bm, IFLA_GRE_REMOTE)) { + error = EINVAL; + nlmsg_report_err_msg(npt, "Specify both remote and local address together"); + } + + if (error == 0 && nl_has_attr(&bm, IFLA_GRE_FLAGS)) + error = gre_set_flags_nl(sc, npt, params.ifla_flags); + + if (error == 0 && nl_has_attr(&bm, IFLA_GRE_OKEY)) + error = gre_set_key_nl(sc, npt, params.ifla_okey); + + if (error == 0 && nl_has_attr(&bm, IFLA_GRE_ENCAP_TYPE)) + error = gre_set_encap_nl(sc, npt, params.ifla_encap_type); + + if (error == 0 && nl_has_attr(&bm, IFLA_GRE_ENCAP_SPORT)) + error = gre_set_udp_sport_nl(sc, npt, params.ifla_encap_sport); + + if (error == 0) + error = nl_modify_ifp_generic(ifp, ifd->lattrs, ifd->bm, ifd->npt); + + return (error); +} + +static void +gre_clone_dump_nl(struct ifnet *ifp, struct nl_writer *nw) +{ + GRE_RLOCK_TRACKER; + struct gre_softc *sc; + struct ifreq ifr; + + nlattr_add_u32(nw, IFLA_LINK, ifp->if_index); + nlattr_add_string(nw, IFLA_IFNAME, ifp->if_xname); + + int off = nlattr_add_nested(nw, IFLA_LINKINFO); + if (off == 0) + return; + + nlattr_add_string(nw, IFLA_INFO_KIND, "gre"); + int off2 = nlattr_add_nested(nw, IFLA_INFO_DATA); + if (off2 == 0) { + nlattr_set_len(nw, off); + return; + } + + sc = ifp->if_softc; + GRE_RLOCK(); + + if (sc->gre_family == AF_INET) { +#ifdef INET + if (in_gre_ioctl(sc, SIOCGIFPSRCADDR, (caddr_t)&ifr) == 0) + nlattr_add_in_addr(nw, IFLA_GRE_LOCAL, + (const struct in_addr *)&ifr.ifr_addr); + if (in_gre_ioctl(sc, SIOCGIFPDSTADDR, (caddr_t)&ifr) == 0) + nlattr_add_in_addr(nw, IFLA_GRE_LOCAL, + (const struct in_addr *)&ifr.ifr_dstaddr); +#endif + } else if (sc->gre_family == AF_INET6) { +#ifdef INET6 + if (in_gre_ioctl(sc, SIOCGIFPSRCADDR_IN6, (caddr_t)&ifr) == 0) + nlattr_add_in6_addr(nw, IFLA_GRE_LOCAL, + (const struct in6_addr *)&ifr.ifr_addr); + if (in_gre_ioctl(sc, SIOCGIFPDSTADDR_IN6, (caddr_t)&ifr) == 0) + nlattr_add_in6_addr(nw, IFLA_GRE_LOCAL, + (const struct in6_addr *)&ifr.ifr_dstaddr); +#endif + } + + nlattr_add_u32(nw, IFLA_GRE_FLAGS, sc->gre_options); + nlattr_add_u32(nw, IFLA_GRE_OKEY, sc->gre_key); + nlattr_add_u32(nw, IFLA_GRE_ENCAP_TYPE, + sc->gre_options & GRE_UDPENCAP ? IFLA_TUNNEL_GRE_UDP : IFLA_TUNNEL_NONE); + nlattr_add_u16(nw, IFLA_GRE_ENCAP_SPORT, sc->gre_port); + + nlattr_set_len(nw, off2); + nlattr_set_len(nw, off); + + GRE_RUNLOCK(); +} + +static int +gre_clone_create(struct if_clone *ifc, char *name, size_t len, + struct ifc_data *ifd, struct ifnet **ifpp) { struct gre_softc *sc; @@ -177,7 +358,7 @@ gre_clone_create(struct if_clone *ifc, int unit, caddr_t params) sc->gre_fibnum = curthread->td_proc->p_fibnum; GRE2IFP(sc) = if_alloc(IFT_TUNNEL); GRE2IFP(sc)->if_softc = sc; - if_initname(GRE2IFP(sc), grename, unit); + if_initname(GRE2IFP(sc), grename, ifd->unit); GRE2IFP(sc)->if_mtu = GREMTU; GRE2IFP(sc)->if_flags = IFF_POINTOPOINT|IFF_MULTICAST; @@ -192,6 +373,8 @@ gre_clone_create(struct if_clone *ifc, int unit, caddr_t params) GRE2IFP(sc)->if_capenable |= IFCAP_LINKSTATE; if_attach(GRE2IFP(sc)); bpfattach(GRE2IFP(sc), DLT_NULL, sizeof(u_int32_t)); + *ifpp = GRE2IFP(sc); + return (0); } @@ -210,8 +393,8 @@ gre_reassign(struct ifnet *ifp, struct vnet *new_vnet __unused, } #endif /* VIMAGE */ -static void -gre_clone_destroy(struct ifnet *ifp) +static int +gre_clone_destroy(struct if_clone *ifc, struct ifnet *ifp, uint32_t flags) { struct gre_softc *sc; @@ -226,8 +409,103 @@ gre_clone_destroy(struct ifnet *ifp) GRE_WAIT(); if_free(ifp); free(sc, M_GRE); + + return (0); +} + +static int +gre_set_key(struct gre_softc *sc, uint32_t key) +{ + int error = 0; + + GRE_LOCK_ASSERT(); + + if (sc->gre_key == key) + return (0); + error = gre_setopts(sc, GRESKEY, key); + + return (error); +} + +static int +gre_set_flags(struct gre_softc *sc, uint32_t opt) +{ + int error = 0; + + GRE_LOCK_ASSERT(); + + if (opt & ~GRE_OPTMASK) + return (EINVAL); + if (sc->gre_options == opt) + return (0); + error = gre_setopts(sc, GRESOPTS, opt); + + return (error); +} + +static int +gre_set_udp_sport(struct gre_softc *sc, uint16_t port) +{ + int error = 0; + + GRE_LOCK_ASSERT(); + + if (port != 0 && (port < V_ipport_hifirstauto || + port > V_ipport_hilastauto)) + return (EINVAL); + if (sc->gre_port == port) + return (0); + if ((sc->gre_options & GRE_UDPENCAP) == 0) { + /* + * UDP encapsulation is not enabled, thus + * there is no need to reattach softc. + */ + sc->gre_port = port; + return (0); + } + error = gre_setopts(sc, GRESPORT, port); + + return (error); } +static int +gre_setopts(struct gre_softc *sc, u_long cmd, uint32_t opt) +{ + int error = 0; + + GRE_LOCK_ASSERT(); + + switch (sc->gre_family) { +#ifdef INET + case AF_INET: + error = in_gre_setopts(sc, cmd, opt); + break; +#endif +#ifdef INET6 + case AF_INET6: + error = in6_gre_setopts(sc, cmd, opt); + break; +#endif + default: + /* + * Tunnel is not yet configured. + * We can just change any parameters. + */ + if (cmd == GRESKEY) + sc->gre_key = opt; + if (cmd == GRESOPTS) + sc->gre_options = opt; + if (cmd == GRESPORT) + sc->gre_port = opt; + break; + } + /* + * XXX: Do we need to initiate change of interface + * state here? + */ + return (error); +}; + static int gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { @@ -303,61 +581,12 @@ gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) if ((error = copyin(ifr_data_get_ptr(ifr), &opt, sizeof(opt))) != 0) break; - if (cmd == GRESKEY) { - if (sc->gre_key == opt) - break; - } else if (cmd == GRESOPTS) { - if (opt & ~GRE_OPTMASK) { - error = EINVAL; - break; - } - if (sc->gre_options == opt) - break; - } else if (cmd == GRESPORT) { - if (opt != 0 && (opt < V_ipport_hifirstauto || - opt > V_ipport_hilastauto)) { - error = EINVAL; - break; - } - if (sc->gre_port == opt) - break; - if ((sc->gre_options & GRE_UDPENCAP) == 0) { - /* - * UDP encapsulation is not enabled, thus - * there is no need to reattach softc. - */ - sc->gre_port = opt; - break; - } - } - switch (sc->gre_family) { -#ifdef INET - case AF_INET: - error = in_gre_setopts(sc, cmd, opt); - break; -#endif -#ifdef INET6 - case AF_INET6: - error = in6_gre_setopts(sc, cmd, opt); - break; -#endif - default: - /* - * Tunnel is not yet configured. - * We can just change any parameters. - */ - if (cmd == GRESKEY) - sc->gre_key = opt; - if (cmd == GRESOPTS) - sc->gre_options = opt; - if (cmd == GRESPORT) - sc->gre_port = opt; - break; - } - /* - * XXX: Do we need to initiate change of interface - * state here? - */ + if (cmd == GRESKEY) + error = gre_set_key(sc, opt); + else if (cmd == GRESOPTS) + error = gre_set_flags(sc, opt); + else if (cmd == GRESPORT) + error = gre_set_udp_sport(sc, opt); break; case GREGKEY: error = copyout(&sc->gre_key, ifr_data_get_ptr(ifr), @@ -539,7 +768,7 @@ gre_input(struct mbuf *m, int off, int proto, void *arg) } if (flags & GRE_FLAGS_KP) { #ifdef notyet - /* + /* * XXX: The current implementation uses the key only for outgoing * packets. But we can check the key value here, or even in the * encapcheck function. @@ -808,12 +1037,124 @@ gre_qflush(struct ifnet *ifp __unused) } +static int +gre_set_addr_nl(struct gre_softc *sc, struct nl_pstate *npt, + struct sockaddr *src, struct sockaddr *dst) +{ + union { + struct in_aliasreq in; + struct in6_aliasreq in6; + } aliasreq; + int error; + + /* XXX: this sanity check runs again in in[6]_gre_ioctl */ + if (src->sa_family != dst->sa_family) + error = EADDRNOTAVAIL; +#ifdef INET + else if (src->sa_family == AF_INET) { + memcpy(&aliasreq.in.ifra_addr, src, sizeof(struct sockaddr_in)); + memcpy(&aliasreq.in.ifra_dstaddr, dst, sizeof(struct sockaddr_in)); + sx_xlock(&gre_ioctl_sx); + error = in_gre_ioctl(sc, SIOCSIFPHYADDR, (caddr_t)&aliasreq.in); + sx_xunlock(&gre_ioctl_sx); +#endif +#ifdef INET6 + } else if (src->sa_family == AF_INET6) { + memcpy(&aliasreq.in6.ifra_addr, src, sizeof(struct sockaddr_in6)); + memcpy(&aliasreq.in6.ifra_dstaddr, dst, sizeof(struct sockaddr_in6)); + sx_xlock(&gre_ioctl_sx); + error = in6_gre_ioctl(sc, SIOCSIFPHYADDR_IN6, (caddr_t)&aliasreq.in6); + sx_xunlock(&gre_ioctl_sx); +#endif + } else + error = EAFNOSUPPORT; + + if (error == EADDRNOTAVAIL) + nlmsg_report_err_msg(npt, "address is invalid"); + if (error == EEXIST) + nlmsg_report_err_msg(npt, "remote and local addresses are the same"); + if (error == EAFNOSUPPORT) + nlmsg_report_err_msg(npt, "address family is not supported"); + + return (error); +} + +static int +gre_set_flags_nl(struct gre_softc *sc, struct nl_pstate *npt, uint32_t opt) +{ + int error = 0; + + sx_xlock(&gre_ioctl_sx); + error = gre_set_flags(sc, opt); + sx_xunlock(&gre_ioctl_sx); + + if (error == EINVAL) + nlmsg_report_err_msg(npt, "gre flags are invalid"); + + return (error); +} + +static int +gre_set_key_nl(struct gre_softc *sc, struct nl_pstate *npt, uint32_t key) +{ + int error = 0; + + sx_xlock(&gre_ioctl_sx); + error = gre_set_key(sc, key); + sx_xunlock(&gre_ioctl_sx); + + if (error == EINVAL) + nlmsg_report_err_msg(npt, "gre key is invalid: %u", key); + + return (error); +} + +static int +gre_set_encap_nl(struct gre_softc *sc, struct nl_pstate *npt, uint32_t type) +{ + uint32_t opt; + int error = 0; + + sx_xlock(&gre_ioctl_sx); + opt = sc->gre_options; + if (type & IFLA_TUNNEL_GRE_UDP) + opt |= GRE_UDPENCAP; + else + opt &= ~GRE_UDPENCAP; + error = gre_set_flags(sc, opt); + sx_xunlock(&gre_ioctl_sx); + + if (error == EEXIST) + nlmsg_report_err_msg(npt, "same gre tunnel exist"); + + return (error); +} + + +static int +gre_set_udp_sport_nl(struct gre_softc *sc, struct nl_pstate *npt, uint16_t port) +{ + int error = 0; + + sx_xlock(&gre_ioctl_sx); + error = gre_set_udp_sport(sc, port); + sx_xunlock(&gre_ioctl_sx); + + if (error == EINVAL) + nlmsg_report_err_msg(npt, "source port is invalid: %u", port); + + return (error); +} + + static int gremodevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: + NL_VERIFY_PARSERS(all_parsers); + break; case MOD_UNLOAD: break; default: diff --git a/sys/net/if_gre.h b/sys/net/if_gre.h index 67e4d88426fb..ae6d18385dfb 100644 --- a/sys/net/if_gre.h +++ b/sys/net/if_gre.h @@ -109,6 +109,7 @@ struct gre_softc { CK_LIST_ENTRY(gre_softc) chain; CK_LIST_ENTRY(gre_softc) srchash; }; + MALLOC_DECLARE(M_GRE); #ifndef GRE_HASH_SIZE diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h index 667bf2c96151..8b5189d1c588 100644 --- a/sys/netlink/route/interface.h +++ b/sys/netlink/route/interface.h @@ -246,6 +246,15 @@ enum { }; #define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) +/* Encapsulation Types */ +enum { + IFLA_TUNNEL_NONE, + IFLA_TUNNEL_GRE_UDP, /* GRE UDP Encapsulation */ + __IFLA_TUNNEL_MAX, +}; + +#define IFLA_TUNNEL_MAX (__IFLA_TUNNEL_MAX - 1) + /* IFLA_INFO_DATA vlan attributes */ enum { IFLA_VLAN_UNSPEC, @@ -263,4 +272,18 @@ struct ifla_vlan_flags { uint32_t mask; }; +/* IFLA_INFO_DATA gre attributes */ +enum { + IFLA_GRE_UNSPEC, + IFLA_GRE_LOCAL, + IFLA_GRE_REMOTE, + IFLA_GRE_FLAGS, + IFLA_GRE_OKEY, + IFLA_GRE_ENCAP_TYPE, + IFLA_GRE_ENCAP_SPORT, + __IFLA_GRE_MAX, +}; + +#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) + #endif diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile index c07ef8663867..43b9db80ee63 100644 --- a/tests/sys/netlink/Makefile +++ b/tests/sys/netlink/Makefile @@ -5,6 +5,7 @@ TESTSDIR= ${TESTSBASE}/sys/netlink ATF_TESTS_C+= netlink_socket ATF_TESTS_C+= test_snl test_snl_generic +ATF_TESTS_C+= test_rtnl_gre ATF_TESTS_PYTEST += test_nl_core.py ATF_TESTS_PYTEST += test_rtnl_iface.py ATF_TESTS_PYTEST += test_rtnl_ifaddr.py diff --git a/tests/sys/netlink/test_rtnl_gre.c b/tests/sys/netlink/test_rtnl_gre.c new file mode 100644 index 000000000000..12ecc8d14230 --- /dev/null +++ b/tests/sys/netlink/test_rtnl_gre.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/param.h> +#include <sys/module.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if_gre.h> + +#include <netlink/netlink.h> +#include <netlink/netlink_route.h> +#include "netlink/netlink_snl.h" +#include <netlink/netlink_snl_route.h> +#include <netlink/netlink_snl_route_compat.h> +#include <netlink/netlink_snl_route_parsers.h> + +#include <atf-c.h> + +struct nl_parsed_gre { + struct sockaddr *ifla_local; + struct sockaddr *ifla_remote; + uint32_t ifla_flags; + uint32_t ifla_okey; + uint32_t ifla_encap_type; + uint16_t ifla_encap_sport; +}; + +struct nla_gre_info { + const char *kind; + struct nl_parsed_gre data; +}; + +struct nla_gre_link { + uint32_t ifi_index; + struct nla_gre_info linkinfo; +}; + +#define _OUT(_field) offsetof(struct nl_parsed_gre, _field) +static const struct snl_attr_parser nla_p_gre[] = { + { .type = IFLA_GRE_LOCAL, .off = _OUT(ifla_local), .cb = snl_attr_get_ip }, + { .type = IFLA_GRE_REMOTE, .off = _OUT(ifla_remote), .cb = snl_attr_get_ip }, + { .type = IFLA_GRE_FLAGS, .off = _OUT(ifla_flags), .cb = snl_attr_get_uint32 }, + { .type = IFLA_GRE_OKEY, .off = _OUT(ifla_okey), .cb = snl_attr_get_uint32 }, + { .type = IFLA_GRE_ENCAP_TYPE, .off = _OUT(ifla_encap_type), .cb = snl_attr_get_uint32 }, + { .type = IFLA_GRE_ENCAP_SPORT, .off = _OUT(ifla_encap_sport), .cb = snl_attr_get_uint16 }, +}; +#undef _OUT +SNL_DECLARE_ATTR_PARSER(gre_linkinfo_data_parser, nla_p_gre); + +#define _OUT(_field) offsetof(struct nla_gre_info, _field) +static const struct snl_attr_parser ap_gre_linkinfo[] = { + { .type = IFLA_INFO_KIND, .off = _OUT(kind), .cb = snl_attr_get_string }, + { .type = IFLA_INFO_DATA, .off = _OUT(data), + .arg = &gre_linkinfo_data_parser, .cb = snl_attr_get_nested }, +}; +#undef _OUT +SNL_DECLARE_ATTR_PARSER(gre_linkinfo_parser, ap_gre_linkinfo); + +#define _IN(_field) offsetof(struct ifinfomsg, _field) +#define _OUT(_field) offsetof(struct nla_gre_link, _field) +static const struct snl_attr_parser ap_gre_link[] = { + { .type = IFLA_LINKINFO, .off = _OUT(linkinfo), + .arg = &gre_linkinfo_parser, .cb = snl_attr_get_nested }, +}; + +static const struct snl_field_parser fp_gre_link[] = { + { .off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 }, +}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(gre_parser, struct ifinfomsg, fp_gre_link, ap_gre_link); + +ATF_TC(test_rtnl_gre); +ATF_TC_HEAD(test_rtnl_gre, tc) +{ + atf_tc_set_md_var(tc, "descr", "test gre interface using netlink"); + atf_tc_set_md_var(tc, "require.user", "root"); +} + +ATF_TC_BODY(test_rtnl_gre, tc) +{ + struct snl_state ss; + struct snl_writer nw; + struct nlmsghdr *hdr, *rx_hdr; + struct sockaddr_in src, dst; + struct nla_gre_link lattrs = {}; + struct nl_parsed_gre attrs = {}; + struct snl_errmsg_data e = {}; + struct ifinfomsg *ifmsg; + int off, off2; + + ATF_REQUIRE_MSG(snl_init(&ss, NETLINK_ROUTE), "snl_init() failed"); + + /* Create gre interface */ + snl_init_writer(&ss, &nw); + ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWLINK)) != NULL); + hdr->nlmsg_flags |= (NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST | NLM_F_ACK); + snl_reserve_msg_object(&nw, struct ifinfomsg); + + /* Create parameters */ + snl_add_msg_attr_string(&nw, IFLA_IFNAME, "gre10"); + off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO); + snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "gre"); + off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA); + + src.sin_family = AF_INET; + dst.sin_family = AF_INET; + inet_pton(src.sin_family, "127.0.0.1", &src.sin_addr); + inet_pton(dst.sin_family, "127.0.0.2", &dst.sin_addr); + snl_add_msg_attr_ip(&nw, IFLA_GRE_LOCAL, (struct sockaddr *)&src); + snl_add_msg_attr_ip(&nw, IFLA_GRE_REMOTE, (struct sockaddr *)&dst); + snl_add_msg_attr_u32(&nw, IFLA_GRE_FLAGS, (GRE_ENABLE_SEQ | GRE_ENABLE_CSUM)); + snl_add_msg_attr_u32(&nw, IFLA_GRE_OKEY, 123456); + snl_add_msg_attr_u32(&nw, IFLA_GRE_ENCAP_TYPE, IFLA_TUNNEL_GRE_UDP); + snl_add_msg_attr_u16(&nw, IFLA_GRE_ENCAP_SPORT, 50000); + + snl_end_attr_nested(&nw, off2); + snl_end_attr_nested(&nw, off); + + ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL); + ATF_REQUIRE(snl_send_message(&ss, hdr)); + ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL); + ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e)); + ATF_REQUIRE_INTEQ(e.error, 0); + + /* Dump gre interface */ + snl_init_writer(&ss, &nw); + ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETLINK)) != NULL); + hdr->nlmsg_flags |= NLM_F_DUMP; + snl_reserve_msg_object(&nw, struct ifinfomsg); + snl_add_msg_attr_string(&nw, IFLA_IFNAME, "gre10"); + off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO); + snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "gre"); + snl_end_attr_nested(&nw, off); + + ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL); + ATF_REQUIRE(snl_send_message(&ss, hdr)); + + /* Check parameters */ + ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL); + ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &gre_parser, &lattrs)); + attrs = lattrs.linkinfo.data; + ATF_CHECK_STREQ(lattrs.linkinfo.kind, "gre"); + ATF_CHECK_INTEQ(attrs.ifla_flags, (GRE_ENABLE_SEQ | GRE_ENABLE_CSUM | GRE_UDPENCAP)); + ATF_CHECK_INTEQ(attrs.ifla_okey, 123456); + ATF_CHECK_INTEQ(attrs.ifla_encap_type, IFLA_TUNNEL_GRE_UDP); + ATF_CHECK_INTEQ(attrs.ifla_encap_sport, 50000); + + /* Delete gre interface */ + snl_init_writer(&ss, &nw); + ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_DELLINK)) != NULL); + hdr->nlmsg_flags |= (NLM_F_ACK | NLM_F_REQUEST); + ATF_REQUIRE((ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg)) != NULL); + ifmsg->ifi_index = lattrs.ifi_index; + ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL); + ATF_REQUIRE(snl_send_message(&ss, hdr)); + ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL); + ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e)); + ATF_REQUIRE_INTEQ(e.error, 0); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, test_rtnl_gre); + + return (atf_no_error()); +} +
