// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include "../kselftest_harness.h" #define TEST_ZONE_ID 123 #define CTA_FILTER_F_CTA_TUPLE_ZONE (1 << 2) static int reply_counter; static int build_cta_tuple_v4(struct nlmsghdr *nlh, int type, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port) { struct nlattr *nest, *nest_ip, *nest_proto; nest = mnl_attr_nest_start(nlh, type); if (!nest) return -1; nest_ip = mnl_attr_nest_start(nlh, CTA_TUPLE_IP); if (!nest_ip) return -1; mnl_attr_put_u32(nlh, CTA_IP_V4_SRC, src_ip); mnl_attr_put_u32(nlh, CTA_IP_V4_DST, dst_ip); mnl_attr_nest_end(nlh, nest_ip); nest_proto = mnl_attr_nest_start(nlh, CTA_TUPLE_PROTO); if (!nest_proto) return -1; mnl_attr_put_u8(nlh, CTA_PROTO_NUM, 6); mnl_attr_put_u16(nlh, CTA_PROTO_SRC_PORT, htons(src_port)); mnl_attr_put_u16(nlh, CTA_PROTO_DST_PORT, htons(dst_port)); mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); } static int build_cta_tuple_v6(struct nlmsghdr *nlh, int type, struct in6_addr src_ip, struct in6_addr dst_ip, uint16_t src_port, uint16_t dst_port) { struct nlattr *nest, *nest_ip, *nest_proto; nest = mnl_attr_nest_start(nlh, type); if (!nest) return -1; nest_ip = mnl_attr_nest_start(nlh, CTA_TUPLE_IP); if (!nest_ip) return -1; mnl_attr_put(nlh, CTA_IP_V6_SRC, sizeof(struct in6_addr), &src_ip); mnl_attr_put(nlh, CTA_IP_V6_DST, sizeof(struct in6_addr), &dst_ip); mnl_attr_nest_end(nlh, nest_ip); nest_proto = mnl_attr_nest_start(nlh, CTA_TUPLE_PROTO); if (!nest_proto) return -1; mnl_attr_put_u8(nlh, CTA_PROTO_NUM, 6); mnl_attr_put_u16(nlh, CTA_PROTO_SRC_PORT, htons(src_port)); mnl_attr_put_u16(nlh, CTA_PROTO_DST_PORT, htons(dst_port)); mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); } static int build_cta_proto(struct nlmsghdr *nlh) { struct nlattr *nest, *nest_proto; nest = mnl_attr_nest_start(nlh, CTA_PROTOINFO); if (!nest) return -1; nest_proto = mnl_attr_nest_start(nlh, CTA_PROTOINFO_TCP); if (!nest_proto) return -1; mnl_attr_put_u8(nlh, CTA_PROTOINFO_TCP_STATE, TCP_CONNTRACK_ESTABLISHED); mnl_attr_put_u16(nlh, CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, 0x0a0a); mnl_attr_put_u16(nlh, CTA_PROTOINFO_TCP_FLAGS_REPLY, 0x0a0a); mnl_attr_nest_end(nlh, nest_proto); mnl_attr_nest_end(nlh, nest); } static int conntrack_data_insert(struct mnl_socket *sock, struct nlmsghdr *nlh, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *rplnlh; unsigned int portid; int err, ret; portid = mnl_socket_get_portid(sock); ret = build_cta_proto(nlh); if (ret < 0) { perror("build_cta_proto"); return -1; } mnl_attr_put_u32(nlh, CTA_TIMEOUT, htonl(20000)); mnl_attr_put_u16(nlh, CTA_ZONE, htons(zone)); if (mnl_socket_sendto(sock, nlh, nlh->nlmsg_len) < 0) { perror("mnl_socket_sendto"); return -1; } ret = mnl_socket_recvfrom(sock, buf, MNL_SOCKET_BUFFER_SIZE); if (ret < 0) { perror("mnl_socket_recvfrom"); return ret; } ret = mnl_cb_run(buf, ret, nlh->nlmsg_seq, portid, NULL, NULL); if (ret < 0) { if (errno == EEXIST) { /* The entries are probably still there from a previous * run. So we are good */ return 0; } perror("mnl_cb_run"); return ret; } return 0; } static int conntrack_data_generate_v4(struct mnl_socket *sock, uint32_t src_ip, uint32_t dst_ip, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct nfgenmsg *nfh; int ret; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK | NLM_F_EXCL; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = AF_INET; nfh->version = NFNETLINK_V0; nfh->res_id = 0; ret = build_cta_tuple_v4(nlh, CTA_TUPLE_ORIG, src_ip, dst_ip, 12345, 443); if (ret < 0) { perror("build_cta_tuple_v4"); return ret; } ret = build_cta_tuple_v4(nlh, CTA_TUPLE_REPLY, dst_ip, src_ip, 443, 12345); if (ret < 0) { perror("build_cta_tuple_v4"); return ret; } return conntrack_data_insert(sock, nlh, zone); } static int conntrack_data_generate_v6(struct mnl_socket *sock, struct in6_addr src_ip, struct in6_addr dst_ip, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct nfgenmsg *nfh; int ret; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK | NLM_F_EXCL; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = AF_INET6; nfh->version = NFNETLINK_V0; nfh->res_id = 0; ret = build_cta_tuple_v6(nlh, CTA_TUPLE_ORIG, src_ip, dst_ip, 12345, 443); if (ret < 0) { perror("build_cta_tuple_v6"); return ret; } ret = build_cta_tuple_v6(nlh, CTA_TUPLE_REPLY, dst_ip, src_ip, 12345, 443); if (ret < 0) { perror("build_cta_tuple_v6"); return ret; } return conntrack_data_insert(sock, nlh, zone); } static int count_entries(const struct nlmsghdr *nlh, void *data) { reply_counter++; } static int conntracK_count_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh, *rplnlh; struct nfgenmsg *nfh; struct nlattr *nest; unsigned int portid; int err, ret; portid = mnl_socket_get_portid(sock); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = AF_UNSPEC; nfh->version = NFNETLINK_V0; nfh->res_id = 0; mnl_attr_put_u16(nlh, CTA_ZONE, htons(zone)); ret = mnl_socket_sendto(sock, nlh, nlh->nlmsg_len); if (ret < 0) { perror("mnl_socket_sendto"); return ret; } reply_counter = 0; ret = mnl_socket_recvfrom(sock, buf, MNL_SOCKET_BUFFER_SIZE); while (ret > 0) { ret = mnl_cb_run(buf, ret, nlh->nlmsg_seq, portid, count_entries, NULL); if (ret <= MNL_CB_STOP) break; ret = mnl_socket_recvfrom(sock, buf, MNL_SOCKET_BUFFER_SIZE); } if (ret < 0) { perror("mnl_socket_recvfrom"); return ret; } return reply_counter; } static int conntrack_flush_zone(struct mnl_socket *sock, uint16_t zone) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh, *rplnlh; struct nfgenmsg *nfh; struct nlattr *nest; unsigned int portid; int err, ret; portid = mnl_socket_get_portid(sock); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_DELETE; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = AF_UNSPEC; nfh->version = NFNETLINK_V0; nfh->res_id = 0; mnl_attr_put_u16(nlh, CTA_ZONE, htons(zone)); ret = mnl_socket_sendto(sock, nlh, nlh->nlmsg_len); if (ret < 0) { perror("mnl_socket_sendto"); return ret; } ret = mnl_socket_recvfrom(sock, buf, MNL_SOCKET_BUFFER_SIZE); if (ret < 0) { perror("mnl_socket_recvfrom"); return ret; } ret = mnl_cb_run(buf, ret, nlh->nlmsg_seq, portid, NULL, NULL); if (ret < 0) { perror("mnl_cb_run"); return ret; } return 0; } FIXTURE(conntrack_dump_flush) { struct mnl_socket *sock; }; FIXTURE_SETUP(conntrack_dump_flush) { struct in6_addr src, dst; int ret; self->sock = mnl_socket_open(NETLINK_NETFILTER); if (!self->sock) { perror("mnl_socket_open"); exit(EXIT_FAILURE); } if (mnl_socket_bind(self->sock, 0, MNL_SOCKET_AUTOPID) < 0) { perror("mnl_socket_bind"); exit(EXIT_FAILURE); } ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); if (ret < 0 && errno == EPERM) SKIP(return, "Needs to be run as root"); else if (ret < 0 && errno == EOPNOTSUPP) SKIP(return, "Kernel does not seem to support conntrack zones"); ret = conntrack_data_generate_v4(self->sock, 0xf0f0f0f0, 0xf1f1f1f1, TEST_ZONE_ID); EXPECT_EQ(ret, 0); ret = conntrack_data_generate_v4(self->sock, 0xf2f2f2f2, 0xf3f3f3f3, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 0); ret = conntrack_data_generate_v4(self->sock, 0xf4f4f4f4, 0xf5f5f5f5, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 0); src = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x01000000 } }}; dst = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x02000000 } }}; ret = conntrack_data_generate_v6(self->sock, src, dst, TEST_ZONE_ID); EXPECT_EQ(ret, 0); src = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x03000000 } }}; dst = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x04000000 } }}; ret = conntrack_data_generate_v6(self->sock, src, dst, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 0); src = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x05000000 } }}; dst = (struct in6_addr) {{ .__u6_addr32 = { 0xb80d0120, 0x00000000, 0x00000000, 0x06000000 } }}; ret = conntrack_data_generate_v6(self->sock, src, dst, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 0); ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); EXPECT_GE(ret, 2); if (ret > 2) SKIP(return, "kernel does not support filtering by zone"); } FIXTURE_TEARDOWN(conntrack_dump_flush) { } TEST_F(conntrack_dump_flush, test_dump_by_zone) { int ret; ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 2); } TEST_F(conntrack_dump_flush, test_flush_by_zone) { int ret; ret = conntrack_flush_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); ret = conntracK_count_zone(self->sock, TEST_ZONE_ID); EXPECT_EQ(ret, 0); ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 1); EXPECT_EQ(ret, 2); ret = conntracK_count_zone(self->sock, TEST_ZONE_ID + 2); EXPECT_EQ(ret, 2); } TEST_HARNESS_MAIN