/* SPDX-License-Identifier: LGPL-2.1-only */ /* * lib/netfilter/ct.c Conntrack * * 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 */ /** * @ingroup nfnl * @defgroup ct Conntrack * @brief * @{ */ #include #include #include #include #include #include #include #include static struct nl_cache_ops nfnl_ct_ops; static struct nla_policy ct_policy[CTA_MAX+1] = { [CTA_TUPLE_ORIG] = { .type = NLA_NESTED }, [CTA_TUPLE_REPLY] = { .type = NLA_NESTED }, [CTA_STATUS] = { .type = NLA_U32 }, [CTA_PROTOINFO] = { .type = NLA_NESTED }, //[CTA_HELP] //[CTA_NAT_SRC] [CTA_TIMEOUT] = { .type = NLA_U32 }, [CTA_MARK] = { .type = NLA_U32 }, [CTA_COUNTERS_ORIG] = { .type = NLA_NESTED }, [CTA_COUNTERS_REPLY] = { .type = NLA_NESTED }, [CTA_USE] = { .type = NLA_U32 }, [CTA_ID] = { .type = NLA_U32 }, [CTA_ZONE] = { .type = NLA_U16 }, //[CTA_NAT_DST] }; static struct nla_policy ct_tuple_policy[CTA_TUPLE_MAX+1] = { [CTA_TUPLE_IP] = { .type = NLA_NESTED }, [CTA_TUPLE_PROTO] = { .type = NLA_NESTED }, }; static struct nla_policy ct_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 ct_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 ct_protoinfo_policy[CTA_PROTOINFO_MAX+1] = { [CTA_PROTOINFO_TCP] = { .type = NLA_NESTED }, }; static struct nla_policy ct_protoinfo_tcp_policy[CTA_PROTOINFO_TCP_MAX+1] = { [CTA_PROTOINFO_TCP_STATE] = { .type = NLA_U8 }, [CTA_PROTOINFO_TCP_WSCALE_ORIGINAL] = { .type = NLA_U8 }, [CTA_PROTOINFO_TCP_WSCALE_REPLY] = { .type = NLA_U8 }, [CTA_PROTOINFO_TCP_FLAGS_ORIGINAL] = { .minlen = 2 }, [CTA_PROTOINFO_TCP_FLAGS_REPLY] = { .minlen = 2 }, }; static struct nla_policy ct_counters_policy[CTA_COUNTERS_MAX+1] = { [CTA_COUNTERS_PACKETS] = { .type = NLA_U64 }, [CTA_COUNTERS_BYTES] = { .type = NLA_U64 }, [CTA_COUNTERS32_PACKETS]= { .type = NLA_U32 }, [CTA_COUNTERS32_BYTES] = { .type = NLA_U32 }, }; static struct nla_policy ct_timestamp_policy[CTA_TIMESTAMP_MAX + 1] = { [CTA_TIMESTAMP_START] = { .type = NLA_U64 }, [CTA_TIMESTAMP_STOP] = { .type = NLA_U64 }, }; static int ct_parse_ip(struct nfnl_ct *ct, int repl, 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, ct_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_ct_set_src(ct, repl, 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_ct_set_dst(ct, repl, 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_ct_set_src(ct, repl, 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_ct_set_dst(ct, repl, addr); nl_addr_put(addr); if (err < 0) goto errout; } return 0; errout_enomem: err = -NLE_NOMEM; errout: return err; } static int ct_parse_proto(struct nfnl_ct *ct, int repl, struct nlattr *attr) { struct nlattr *tb[CTA_PROTO_MAX+1]; int err; err = nla_parse_nested(tb, CTA_PROTO_MAX, attr, ct_proto_policy); if (err < 0) return err; if (!repl && tb[CTA_PROTO_NUM]) nfnl_ct_set_proto(ct, nla_get_u8(tb[CTA_PROTO_NUM])); if (tb[CTA_PROTO_SRC_PORT]) nfnl_ct_set_src_port(ct, repl, ntohs(nla_get_u16(tb[CTA_PROTO_SRC_PORT]))); if (tb[CTA_PROTO_DST_PORT]) nfnl_ct_set_dst_port(ct, repl, ntohs(nla_get_u16(tb[CTA_PROTO_DST_PORT]))); if (ct->ct_family == AF_INET) { if (tb[CTA_PROTO_ICMP_ID]) nfnl_ct_set_icmp_id(ct, repl, ntohs(nla_get_u16(tb[CTA_PROTO_ICMP_ID]))); if (tb[CTA_PROTO_ICMP_TYPE]) nfnl_ct_set_icmp_type(ct, repl, nla_get_u8(tb[CTA_PROTO_ICMP_TYPE])); if (tb[CTA_PROTO_ICMP_CODE]) nfnl_ct_set_icmp_code(ct, repl, nla_get_u8(tb[CTA_PROTO_ICMP_CODE])); } else if (ct->ct_family == AF_INET6) { if (tb[CTA_PROTO_ICMPV6_ID]) nfnl_ct_set_icmp_id(ct, repl, ntohs(nla_get_u16(tb[CTA_PROTO_ICMPV6_ID]))); if (tb[CTA_PROTO_ICMPV6_TYPE]) nfnl_ct_set_icmp_type(ct, repl, nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE])); if (tb[CTA_PROTO_ICMPV6_CODE]) nfnl_ct_set_icmp_code(ct, repl, nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE])); } return 0; } static int ct_parse_tuple(struct nfnl_ct *ct, int repl, struct nlattr *attr) { struct nlattr *tb[CTA_TUPLE_MAX+1]; int err; err = nla_parse_nested(tb, CTA_TUPLE_MAX, attr, ct_tuple_policy); if (err < 0) return err; if (tb[CTA_TUPLE_IP]) { err = ct_parse_ip(ct, repl, tb[CTA_TUPLE_IP]); if (err < 0) return err; } if (tb[CTA_TUPLE_PROTO]) { err = ct_parse_proto(ct, repl, tb[CTA_TUPLE_PROTO]); if (err < 0) return err; } return 0; } static int ct_parse_protoinfo_tcp(struct nfnl_ct *ct, struct nlattr *attr) { struct nlattr *tb[CTA_PROTOINFO_TCP_MAX+1]; int err; err = nla_parse_nested(tb, CTA_PROTOINFO_TCP_MAX, attr, ct_protoinfo_tcp_policy); if (err < 0) return err; if (tb[CTA_PROTOINFO_TCP_STATE]) nfnl_ct_set_tcp_state(ct, nla_get_u8(tb[CTA_PROTOINFO_TCP_STATE])); return 0; } static int ct_parse_protoinfo(struct nfnl_ct *ct, struct nlattr *attr) { struct nlattr *tb[CTA_PROTOINFO_MAX+1]; int err; err = nla_parse_nested(tb, CTA_PROTOINFO_MAX, attr, ct_protoinfo_policy); if (err < 0) return err; if (tb[CTA_PROTOINFO_TCP]) { err = ct_parse_protoinfo_tcp(ct, tb[CTA_PROTOINFO_TCP]); if (err < 0) return err; } return 0; } static int ct_parse_counters(struct nfnl_ct *ct, int repl, struct nlattr *attr) { struct nlattr *tb[CTA_COUNTERS_MAX+1]; int err; err = nla_parse_nested(tb, CTA_COUNTERS_MAX, attr, ct_counters_policy); if (err < 0) return err; if (tb[CTA_COUNTERS_PACKETS]) nfnl_ct_set_packets(ct, repl, ntohll(nla_get_u64(tb[CTA_COUNTERS_PACKETS]))); if (tb[CTA_COUNTERS32_PACKETS]) nfnl_ct_set_packets(ct, repl, ntohl(nla_get_u32(tb[CTA_COUNTERS32_PACKETS]))); if (tb[CTA_COUNTERS_BYTES]) nfnl_ct_set_bytes(ct, repl, ntohll(nla_get_u64(tb[CTA_COUNTERS_BYTES]))); if (tb[CTA_COUNTERS32_BYTES]) nfnl_ct_set_bytes(ct, repl, ntohl(nla_get_u32(tb[CTA_COUNTERS32_BYTES]))); return 0; } int nfnlmsg_ct_group(struct nlmsghdr *nlh) { switch (nfnlmsg_subtype(nlh)) { case IPCTNL_MSG_CT_NEW: if (nlh->nlmsg_flags & (NLM_F_CREATE|NLM_F_EXCL)) return NFNLGRP_CONNTRACK_NEW; else return NFNLGRP_CONNTRACK_UPDATE; case IPCTNL_MSG_CT_DELETE: return NFNLGRP_CONNTRACK_DESTROY; default: return NFNLGRP_NONE; } } static int ct_parse_timestamp(struct nfnl_ct *ct, struct nlattr *attr) { struct nlattr *tb[CTA_TIMESTAMP_MAX + 1]; int err; err = nla_parse_nested(tb, CTA_TIMESTAMP_MAX, attr, ct_timestamp_policy); if (err < 0) return err; if (tb[CTA_TIMESTAMP_START] && tb[CTA_TIMESTAMP_STOP]) nfnl_ct_set_timestamp(ct, ntohll(nla_get_u64(tb[CTA_TIMESTAMP_START])), ntohll(nla_get_u64(tb[CTA_TIMESTAMP_STOP]))); return 0; } int nfnlmsg_ct_parse(struct nlmsghdr *nlh, struct nfnl_ct **result) { struct nfnl_ct *ct; struct nlattr *tb[CTA_MAX+1]; int err; ct = nfnl_ct_alloc(); if (!ct) return -NLE_NOMEM; ct->ce_msgtype = nlh->nlmsg_type; err = nlmsg_parse(nlh, sizeof(struct nfgenmsg), tb, CTA_MAX, ct_policy); if (err < 0) goto errout; nfnl_ct_set_family(ct, nfnlmsg_family(nlh)); if (tb[CTA_TUPLE_ORIG]) { err = ct_parse_tuple(ct, 0, tb[CTA_TUPLE_ORIG]); if (err < 0) goto errout; } if (tb[CTA_TUPLE_REPLY]) { err = ct_parse_tuple(ct, 1, tb[CTA_TUPLE_REPLY]); if (err < 0) goto errout; } if (tb[CTA_PROTOINFO]) { err = ct_parse_protoinfo(ct, tb[CTA_PROTOINFO]); if (err < 0) goto errout; } if (tb[CTA_STATUS]) nfnl_ct_set_status(ct, ntohl(nla_get_u32(tb[CTA_STATUS]))); if (tb[CTA_TIMEOUT]) nfnl_ct_set_timeout(ct, ntohl(nla_get_u32(tb[CTA_TIMEOUT]))); if (tb[CTA_MARK]) nfnl_ct_set_mark(ct, ntohl(nla_get_u32(tb[CTA_MARK]))); if (tb[CTA_USE]) nfnl_ct_set_use(ct, ntohl(nla_get_u32(tb[CTA_USE]))); if (tb[CTA_ID]) nfnl_ct_set_id(ct, ntohl(nla_get_u32(tb[CTA_ID]))); if (tb[CTA_ZONE]) nfnl_ct_set_zone(ct, ntohs(nla_get_u16(tb[CTA_ZONE]))); if (tb[CTA_COUNTERS_ORIG]) { err = ct_parse_counters(ct, 0, tb[CTA_COUNTERS_ORIG]); if (err < 0) goto errout; } if (tb[CTA_COUNTERS_REPLY]) { err = ct_parse_counters(ct, 1, tb[CTA_COUNTERS_REPLY]); if (err < 0) goto errout; } if (tb[CTA_TIMESTAMP]) { err = ct_parse_timestamp(ct, tb[CTA_TIMESTAMP]); if (err < 0) goto errout; } *result = ct; return 0; errout: nfnl_ct_put(ct); return err; } static int ct_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, struct nlmsghdr *nlh, struct nl_parser_param *pp) { struct nfnl_ct *ct; int err; if ((err = nfnlmsg_ct_parse(nlh, &ct)) < 0) return err; err = pp->pp_cb((struct nl_object *) ct, pp); nfnl_ct_put(ct); return err; } /** * Send nfnl ct 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_ct_dump_request(struct nl_sock *sk) { return nfnl_send_simple(sk, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET, NLM_F_DUMP, AF_UNSPEC, 0); } static int ct_request_update(struct nl_cache *cache, struct nl_sock *sk) { return nfnl_ct_dump_request(sk); } static int nfnl_ct_build_tuple(struct nl_msg *msg, const struct nfnl_ct *ct, int repl) { struct nlattr *tuple, *ip, *proto; struct nl_addr *addr; int family; family = nfnl_ct_get_family(ct); tuple = nla_nest_start(msg, repl ? CTA_TUPLE_REPLY : CTA_TUPLE_ORIG); if (!tuple) goto nla_put_failure; ip = nla_nest_start(msg, CTA_TUPLE_IP); if (!ip) goto nla_put_failure; addr = nfnl_ct_get_src(ct, repl); if (addr) NLA_PUT_ADDR(msg, family == AF_INET ? CTA_IP_V4_SRC : CTA_IP_V6_SRC, addr); addr = nfnl_ct_get_dst(ct, repl); 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_ct_test_proto(ct)) NLA_PUT_U8(msg, CTA_PROTO_NUM, nfnl_ct_get_proto(ct)); if (nfnl_ct_test_src_port(ct, repl)) NLA_PUT_U16(msg, CTA_PROTO_SRC_PORT, htons(nfnl_ct_get_src_port(ct, repl))); if (nfnl_ct_test_dst_port(ct, repl)) NLA_PUT_U16(msg, CTA_PROTO_DST_PORT, htons(nfnl_ct_get_dst_port(ct, repl))); if (family == AF_INET) { if (nfnl_ct_test_icmp_id(ct, repl)) NLA_PUT_U16(msg, CTA_PROTO_ICMP_ID, htons(nfnl_ct_get_icmp_id(ct, repl))); if (nfnl_ct_test_icmp_type(ct, repl)) NLA_PUT_U8(msg, CTA_PROTO_ICMP_TYPE, nfnl_ct_get_icmp_type(ct, repl)); if (nfnl_ct_test_icmp_code(ct, repl)) NLA_PUT_U8(msg, CTA_PROTO_ICMP_CODE, nfnl_ct_get_icmp_code(ct, repl)); } else if (family == AF_INET6) { if (nfnl_ct_test_icmp_id(ct, repl)) NLA_PUT_U16(msg, CTA_PROTO_ICMPV6_ID, htons(nfnl_ct_get_icmp_id(ct, repl))); if (nfnl_ct_test_icmp_type(ct, repl)) NLA_PUT_U8(msg, CTA_PROTO_ICMPV6_TYPE, nfnl_ct_get_icmp_type(ct, repl)); if (nfnl_ct_test_icmp_code(ct, repl)) NLA_PUT_U8(msg, CTA_PROTO_ICMPV6_CODE, nfnl_ct_get_icmp_code(ct, repl)); } nla_nest_end(msg, proto); nla_nest_end(msg, tuple); return 0; nla_put_failure: return -NLE_MSGSIZE; } static int nfnl_ct_build_message(const struct nfnl_ct *ct, int cmd, int flags, struct nl_msg **result) { struct nl_msg *msg; int err; msg = nfnlmsg_alloc_simple(NFNL_SUBSYS_CTNETLINK, cmd, flags, nfnl_ct_get_family(ct), 0); if (msg == NULL) return -NLE_NOMEM; if ((err = nfnl_ct_build_tuple(msg, ct, 0)) < 0) goto err_out; /* REPLY tuple is optional, dont add unless at least src/dst specified */ if ( nfnl_ct_get_src(ct, 1) && nfnl_ct_get_dst(ct, 1) ) if ((err = nfnl_ct_build_tuple(msg, ct, 1)) < 0) goto err_out; if (nfnl_ct_test_status(ct)) NLA_PUT_U32(msg, CTA_STATUS, htonl(nfnl_ct_get_status(ct))); if (nfnl_ct_test_timeout(ct)) NLA_PUT_U32(msg, CTA_TIMEOUT, htonl(nfnl_ct_get_timeout(ct))); if (nfnl_ct_test_mark(ct)) NLA_PUT_U32(msg, CTA_MARK, htonl(nfnl_ct_get_mark(ct))); if (nfnl_ct_test_id(ct)) NLA_PUT_U32(msg, CTA_ID, htonl(nfnl_ct_get_id(ct))); if (nfnl_ct_test_zone(ct)) NLA_PUT_U16(msg, CTA_ZONE, htons(nfnl_ct_get_zone(ct))); *result = msg; return 0; nla_put_failure: err_out: nlmsg_free(msg); return err; } int nfnl_ct_build_add_request(const struct nfnl_ct *ct, int flags, struct nl_msg **result) { return nfnl_ct_build_message(ct, IPCTNL_MSG_CT_NEW, flags, result); } int nfnl_ct_add(struct nl_sock *sk, const struct nfnl_ct *ct, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_ct_build_add_request(ct, 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_ct_build_delete_request(const struct nfnl_ct *ct, int flags, struct nl_msg **result) { return nfnl_ct_build_message(ct, IPCTNL_MSG_CT_DELETE, flags, result); } int nfnl_ct_del(struct nl_sock *sk, const struct nfnl_ct *ct, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_ct_build_delete_request(ct, 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_ct_build_query_request(const struct nfnl_ct *ct, int flags, struct nl_msg **result) { return nfnl_ct_build_message(ct, IPCTNL_MSG_CT_GET, flags, result); } int nfnl_ct_query(struct nl_sock *sk, const struct nfnl_ct *ct, int flags) { struct nl_msg *msg; int err; if ((err = nfnl_ct_build_query_request(ct, 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 conntrack cache holding all conntrack 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 conntracks currently in the kernel. * * @return 0 on success or a negative error code. */ int nfnl_ct_alloc_cache(struct nl_sock *sk, struct nl_cache **result) { return nl_cache_alloc_and_fill(&nfnl_ct_ops, sk, result); } /** @} */ /** * @name Conntrack Addition * @{ */ /** @} */ static struct nl_af_group ct_groups[] = { { AF_UNSPEC, NFNLGRP_CONNTRACK_NEW }, { AF_UNSPEC, NFNLGRP_CONNTRACK_UPDATE }, { AF_UNSPEC, NFNLGRP_CONNTRACK_DESTROY }, { END_OF_GROUP_LIST }, }; #define NFNLMSG_CT_TYPE(type) NFNLMSG_TYPE(NFNL_SUBSYS_CTNETLINK, (type)) static struct nl_cache_ops nfnl_ct_ops = { .co_name = "netfilter/ct", .co_hdrsize = NFNL_HDRLEN, .co_msgtypes = { { NFNLMSG_CT_TYPE(IPCTNL_MSG_CT_NEW), NL_ACT_NEW, "new" }, { NFNLMSG_CT_TYPE(IPCTNL_MSG_CT_GET), NL_ACT_GET, "get" }, { NFNLMSG_CT_TYPE(IPCTNL_MSG_CT_DELETE), NL_ACT_DEL, "del" }, END_OF_MSGTYPES_LIST, }, .co_protocol = NETLINK_NETFILTER, .co_groups = ct_groups, .co_request_update = ct_request_update, .co_msg_parser = ct_msg_parser, .co_obj_ops = &ct_obj_ops, }; static void __init ct_init(void) { nl_cache_mngt_register(&nfnl_ct_ops); } static void __exit ct_exit(void) { nl_cache_mngt_unregister(&nfnl_ct_ops); } /** @} */