diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c --- a/sbin/ifconfig/ifbridge.c +++ b/sbin/ifconfig/ifbridge.c @@ -48,6 +48,9 @@ #include #include +#include +#include + #include #include #include @@ -273,26 +276,81 @@ ifconfig_bridge_free_bridge_status(bridge); } +static struct nlmsghdr * +bridge_init_netlink(struct snl_state *ss, struct snl_writer *nw, if_ctx *ctx, + int request) +{ + struct nlmsghdr *hdr; + int family_id; + + if (!snl_init(ss, NETLINK_GENERIC)) + errx(1, "snl_init() failed (netlink not supported?)"); + + snl_init_writer(ss, nw); + + family_id = snl_get_genl_family(ss, BRIDGE_NL_FAMILY_NAME); + if (family_id == 0) + errx(1, "bridge netlink family not supported"); + + hdr = snl_create_genl_msg_request(nw, family_id, request); + snl_add_msg_attr_string(nw, BRIDGE_NL_BRIDGE, ctx->ifname); + + return (hdr); +} + +static void +bridge_send_netlink(struct snl_state *ss, struct snl_writer *nw, + struct nlmsghdr *hdr) +{ + struct snl_errmsg_data e = { }; + uint32_t seq_id; + + hdr = snl_finalize_msg(nw); + if (hdr == NULL) + errx(1, "failed to finalise the netlink message"); + + seq_id = hdr->nlmsg_seq; + if (!snl_send_message(ss, hdr)) + errx(1, "failed to send the netlink message"); + + if (!snl_read_reply_code(ss, seq_id, &e)) { + if (e.error_str != NULL) + errx(1, "%s", e.error_str); + else if (e.error != 0) + errc(1, e.error, NULL); + else + errx(1, "failed to read netlink reply"); + } + + snl_free(ss); +} + static void setbridge_add(if_ctx *ctx, const char *val, int dummy __unused) { - struct ifbreq req; + struct snl_state ss = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; - memset(&req, 0, sizeof(req)); - strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname)); - if (do_cmd(ctx, BRDGADD, &req, sizeof(req), 1) < 0) - err(1, "BRDGADD %s", val); + hdr = bridge_init_netlink(&ss, &nw, ctx, BRIDGE_NL_CMD_ADDMEMBER); + + snl_add_msg_attr_string(&nw, BRIDGE_NL_MEMBER, val); + + bridge_send_netlink(&ss, &nw, hdr); } static void setbridge_delete(if_ctx *ctx, const char *val, int dummy __unused) { - struct ifbreq req; + struct snl_state ss = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; - memset(&req, 0, sizeof(req)); - strlcpy(req.ifbr_ifsname, val, sizeof(req.ifbr_ifsname)); - if (do_cmd(ctx, BRDGDEL, &req, sizeof(req), 1) < 0) - err(1, "BRDGDEL %s", val); + hdr = bridge_init_netlink(&ss, &nw, ctx, BRIDGE_NL_CMD_REMOVEMEMBER); + + snl_add_msg_attr_string(&nw, BRIDGE_NL_MEMBER, val); + + bridge_send_netlink(&ss, &nw, hdr); } static void diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -132,6 +132,16 @@ #include #include +#include +#include +#include +#include + +#define DEBUG_MOD_NAME nl_bridge +#define DEBUG_MAX_LEVEL LOG_DEBUG3 +#include +_DECLARE_DEBUG(LOG_DEBUG); + #include #define EXTERR_CATEGORY EXTERR_CAT_BRIDGE @@ -386,6 +396,9 @@ static void bridge_delete_span(struct bridge_softc *, struct bridge_iflist *); +static void bridge_cmd_add(struct bridge_softc *sc, struct ifnet *ifs, + int *error, char const **errmsg); + static int bridge_ioctl_add(struct bridge_softc *, void *); static int bridge_ioctl_del(struct bridge_softc *, void *); static int bridge_ioctl_gifflags(struct bridge_softc *, void *); @@ -437,6 +450,9 @@ #endif /* INET6 */ static void bridge_linkstate(struct ifnet *ifp); static void bridge_linkcheck(struct bridge_softc *sc); +/* Netlink */ +static void bridge_nl_register(void); +static void bridge_nl_unregister(void); /* * Use the "null" value from IEEE 802.1Q-2014 Table 9-2 @@ -724,6 +740,7 @@ bridge_detach_cookie = EVENTHANDLER_REGISTER( ifnet_departure_event, bridge_ifdetach, NULL, EVENTHANDLER_PRI_ANY); + bridge_nl_register(); break; case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(ifnet_departure_event, @@ -732,6 +749,7 @@ bridge_same_p = NULL; bridge_get_softc_p = NULL; bridge_member_ifaddrs_p = NULL; + bridge_nl_unregister(); break; default: return (EOPNOTSUPP); @@ -1343,33 +1361,36 @@ NET_EPOCH_CALL(bridge_delete_member_cb, &bif->bif_epoch_ctx); } -static int -bridge_ioctl_add(struct bridge_softc *sc, void *arg) +static void +bridge_cmd_add(struct bridge_softc *sc, struct ifnet *ifs, int *error, + char const **errmsg) { - struct ifbreq *req = arg; - struct bridge_iflist *bif = NULL; - struct ifnet *ifs; - int error = 0; + struct bridge_iflist *bif; + +#define ERRMSG(e, s) \ + do { \ + *error = e; \ + *errmsg = s; \ + return; \ + } while (0) + + *error = 0; - ifs = ifunit(req->ifbr_ifsname); - if (ifs == NULL) - return (EXTERROR(ENOENT, "No such interface")); if (ifs->if_ioctl == NULL) /* must be supported */ - return (EXTERROR(EINVAL, "Interface must support ioctl(2)")); + ERRMSG(EINVAL, "Interface must support ioctl(2)"); /* If it's in the span list, it can't be a member. */ CK_LIST_FOREACH(bif, &sc->sc_spanlist, bif_next) if (ifs == bif->bif_ifp) - return (EXTERROR(EBUSY, - "Span interface cannot be a member")); + ERRMSG(EBUSY, "Span interface cannot be a member"); if (ifs->if_bridge) { struct bridge_iflist *sbif = ifs->if_bridge; if (sbif->bif_sc == sc) - return (EXTERROR(EEXIST, - "Interface is already a member of this bridge")); + ERRMSG(EEXIST, + "Interface is already a member of this bridge"); - return EXTERROR(EBUSY, + ERRMSG(EBUSY, "Interface is already a member of another bridge"); } @@ -1380,7 +1401,7 @@ /* permitted interface types */ break; default: - return (EXTERROR(EINVAL, "Unsupported interface type")); + ERRMSG(EINVAL, "Unsupported interface type"); } #ifdef INET6 @@ -1432,15 +1453,15 @@ CK_STAILQ_FOREACH(ifa, &ifs->if_addrhead, ifa_link) { #ifdef INET if (ifa->ifa_addr->sa_family == AF_INET) - return (EXTERROR(EINVAL, + ERRMSG(EINVAL, "Member interface may not have " - "an IPv4 address configured")); + "an IPv4 address configured"); #endif #ifdef INET6 if (ifa->ifa_addr->sa_family == AF_INET6) - return (EXTERROR(EINVAL, + ERRMSG(EINVAL, "Member interface may not have " - "an IPv6 address configured")); + "an IPv6 address configured"); #endif } } @@ -1455,21 +1476,19 @@ ifs->if_xname); ifr.ifr_mtu = sc->sc_ifp->if_mtu; - error = (*ifs->if_ioctl)(ifs, - SIOCSIFMTU, (caddr_t)&ifr); - if (error != 0) { + *error = (*ifs->if_ioctl)(ifs, SIOCSIFMTU, (caddr_t)&ifr); + if (*error != 0) { log(LOG_NOTICE, "%s: invalid MTU: %u for" " new member %s\n", sc->sc_ifp->if_xname, ifr.ifr_mtu, ifs->if_xname); - return (EXTERROR(EINVAL, - "Failed to set MTU on new member")); + ERRMSG(EINVAL, "Failed to set MTU on new member"); } } bif = malloc(sizeof(*bif), M_DEVBUF, M_NOWAIT|M_ZERO); if (bif == NULL) - return (ENOMEM); + ERRMSG(ENOMEM, "Failed to allocate bridge member"); bif->bif_sc = sc; bif->bif_ifp = ifs; @@ -1513,13 +1532,34 @@ switch (ifs->if_type) { case IFT_ETHER: case IFT_L2VLAN: - error = ifpromisc(ifs, 1); + *error = ifpromisc(ifs, 1); break; } - if (error) + if (*error) bridge_delete_member(sc, bif, 0); - return (error); + + return; +#undef ERRMSG +} + +static int +bridge_ioctl_add(struct bridge_softc *sc, void *arg) +{ + struct ifbreq *req = arg; + struct ifnet *ifs; + const char *errmsg; + int error = 0; + + ifs = ifunit(req->ifbr_ifsname); + if (ifs == NULL) + return (EXTERROR(ENOENT, "No such interface")); + + bridge_cmd_add(sc, ifs, &error, &errmsg); + if (error != 0) + return (EXTERROR(error, errmsg)); + + return (0); } static int @@ -4430,3 +4470,190 @@ } if_link_state_change(sc->sc_ifp, new_link); } + +/* + * Netlink support. + */ + +#define ERROUT(e) do { error = e; goto out; } while (0) +#define ERRMSG(e, ...) \ + do { \ + nlmsg_report_err_msg(npt, __VA_ARGS__); \ + ERROUT(e); \ + } while (0) + +/* modifymember is used for both add and modify requests */ + +struct brnl_modifymember { + const char *bridge; + const char *member; +}; + +#define _OUT(_field) offsetof(struct brnl_modifymember, _field) +static const struct nlattr_parser nla_p_modifymember[] = { + { .type = BRIDGE_NL_BRIDGE, .off = _OUT(bridge), .cb = nlattr_get_string }, + { .type = BRIDGE_NL_MEMBER, .off = _OUT(member), .cb = nlattr_get_string }, +}; +#undef _OUT +NL_DECLARE_PARSER(modifymember_parser, struct genlmsghdr, nlf_p_empty, + nla_p_modifymember); + +struct brnl_removemember { + const char *bridge; + const char *member; +}; + +#define _OUT(_field) offsetof(struct brnl_removemember, _field) +static const struct nlattr_parser nla_p_removemember[] = { + { .type = BRIDGE_NL_BRIDGE, .off = _OUT(bridge), .cb = nlattr_get_string }, + { .type = BRIDGE_NL_MEMBER, .off = _OUT(member), .cb = nlattr_get_string }, +}; +#undef _OUT +NL_DECLARE_PARSER(removemember_parser, struct genlmsghdr, nlf_p_empty, + nla_p_removemember); + +static const struct nlhdr_parser *bridge_parsers[] = { + &modifymember_parser, + &removemember_parser, +}; + +static int +bridge_nl_addmember(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct brnl_modifymember attrs = {}; + struct bridge_softc *sc; + struct ifnet *bridge, *member; + const char *errmsg; + int error; + + error = nl_parse_nlmsg(hdr, &modifymember_parser, npt, &attrs); + if (error != 0) + return (error); + + /* Get the bridge ifnet */ + + sc = NULL; + member = NULL; + + bridge = ifunit_ref(attrs.bridge); + if (bridge == NULL) + ERRMSG(ENOENT, "Interface %s does not exist", attrs.bridge); + + if (bridge->if_type != IFT_BRIDGE) + ERRMSG(EINVAL, "Interface %s is not a bridge", + bridge->if_xname); + + sc = bridge->if_softc; + BRIDGE_LOCK(sc); + + /* Get the new member ifnet */ + member = ifunit_ref(attrs.member); + if (member == NULL) + ERRMSG(ENOENT, "Interface %s does not exist", attrs.member); + + bridge_cmd_add(sc, member, &error, &errmsg); + if (error != 0) + ERRMSG(error, "%s", errmsg); + +out: + if (sc != NULL) + BRIDGE_UNLOCK(sc); + if (bridge != NULL) + if_rele(bridge); + if (member != NULL) + if_rele(member); + + return (error); +} + +static int +bridge_nl_removemember(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct brnl_removemember attrs = {}; + struct bridge_softc *sc; + struct ifnet *bridge, *member; + struct bridge_iflist *bif; + int error; + + error = nl_parse_nlmsg(hdr, &removemember_parser, npt, &attrs); + if (error != 0) + return (error); + + /* Get the bridge ifnet */ + + sc = NULL; + member = NULL; + + bridge = ifunit_ref(attrs.bridge); + + if (bridge == NULL) + ERRMSG(ENOENT, "Interface %s does not exist", attrs.bridge); + + if (bridge->if_type != IFT_BRIDGE) + ERRMSG(EINVAL, "Interface %s is not a bridge", + bridge->if_xname); + + sc = bridge->if_softc; + BRIDGE_LOCK(sc); + + /* Get the member bridge_iflist */ + member = ifunit_ref(attrs.member); + if (member == NULL) + ERRMSG(ENOENT, "Interface %s does not exist", attrs.member); + + bif = member->if_bridge; + if (bif == NULL) + ERRMSG(ENOENT, "Interface %s is not in a bridge", + member->if_xname); + + if (bif->bif_sc != sc) + ERRMSG(ENOENT, "Interface %s is not in bridge %s", + member->if_xname, bridge->if_xname); + + /* Delete the member */ + bridge_delete_member(sc, bif, 0); + +out: + if (sc != NULL) + BRIDGE_UNLOCK(sc); + if (bridge != NULL) + if_rele(bridge); + if (member != NULL) + if_rele(member); + return error; +} + +static const struct genl_cmd bridge_cmds[] = { + { + .cmd_num = BRIDGE_NL_CMD_ADDMEMBER, + .cmd_name = "ADDMEMBER", + .cmd_cb = bridge_nl_addmember, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NET_BRIDGE, + }, + { + .cmd_num = BRIDGE_NL_CMD_REMOVEMEMBER, + .cmd_name = "REMOVEMEMBER", + .cmd_cb = bridge_nl_removemember, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NET_BRIDGE, + }, +}; + +static uint16_t bridge_family_id; + +void +bridge_nl_register(void) +{ + NL_VERIFY_PARSERS(bridge_parsers); + + bridge_family_id = genl_register_family(BRIDGE_NL_FAMILY_NAME, 0, 2, + BRIDGE_NL_CMD_MAX); + genl_register_cmds(bridge_family_id, bridge_cmds, nitems(bridge_cmds)); +} + +void +bridge_nl_unregister(void) +{ + genl_unregister_family(bridge_family_id); +} diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h --- a/sys/net/if_bridgevar.h +++ b/sys/net/if_bridgevar.h @@ -87,12 +87,48 @@ #include #include +/* + * Netlink interface. + */ + +#define BRIDGE_NL_FAMILY_NAME "if_bridge" + +/* + * Attributes types + */ +#define BRIDGE_NL_BRIDGE 1 /* (string) bridge interface */ +#define BRIDGE_NL_MEMBER 2 /* (string) member interface */ + +/* + * Commands. + */ +enum { + BRIDGE_NL_CMD_UNSPEC = 0, + + /* + * BRIDGE_NL_CMD_ADDMEMBER: Add a new interface to the bridge. + * Arguments: BRIDGE_NL_BRIDGE: the bridge to add the member to + * BRIDGE_NL_MEMBER: the interface to add + */ + BRIDGE_NL_CMD_ADDMEMBER, + + /* + * BRIDGE_NL_CMD_REMOVEMEMBER: Remove an interface from a bridge. + * Arguments: BRIDGE_NL_BRIDGE: the bridge to remove the member from + * BRIDGE_NL_MEMBER: the interface to remove + */ + BRIDGE_NL_CMD_REMOVEMEMBER, + + __BRIDGE_NL_CMD_LAST +}; +#define BRIDGE_NL_CMD_MAX (__BRIDGE_NL_CMD_LAST - 1) + /* * Commands used in the SIOCSDRVSPEC ioctl. Note the lookup of the * bridge interface itself is keyed off the ifdrv structure. */ -#define BRDGADD 0 /* add bridge member (ifbreq) */ -#define BRDGDEL 1 /* delete bridge member (ifbreq) */ +#define BRDGADD 0 /* compat; use Netlink instead */ +#define BRDGDEL 1 /* compat; use Netlink instead */ #define BRDGGIFFLGS 2 /* get member if flags (ifbreq) */ #define BRDGSIFFLGS 3 /* set member if flags (ifbreq) */ #define BRDGSCACHE 4 /* set cache size (ifbrparam) */