/* SPDX-License-Identifier: LGPL-2.1-only */ /* * lib/netfilter/exp.c Conntrack Expectation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * Copyright (c) 2003-2008 Thomas Graf * Copyright (c) 2007 Philip Craig * Copyright (c) 2007 Secure Computing Corporation * Copyright (c= 2008 Patrick McHardy * Copyright (c) 2012 Rich Fought */ /** * @ingroup nfnl * @defgroup exp Expectation * @brief * @{ */ #include #include #include #include #include #include #include static struct nl_cache_ops nfnl_exp_ops; static struct nla_policy exp_policy[CTA_EXPECT_MAX+1] = { [CTA_EXPECT_MASTER] = { .type = NLA_NESTED }, [CTA_EXPECT_TUPLE] = { .type = NLA_NESTED }, [CTA_EXPECT_MASK] = { .type = NLA_NESTED }, [CTA_EXPECT_TIMEOUT] = { .type = NLA_U32 }, [CTA_EXPECT_ID] = { .type = NLA_U32 }, [CTA_EXPECT_HELP_NAME] = { .type = NLA_STRING }, [CTA_EXPECT_ZONE] = { .type = NLA_U16 }, [CTA_EXPECT_FLAGS] = { .type = NLA_U32 }, // Added in kernel 2.6.37 [CTA_EXPECT_CLASS] = { .type = NLA_U32 }, // Added in kernel 3.5 [CTA_EXPECT_NAT] = { .type = NLA_NESTED }, // Added in kernel 3.5 [CTA_EXPECT_FN] = { .type = NLA_STRING }, // Added in kernel 3.5 }; static struct nla_policy exp_tuple_policy[CTA_TUPLE_MAX+1] = { [CTA_TUPLE_IP] = { .type = NLA_NESTED }, [CTA_TUPLE_PROTO] = { .type = NLA_NESTED }, }; static struct nla_policy exp_ip_policy[CTA_IP_MAX+1] = { [CTA_IP_V4_SRC] = { .type = NLA_U32 }, [CTA_IP_V4_DST] = { .type = NLA_U32 }, [CTA_IP_V6_SRC] = { .minlen = 16 }, [CTA_IP_V6_DST] = { .minlen = 16 }, }; static struct nla_policy exp_proto_policy[CTA_PROTO_MAX+1] = { [CTA_PROTO_NUM] = { .type = NLA_U8 }, [CTA_PROTO_SRC_PORT] = { .type = NLA_U16 }, [CTA_PROTO_DST_PORT] = { .type = NLA_U16 }, [CTA_PROTO_ICMP_ID] = { .type = NLA_U16 }, [CTA_PROTO_ICMP_TYPE] = { .type = NLA_U8 }, [CTA_PROTO_ICMP_CODE] = { .type = NLA_U8 }, [CTA_PROTO_ICMPV6_ID] = { .type = NLA_U16 }, [CTA_PROTO_ICMPV6_TYPE] = { .type = NLA_U8 }, [CTA_PROTO_ICMPV6_CODE] = { .type = NLA_U8 }, }; static struct nla_policy exp_nat_policy[CTA_EXPECT_NAT_MAX+1] = { [CTA_EXPECT_NAT_DIR] = { .type = NLA_U32 }, [CTA_EXPECT_NAT_TUPLE] = { .type = NLA_NESTED }, }; static int exp_parse_ip(struct nfnl_exp *exp, int tuple, struct nlattr *attr) { struct nlattr *tb[CTA_IP_MAX+1]; struct nl_addr *addr; int err; err = nla_parse_nested(tb, CTA_IP_MAX, attr, exp_ip_policy); if (err < 0) goto errout; if (tb[CTA_IP_V4_SRC]) { addr = nl_addr_alloc_attr(tb[CTA_IP_V4_SRC], AF_INET); if (addr == NULL) goto errout_enomem; err = nfnl_exp_set_src(exp, tuple, addr); nl_addr_put(addr); if (err < 0) goto errout; } if (tb[CTA_IP_V4_DST]) { addr = nl_addr_alloc_attr(tb[CTA_IP_V4_DST], AF_INET); if (addr == NULL) goto errout_enomem; err = nfnl_exp_set_dst(exp, tuple, addr); nl_addr_put(addr); if (err < 0) goto errout; } if (tb[CTA_IP_V6_SRC]) { addr = nl_addr_alloc_attr(tb[CTA_IP_V6_SRC], AF_INET6); if (addr == NULL) goto errout_enomem; err = nfnl_exp_set_src(exp, tuple, addr); nl_addr_put(addr); if (err < 0) goto errout; } if (tb[CTA_IP_V6_DST]) { addr = nl_addr_alloc_attr(tb[CTA_IP_V6_DST], AF_INET6); if (addr == NULL) goto errout_enomem; err = nfnl_exp_set_dst(exp, tuple, addr); nl_addr_put(addr); if (err < 0) goto errout; } return 0; errout_enomem: err = -NLE_NOMEM; errout: return err; } static int exp_parse_proto(struct nfnl_exp *exp, int tuple, struct nlattr *attr) { struct nlattr *tb[CTA_PROTO_MAX+1]; int err; uint16_t srcport = 0, dstport = 0, icmpid = 0; uint8_t icmptype = 0, icmpcode = 0; err = nla_parse_nested(tb, CTA_PROTO_MAX, attr, exp_proto_policy); if (err < 0) return err; if (tb[CTA_PROTO_NUM]) nfnl_exp_set_l4protonum(exp, tuple, nla_get_u8(tb[CTA_PROTO_NUM])); if (tb[CTA_PROTO_SRC_PORT]) srcport = ntohs(nla_get_u16(tb[CTA_PROTO_SRC_PORT])); if (tb[CTA_PROTO_DST_PORT]) dstport = ntohs(nla_get_u16(tb[CTA_PROTO_DST_PORT])); if (tb[CTA_PROTO_SRC_PORT] || tb[CTA_PROTO_DST_PORT]) nfnl_exp_set_ports(exp, tuple, srcport, dstport); if (tb[CTA_PROTO_ICMP_ID]) icmpid = ntohs(nla_get_u16(tb[CTA_PROTO_ICMP_ID])); if (tb[CTA_PROTO_ICMP_TYPE]) icmptype = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]); if (tb[CTA_PROTO_ICMP_CODE]) icmpcode = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]); if (tb[CTA_PROTO_ICMP_ID] || tb[CTA_PROTO_ICMP_TYPE] || tb[CTA_PROTO_ICMP_CODE]) nfnl_exp_set_icmp(exp, tuple, icmpid, icmptype, icmpcode); return 0; } static int exp_parse_tuple(struct nfnl_exp *exp, int tuple, struct nlattr *attr) { struct nlattr *tb[CTA_TUPLE_MAX+1]; int err; err = nla_parse_nested(tb, CTA_TUPLE_MAX, attr, exp_tuple_policy); if (err < 0) return err; if (tb[CTA_TUPLE_IP]) { err = exp_parse_ip(exp, tuple, tb[CTA_TUPLE_IP]); if (err < 0) return err; } if (tb[CTA_TUPLE_PROTO]) { err = exp_parse_proto(exp, tuple, tb[CTA_TUPLE_PROTO]); if (err < 0) return err; } return 0; } static int exp_parse_nat(struct nfnl_exp *exp, struct nlattr *attr) { struct nlattr *tb[CTA_EXPECT_NAT_MAX+1]; int err; err = nla_parse_nested(tb, CTA_EXPECT_NAT_MAX, attr, exp_nat_policy); if (err < 0) return err; if (tb[CTA_EXPECT_NAT_DIR]) nfnl_exp_set_nat_dir(exp, nla_get_u32(tb[CTA_EXPECT_NAT_DIR])); if (tb[CTA_EXPECT_NAT_TUPLE]) { err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_NAT, tb[CTA_EXPECT_NAT_TUPLE]); if (err < 0) return err; } return 0; } int nfnlmsg_exp_group(struct nlmsghdr *nlh) { switch (nfnlmsg_subtype(nlh)) { case IPCTNL_MSG_EXP_NEW: if (nlh->nlmsg_flags & (NLM_F_CREATE|NLM_F_EXCL)) return NFNLGRP_CONNTRACK_EXP_NEW; else return NFNLGRP_CONNTRACK_EXP_UPDATE; case IPCTNL_MSG_EXP_DELETE: return NFNLGRP_CONNTRACK_EXP_DESTROY; default: return NFNLGRP_NONE; } } int nfnlmsg_exp_parse(struct nlmsghdr *nlh, struct nfnl_exp **result) { struct nfnl_exp *exp; struct nlattr *tb[CTA_MAX+1]; int err; exp = nfnl_exp_alloc(); if (!exp) return -NLE_NOMEM; exp->ce_msgtype = nlh->nlmsg_type; err = nlmsg_parse(nlh, sizeof(struct nfgenmsg), tb, CTA_EXPECT_MAX, exp_policy); if (err < 0) goto errout; nfnl_exp_set_family(exp, nfnlmsg_family(nlh)); if (tb[CTA_EXPECT_TUPLE]) { err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_EXPECT, tb[CTA_EXPECT_TUPLE]); if (err < 0) goto errout; } if (tb[CTA_EXPECT_MASTER]) { err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_MASTER, tb[CTA_EXPECT_MASTER]); if (err < 0) goto errout; } if (tb[CTA_EXPECT_MASK]) { err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_MASK, tb[CTA_EXPECT_MASK]); if (err < 0) goto errout; } if (tb[CTA_EXPECT_NAT]) { err = exp_parse_nat(exp, tb[CTA_EXPECT_MASK]); if (err < 0) goto errout; } if (tb[CTA_EXPECT_CLASS]) nfnl_exp_set_class(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_CLASS]))); if (tb[CTA_EXPECT_FN]) nfnl_exp_set_fn(exp, nla_data(tb[CTA_EXPECT_FN])); if (tb[CTA_EXPECT_TIMEOUT]) nfnl_exp_set_timeout(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_TIMEOUT]))); if (tb[CTA_EXPECT_ID]) nfnl_exp_set_id(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_ID]))); if (tb[CTA_EXPECT_HELP_NAME]) nfnl_exp_set_helper_name(exp, nla_data(tb[CTA_EXPECT_HELP_NAME])); if (tb[CTA_EXPECT_ZONE]) nfnl_exp_set_zone(exp, ntohs(nla_get_u16(tb[CTA_EXPECT_ZONE]))); if (tb[CTA_EXPECT_FLAGS]) nfnl_exp_set_flags(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_FLAGS]))); *result = exp; return 0; errout: nfnl_exp_put(exp); return err; } static int exp_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, struct nlmsghdr *nlh, struct nl_parser_param *pp) { struct nfnl_exp *exp; int err; if ((err = nfnlmsg_exp_parse(nlh, &exp)) < 0) return err; err = pp->pp_cb((struct nl_object *) exp, pp); nfnl_exp_put(exp); return err; } /** * Send nfnl exp dump request * @arg sk Netlink socket. * * @return 0 on success or a negative error code. Due to a bug, this function * returns the number of bytes sent. Treat any non-negative number as success. */ int nfnl_exp_dump_request(struct nl_sock *sk) { return nfnl_send_simple(sk, NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET, NLM_F_DUMP, AF_UNSPEC, 0); } static int exp_request_update(struct nl_cache *cache, struct nl_sock *sk) { return nfnl_exp_dump_request(sk); } static int exp_get_tuple_attr(int tuple) { int attr = 0; switch (tuple) { case CTA_EXPECT_MASTER: attr = NFNL_EXP_TUPLE_MASTER; break; case CTA_EXPECT_MASK: attr = NFNL_EXP_TUPLE_MASK; break; case CTA_EXPECT_NAT: attr = NFNL_EXP_TUPLE_NAT; break; case CTA_EXPECT_TUPLE: default : attr = NFNL_EXP_TUPLE_EXPECT; break; } return attr; } static int nfnl_exp_build_tuple(struct nl_msg *msg, const struct nfnl_exp *exp, int cta) { struct nlattr *tuple, *ip, *proto; struct nl_addr *addr; int family; int type; family = nfnl_exp_get_family(exp); type = exp_get_tuple_attr(cta); if (cta == CTA_EXPECT_NAT) tuple = nla_nest_start(msg, CTA_EXPECT_NAT_TUPLE); else tuple = nla_nest_start(msg, cta); if (!tuple) goto nla_put_failure; ip = nla_nest_start(msg, CTA_TUPLE_IP); if (!ip) goto nla_put_failure; addr = nfnl_exp_get_src(exp, type); if (addr) NLA_PUT_ADDR(msg, family == AF_INET ? CTA_IP_V4_SRC : CTA_IP_V6_SRC, addr); addr = nfnl_exp_get_dst(exp, type); if (addr) NLA_PUT_ADDR(msg, family == AF_INET ? CTA_IP_V4_DST : CTA_IP_V6_DST, addr); nla_nest_end(msg, ip); proto = nla_nest_start(msg, CTA_TUPLE_PROTO); if (!proto) goto nla_put_failure; if (nfnl_exp_test_l4protonum(exp, type)) NLA_PUT_U8(msg, CTA_PROTO_NUM, nfnl_exp_get_l4protonum(exp, type)); if (nfnl_exp_test_ports(exp, type)) { NLA_PUT_U16(msg, CTA_PROTO_SRC_PORT, htons(nfnl_exp_get_src_port(exp, type))); NLA_PUT_U16(msg, CTA_PROTO_DST_PORT, htons(nfnl_exp_get_dst_port(exp, type))); } if (nfnl_exp_test_icmp(exp, type)) { NLA_PUT_U16(msg, CTA_PROTO_ICMP_ID, htons(nfnl_exp_get_icmp_id(exp, type))); NLA_PUT_U8(msg, CTA_PROTO_ICMP_TYPE, nfnl_exp_get_icmp_type(exp, type)); NLA_PUT_U8(msg, CTA_PROTO_ICMP_CODE, nfnl_exp_get_icmp_code(exp, type)); } nla_nest_end(msg, proto); nla_nest_end(msg, tuple); return 0; nla_put_failure: return -NLE_MSGSIZE; } static int nfnl_exp_build_nat(struct nl_msg *msg, const struct nfnl_exp *exp) { struct nlattr *nat; int err; nat = nla_nest_start(msg, CTA_EXPECT_NAT); if (nfnl_exp_test_nat_dir(exp)) { NLA_PUT_U32(msg, CTA_EXPECT_NAT_DIR, nfnl_exp_get_nat_dir(exp)); } if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_NAT)) < 0) goto nla_put_failure; nla_nest_end(msg, nat); return 0; nla_put_failure: return -NLE_MSGSIZE; } static int nfnl_exp_build_message(const struct nfnl_exp *exp, int cmd, int flags, struct nl_msg **result) { struct nl_msg *msg; int err; msg = nfnlmsg_alloc_simple(NFNL_SUBSYS_CTNETLINK_EXP, cmd, flags, nfnl_exp_get_family(exp), 0); if (msg == NULL) return -NLE_NOMEM; if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_TUPLE)) < 0) goto err_out; if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_MASTER)) < 0) goto err_out; if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_MASK)) < 0) goto err_out; if (nfnl_exp_test_src(exp, NFNL_EXP_TUPLE_NAT)) { if ((err = nfnl_exp_build_nat(msg, exp)) < 0) goto err_out; } if (nfnl_exp_test_class(exp)) NLA_PUT_U32(msg, CTA_EXPECT_CLASS, htonl(nfnl_exp_get_class(exp))); if (nfnl_exp_test_fn(exp)) NLA_PUT_STRING(msg, CTA_EXPECT_FN, nfnl_exp_get_fn(exp)); if (nfnl_exp_test_id(exp)) NLA_PUT_U32(msg, CTA_EXPECT_ID, htonl(nfnl_exp_get_id(exp))); if (nfnl_exp_test_timeout(exp)) NLA_PUT_U32(msg, CTA_EXPECT_TIMEOUT, htonl(nfnl_exp_get_timeout(exp))); if (nfnl_exp_test_helper_name(exp)) NLA_PUT_STRING(msg, CTA_EXPECT_HELP_NAME, nfnl_exp_get_helper_name(exp)); if (nfnl_exp_test_zone(exp)) NLA_PUT_U16(msg, CTA_EXPECT_ZONE, htons(nfnl_exp_get_zone(exp))); if (nfnl_exp_test_flags(exp)) NLA_PUT_U32(msg, CTA_EXPECT_FLAGS, htonl(nfnl_exp_get_flags(exp))); *result = msg; return 0; nla_put_failure: err = -NLE_NOMEM; err_out: nlmsg_free(msg); return err; } int nfnl_exp_build_add_request(const struct nfnl_exp *exp, int flags, struct nl_msg **result) { return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_NEW, flags, result); } int nfnl_exp_add(struct nl_sock *sk, const struct nfnl_exp *exp, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_exp_build_add_request(exp, flags, &msg)) < 0) return err; err = nl_send_auto_complete(sk, msg); nlmsg_free(msg); if (err < 0) return err; return wait_for_ack(sk); } int nfnl_exp_build_delete_request(const struct nfnl_exp *exp, int flags, struct nl_msg **result) { return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_DELETE, flags, result); } int nfnl_exp_del(struct nl_sock *sk, const struct nfnl_exp *exp, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_exp_build_delete_request(exp, flags, &msg)) < 0) return err; err = nl_send_auto_complete(sk, msg); nlmsg_free(msg); if (err < 0) return err; return wait_for_ack(sk); } int nfnl_exp_build_query_request(const struct nfnl_exp *exp, int flags, struct nl_msg **result) { return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_GET, flags, result); } int nfnl_exp_query(struct nl_sock *sk, const struct nfnl_exp *exp, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_exp_build_query_request(exp, flags, &msg)) < 0) return err; err = nl_send_auto_complete(sk, msg); nlmsg_free(msg); if (err < 0) return err; return wait_for_ack(sk); } /** * @name Cache Management * @{ */ /** * Build a expectation cache holding all expectations currently in the kernel * @arg sk Netlink socket. * @arg result Pointer to store resulting cache. * * Allocates a new cache, initializes it properly and updates it to * contain all expectations currently in the kernel. * * @return 0 on success or a negative error code. */ int nfnl_exp_alloc_cache(struct nl_sock *sk, struct nl_cache **result) { return nl_cache_alloc_and_fill(&nfnl_exp_ops, sk, result); } /** @} */ /** * @name Expectation Addition * @{ */ /** @} */ static struct nl_af_group exp_groups[] = { { AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_NEW }, { AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_UPDATE }, { AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_DESTROY }, { END_OF_GROUP_LIST }, }; #define NFNLMSG_EXP_TYPE(type) NFNLMSG_TYPE(NFNL_SUBSYS_CTNETLINK_EXP, (type)) static struct nl_cache_ops nfnl_exp_ops = { .co_name = "netfilter/exp", .co_hdrsize = NFNL_HDRLEN, .co_msgtypes = { { NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_NEW), NL_ACT_NEW, "new" }, { NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_GET), NL_ACT_GET, "get" }, { NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_DELETE), NL_ACT_DEL, "del" }, END_OF_MSGTYPES_LIST, }, .co_protocol = NETLINK_NETFILTER, .co_groups = exp_groups, .co_request_update = exp_request_update, .co_msg_parser = exp_msg_parser, .co_obj_ops = &exp_obj_ops, }; static void __init exp_init(void) { nl_cache_mngt_register(&nfnl_exp_ops); } static void __exit exp_exit(void) { nl_cache_mngt_unregister(&nfnl_exp_ops); } /** @} */