533 lines
13 KiB
C
533 lines
13 KiB
C
/*
|
|
* lib/netfilter/ct.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 <tgraf@suug.ch>
|
|
* Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
|
|
* Copyright (c) 2007 Secure Computing Corporation
|
|
* Copyright (c= 2008 Patrick McHardy <kaber@trash.net>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup nfnl
|
|
* @defgroup exp Expectation
|
|
* @brief
|
|
* @{
|
|
*/
|
|
|
|
#include <byteswap.h>
|
|
#include <sys/types.h>
|
|
#include <linux/netfilter/nfnetlink_conntrack.h>
|
|
|
|
#include <netlink-local.h>
|
|
#include <netlink/attr.h>
|
|
#include <netlink/netfilter/nfnl.h>
|
|
#include <netlink/netfilter/exp.h>
|
|
|
|
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 }, // In latest kernel header
|
|
//[CTA_EXPECT_FLAGS] = { .type = NLA_U32 }, // In latest kernel header
|
|
//[CTA_EXPECT_CLASS] = { .type = NLA_U32 }, // In libnetfilter_conntrack include/linux/linux_nfnetlink_conntrack.h
|
|
//[CTA_EXPECT_NAT] = { .type = NLA_NESTED }, // In libnetfilter_conntrack include/linux/linux_nfnetlink_conntrack.h
|
|
//[CTA_EXPECT_FN] = { .type = NLA_STRING }, // In libnetfilter_conntrack include/linux/linux_nfnetlink_conntrack.h
|
|
};
|
|
|
|
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 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;
|
|
}
|
|
|
|
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_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]));
|
|
|
|
*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)
|
|
goto errout;
|
|
|
|
err = pp->pp_cb((struct nl_object *) exp, pp);
|
|
errout:
|
|
nfnl_exp_put(exp);
|
|
return err;
|
|
}
|
|
|
|
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 NFNL_EXP_TUPLE_MASTER:
|
|
attr = CTA_EXPECT_MASTER;
|
|
break;
|
|
case NFNL_EXP_TUPLE_MASK:
|
|
attr = CTA_EXPECT_MASK;
|
|
break;
|
|
//case NFNL_EXP_TUPLE_NAT:
|
|
// attr = EXP_ATTR_NAT_L4PROTO_NUM;
|
|
// break;
|
|
case NFNL_EXP_TUPLE_EXPECT:
|
|
default :
|
|
attr = CTA_EXPECT_TUPLE;
|
|
break;
|
|
}
|
|
|
|
return attr;
|
|
}
|
|
|
|
static int nfnl_exp_build_tuple(struct nl_msg *msg, const struct nfnl_exp *exp,
|
|
int type)
|
|
{
|
|
struct nlattr *tuple, *ip, *proto;
|
|
struct nl_addr *addr;
|
|
int family;
|
|
|
|
int tupattr = exp_get_tuple_attr(type);
|
|
|
|
family = nfnl_exp_get_family(exp);
|
|
|
|
tuple = nla_nest_start(msg, tupattr);
|
|
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_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;
|
|
|
|
// FIXME timeout and helper name
|
|
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));
|
|
|
|
*result = msg;
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
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);
|
|
}
|
|
|
|
/** @} */
|