// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2021 Linux Test Project */ #include #include #include #include #include #include "lapi/rtnetlink.h" #define TST_NO_DEFAULT_MAIN #include "tst_test.h" #include "tst_netlink.h" #include "tst_netdevice.h" static struct tst_netlink_context *create_request(const char *file, const int lineno, unsigned int type, unsigned int flags, const void *payload, size_t psize) { struct tst_netlink_context *ctx; struct nlmsghdr header = { .nlmsg_type = type, .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags, }; ctx = tst_netlink_create_context(file, lineno, NETLINK_ROUTE); if (!ctx) return NULL; if (!tst_netlink_add_message(file, lineno, ctx, &header, payload, psize)) { tst_netlink_destroy_context(file, lineno, ctx); return NULL; } return ctx; } int tst_netdev_index_by_name(const char *file, const int lineno, const char *ifname) { struct ifreq ifr; int sock, ret; if (strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return -1; } sock = safe_socket(file, lineno, NULL, AF_INET, SOCK_DGRAM, 0); if (sock < 0) return -1; strcpy(ifr.ifr_name, ifname); ret = SAFE_IOCTL_(file, lineno, sock, SIOCGIFINDEX, &ifr); safe_close(file, lineno, NULL, sock); return ret ? -1 : ifr.ifr_ifindex; } int tst_netdev_set_state(const char *file, const int lineno, const char *ifname, int up) { struct ifreq ifr; int sock, ret; if (strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return -1; } sock = safe_socket(file, lineno, NULL, AF_INET, SOCK_DGRAM, 0); if (sock < 0) return -1; strcpy(ifr.ifr_name, ifname); ret = SAFE_IOCTL_(file, lineno, sock, SIOCGIFFLAGS, &ifr); if (ret) { safe_close(file, lineno, NULL, sock); return ret; } if (up) ifr.ifr_flags |= IFF_UP; else ifr.ifr_flags &= ~IFF_UP; ret = SAFE_IOCTL_(file, lineno, sock, SIOCSIFFLAGS, &ifr); safe_close(file, lineno, NULL, sock); return ret; } int tst_create_veth_pair(const char *file, const int lineno, int strict, const char *ifname1, const char *ifname2) { int ret; struct ifinfomsg info = { .ifi_family = AF_UNSPEC }; struct tst_netlink_context *ctx; struct tst_netlink_attr_list peerinfo[] = { {IFLA_IFNAME, ifname2, strlen(ifname2) + 1, NULL}, {0, NULL, -1, NULL} }; struct tst_netlink_attr_list peerdata[] = { {VETH_INFO_PEER, &info, sizeof(info), peerinfo}, {0, NULL, -1, NULL} }; struct tst_netlink_attr_list attrs[] = { {IFLA_IFNAME, ifname1, strlen(ifname1) + 1, NULL}, {IFLA_LINKINFO, NULL, 0, (const struct tst_netlink_attr_list[]){ {IFLA_INFO_KIND, "veth", 4, NULL}, {IFLA_INFO_DATA, NULL, 0, peerdata}, {0, NULL, -1, NULL} }}, {0, NULL, -1, NULL} }; if (strlen(ifname1) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname1); return 0; } if (strlen(ifname2) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname2); return 0; } ctx = create_request(file, lineno, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL, &info, sizeof(info)); if (!ctx) return 0; if (tst_rtnl_add_attr_list(file, lineno, ctx, attrs) != 2) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to create veth interfaces %s+%s: %s", ifname1, ifname2, tst_strerrno(tst_netlink_errno)); } return ret; } int tst_netdev_add_device(const char *file, const int lineno, int strict, const char *ifname, const char *devtype) { int ret; struct ifinfomsg info = { .ifi_family = AF_UNSPEC }; struct tst_netlink_context *ctx; struct tst_netlink_attr_list attrs[] = { {IFLA_IFNAME, ifname, strlen(ifname) + 1, NULL}, {IFLA_LINKINFO, NULL, 0, (const struct tst_netlink_attr_list[]){ {IFLA_INFO_KIND, devtype, strlen(devtype), NULL}, {0, NULL, -1, NULL} }}, {0, NULL, -1, NULL} }; if (strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return 0; } ctx = create_request(file, lineno, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL, &info, sizeof(info)); if (!ctx) return 0; if (tst_rtnl_add_attr_list(file, lineno, ctx, attrs) != 2) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to create %s device %s: %s", devtype, ifname, tst_strerrno(tst_netlink_errno)); } return ret; } int tst_netdev_remove_device(const char *file, const int lineno, int strict, const char *ifname) { struct ifinfomsg info = { .ifi_family = AF_UNSPEC }; struct tst_netlink_context *ctx; int ret; if (strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return 0; } ctx = create_request(file, lineno, RTM_DELLINK, 0, &info, sizeof(info)); if (!ctx) return 0; if (!tst_rtnl_add_attr_string(file, lineno, ctx, IFLA_IFNAME, ifname)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to remove netdevice %s: %s", ifname, tst_strerrno(tst_netlink_errno)); } return ret; } static int modify_address(const char *file, const int lineno, int strict, unsigned int action, unsigned int nl_flags, const char *ifname, unsigned int family, const void *address, unsigned int prefix, size_t addrlen, uint32_t addr_flags) { struct tst_netlink_context *ctx; int index, ret; struct ifaddrmsg info = { .ifa_family = family, .ifa_prefixlen = prefix }; index = tst_netdev_index_by_name(file, lineno, ifname); if (index < 0) { tst_brk_(file, lineno, TBROK, "Interface %s not found", ifname); return 0; } info.ifa_index = index; ctx = create_request(file, lineno, action, nl_flags, &info, sizeof(info)); if (!ctx) return 0; if (!tst_rtnl_add_attr(file, lineno, ctx, IFA_FLAGS, &addr_flags, sizeof(uint32_t))) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (!tst_rtnl_add_attr(file, lineno, ctx, IFA_LOCAL, address, addrlen)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to modify %s network address: %s", ifname, tst_strerrno(tst_netlink_errno)); } return ret; } int tst_netdev_add_address(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, const void *address, unsigned int prefix, size_t addrlen, unsigned int flags) { return modify_address(file, lineno, strict, RTM_NEWADDR, NLM_F_CREATE | NLM_F_EXCL, ifname, family, address, prefix, addrlen, flags); } int tst_netdev_add_address_inet(const char *file, const int lineno, int strict, const char *ifname, in_addr_t address, unsigned int prefix, unsigned int flags) { return tst_netdev_add_address(file, lineno, strict, ifname, AF_INET, &address, prefix, sizeof(address), flags); } int tst_netdev_remove_address(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, const void *address, size_t addrlen) { return modify_address(file, lineno, strict, RTM_DELADDR, 0, ifname, family, address, 0, addrlen, 0); } int tst_netdev_remove_address_inet(const char *file, const int lineno, int strict, const char *ifname, in_addr_t address) { return tst_netdev_remove_address(file, lineno, strict, ifname, AF_INET, &address, sizeof(address)); } static int change_ns(const char *file, const int lineno, int strict, const char *ifname, unsigned short attr, uint32_t value) { struct ifinfomsg info = { .ifi_family = AF_UNSPEC }; struct tst_netlink_context *ctx; int ret; if (strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return 0; } ctx = create_request(file, lineno, RTM_NEWLINK, 0, &info, sizeof(info)); if (!ctx) return 0; if (!tst_rtnl_add_attr_string(file, lineno, ctx, IFLA_IFNAME, ifname)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (!tst_rtnl_add_attr(file, lineno, ctx, attr, &value, sizeof(uint32_t))) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to move %s to another namespace: %s", ifname, tst_strerrno(tst_netlink_errno)); } return ret; } int tst_netdev_change_ns_fd(const char *file, const int lineno, int strict, const char *ifname, int nsfd) { return change_ns(file, lineno, strict, ifname, IFLA_NET_NS_FD, nsfd); } int tst_netdev_change_ns_pid(const char *file, const int lineno, int strict, const char *ifname, pid_t nspid) { return change_ns(file, lineno, strict, ifname, IFLA_NET_NS_PID, nspid); } static int modify_route(const char *file, const int lineno, int strict, unsigned int action, unsigned int flags, const char *ifname, unsigned int family, const void *srcaddr, unsigned int srcprefix, size_t srclen, const void *dstaddr, unsigned int dstprefix, size_t dstlen, const void *gateway, size_t gatewaylen) { struct tst_netlink_context *ctx; int ret; int32_t index; struct rtmsg info = { .rtm_family = family, .rtm_dst_len = dstprefix, .rtm_src_len = srcprefix, .rtm_table = RT_TABLE_MAIN, .rtm_protocol = RTPROT_STATIC, .rtm_type = RTN_UNICAST }; if (!ifname && !gateway) { tst_brk_(file, lineno, TBROK, "Interface name or gateway address required"); return 0; } if (ifname && strlen(ifname) >= IFNAMSIZ) { tst_brk_(file, lineno, TBROK, "Network device name \"%s\" too long", ifname); return 0; } if (ifname) { index = tst_netdev_index_by_name(file, lineno, ifname); if (index < 0) return 0; } if (action == RTM_DELROUTE) info.rtm_scope = RT_SCOPE_NOWHERE; else info.rtm_scope = RT_SCOPE_UNIVERSE; ctx = create_request(file, lineno, action, flags, &info, sizeof(info)); if (!ctx) return 0; if (srcaddr && !tst_rtnl_add_attr(file, lineno, ctx, RTA_SRC, srcaddr, srclen)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (dstaddr && !tst_rtnl_add_attr(file, lineno, ctx, RTA_DST, dstaddr, dstlen)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (gateway && !tst_rtnl_add_attr(file, lineno, ctx, RTA_GATEWAY, gateway, gatewaylen)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (ifname && !tst_rtnl_add_attr(file, lineno, ctx, RTA_OIF, &index, sizeof(index))) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to modify network route: %s", tst_strerrno(tst_netlink_errno)); } return ret; } static int modify_route_inet(const char *file, const int lineno, int strict, unsigned int action, unsigned int flags, const char *ifname, in_addr_t srcaddr, unsigned int srcprefix, in_addr_t dstaddr, unsigned int dstprefix, in_addr_t gateway) { void *src = NULL, *dst = NULL, *gw = NULL; size_t srclen = 0, dstlen = 0, gwlen = 0; if (srcprefix) { src = &srcaddr; srclen = sizeof(srcaddr); } if (dstprefix) { dst = &dstaddr; dstlen = sizeof(dstaddr); } if (gateway) { gw = &gateway; gwlen = sizeof(gateway); } return modify_route(file, lineno, strict, action, flags, ifname, AF_INET, src, srcprefix, srclen, dst, dstprefix, dstlen, gw, gwlen); } int tst_netdev_add_route(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, const void *srcaddr, unsigned int srcprefix, size_t srclen, const void *dstaddr, unsigned int dstprefix, size_t dstlen, const void *gateway, size_t gatewaylen) { return modify_route(file, lineno, strict, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, ifname, family, srcaddr, srcprefix, srclen, dstaddr, dstprefix, dstlen, gateway, gatewaylen); } int tst_netdev_add_route_inet(const char *file, const int lineno, int strict, const char *ifname, in_addr_t srcaddr, unsigned int srcprefix, in_addr_t dstaddr, unsigned int dstprefix, in_addr_t gateway) { return modify_route_inet(file, lineno, strict, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, ifname, srcaddr, srcprefix, dstaddr, dstprefix, gateway); } int tst_netdev_remove_route(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, const void *srcaddr, unsigned int srcprefix, size_t srclen, const void *dstaddr, unsigned int dstprefix, size_t dstlen, const void *gateway, size_t gatewaylen) { return modify_route(file, lineno, strict, RTM_DELROUTE, 0, ifname, family, srcaddr, srcprefix, srclen, dstaddr, dstprefix, dstlen, gateway, gatewaylen); } int tst_netdev_remove_route_inet(const char *file, const int lineno, int strict, const char *ifname, in_addr_t srcaddr, unsigned int srcprefix, in_addr_t dstaddr, unsigned int dstprefix, in_addr_t gateway) { return modify_route_inet(file, lineno, strict, RTM_DELROUTE, 0, ifname, srcaddr, srcprefix, dstaddr, dstprefix, gateway); } static int modify_qdisc(const char *file, const int lineno, int strict, const char *object, unsigned int action, unsigned int nl_flags, const char *ifname, unsigned int family, unsigned int parent, unsigned int handle, unsigned int info, const char *qd_kind, const struct tst_netlink_attr_list *config) { struct tst_netlink_context *ctx; int ret; struct tcmsg msg = { .tcm_family = family, .tcm_handle = handle, .tcm_parent = parent, .tcm_info = info }; if (!qd_kind) { tst_brk_(file, lineno, TBROK, "Queueing discipline name required"); return 0; } if (ifname) { msg.tcm_ifindex = tst_netdev_index_by_name(file, lineno, ifname); if (msg.tcm_ifindex < 0) { tst_brk_(file, lineno, TBROK, "Interface %s not found", ifname); return 0; } } ctx = create_request(file, lineno, action, nl_flags, &msg, sizeof(msg)); if (!ctx) return 0; if (!tst_rtnl_add_attr_string(file, lineno, ctx, TCA_KIND, qd_kind)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } if (config && !tst_rtnl_add_attr_list(file, lineno, ctx, config)) { tst_netlink_destroy_context(file, lineno, ctx); return 0; } ret = tst_netlink_send_validate(file, lineno, ctx); tst_netlink_destroy_context(file, lineno, ctx); if (strict && !ret) { tst_brk_(file, lineno, TBROK, "Failed to modify %s: %s", object, tst_strerrno(tst_netlink_errno)); } return ret; } int tst_netdev_add_qdisc(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, unsigned int parent, unsigned int handle, const char *qd_kind, const struct tst_netlink_attr_list *config) { return modify_qdisc(file, lineno, strict, "queueing discipline", RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL, ifname, family, parent, handle, 0, qd_kind, config); } int tst_netdev_remove_qdisc(const char *file, const int lineno, int strict, const char *ifname, unsigned int family, unsigned int parent, unsigned int handle, const char *qd_kind) { return modify_qdisc(file, lineno, strict, "queueing discipline", RTM_DELQDISC, 0, ifname, family, parent, handle, 0, qd_kind, NULL); } int tst_netdev_add_traffic_class(const char *file, const int lineno, int strict, const char *ifname, unsigned int parent, unsigned int handle, const char *qd_kind, const struct tst_netlink_attr_list *config) { return modify_qdisc(file, lineno, strict, "traffic class", RTM_NEWTCLASS, NLM_F_CREATE | NLM_F_EXCL, ifname, AF_UNSPEC, parent, handle, 0, qd_kind, config); } int tst_netdev_remove_traffic_class(const char *file, const int lineno, int strict, const char *ifname, unsigned int parent, unsigned int handle, const char *qd_kind) { return modify_qdisc(file, lineno, strict, "traffic class", RTM_DELTCLASS, 0, ifname, AF_UNSPEC, parent, handle, 0, qd_kind, NULL); } int tst_netdev_add_traffic_filter(const char *file, const int lineno, int strict, const char *ifname, unsigned int parent, unsigned int handle, unsigned int protocol, unsigned int priority, const char *f_kind, const struct tst_netlink_attr_list *config) { return modify_qdisc(file, lineno, strict, "traffic filter", RTM_NEWTFILTER, NLM_F_CREATE | NLM_F_EXCL, ifname, AF_UNSPEC, parent, handle, TC_H_MAKE(priority << 16, htons(protocol)), f_kind, config); } int tst_netdev_remove_traffic_filter(const char *file, const int lineno, int strict, const char *ifname, unsigned int parent, unsigned int handle, unsigned int protocol, unsigned int priority, const char *f_kind) { return modify_qdisc(file, lineno, strict, "traffic filter", RTM_DELTFILTER, 0, ifname, AF_UNSPEC, parent, handle, TC_H_MAKE(priority << 16, htons(protocol)), f_kind, NULL); }