diff options
Diffstat (limited to 'src/firewall-nftables.c')
-rw-r--r-- | src/firewall-nftables.c | 1133 |
1 files changed, 1133 insertions, 0 deletions
diff --git a/src/firewall-nftables.c b/src/firewall-nftables.c new file mode 100644 index 00000000..1febce44 --- /dev/null +++ b/src/firewall-nftables.c @@ -0,0 +1,1133 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2016 BMW Car IT GmbH. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * This file is based on the libnftnl examples: + * https://git.netfilter.org/libnftnl/tree/examples + * by Pablo Neira Ayuso. and inspiration from systemd nft implemention + * https://github.com/zonque/systemd/blob/rfc-nftnl/src/shared/firewall-util.c + * by Daniel Mack. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> +#include <alloca.h> +#include <netinet/in.h> +#include <netinet/ip.h> + +#include <sys/types.h> +#include <pwd.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <linux/netfilter.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_nat.h> +#include <linux/netfilter/nf_tables.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +#include <glib.h> + +#include "connman.h" + +#define CONNMAN_TABLE "connman" +#define CONNMAN_CHAIN_NAT_PRE "nat-prerouting" +#define CONNMAN_CHAIN_NAT_POST "nat-postrouting" +#define CONNMAN_CHAIN_ROUTE_OUTPUT "route-output" + +static bool debug_enabled = true; + +struct firewall_handle { + uint64_t handle; + const char *chain; +}; + +struct firewall_context { + struct firewall_handle rule; +}; + +struct nftables_info { + struct firewall_handle ct; +}; + +static struct nftables_info *nft_info; + +enum callback_return_type { + CALLBACK_RETURN_NONE = 0, + CALLBACK_RETURN_HANDLE, + CALLBACK_RETURN_BYTE_COUNTER, + _CALLBACK_RETURN_MAX, +}; + +struct callback_data { + enum callback_return_type type; + uint64_t value; + bool success; +}; + +static void debug_netlink_dump_rule(struct nftnl_rule *nlr) +{ + char buf[4096]; + + if (!debug_enabled) + return; + + nftnl_rule_snprintf(buf, sizeof(buf), nlr, 0, 0); + fprintf(stdout, "%s\n", buf); +} + +static void debug_mnl_dump_rule(const void *req, size_t req_size) +{ + if (!debug_enabled) + return; + + mnl_nlmsg_fprintf(stdout, req, req_size, 0); + printf("\n"); +} + +static int rule_expr_cb(struct nftnl_expr *expr, void *data) { + + struct callback_data *cb = data; + const char *name; + + name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); + + if (strcmp(name, "counter")) { + cb->value = nftnl_expr_get_u64(expr, NFTNL_EXPR_CTR_BYTES); + cb->success = true; + } + + return 0; +} + +static int rule_cb(const struct nlmsghdr *nlh, int event, + struct callback_data *cb) +{ + struct nftnl_rule *rule; + + rule = nftnl_rule_alloc(); + if (!rule) + return MNL_CB_OK; + + if (nftnl_rule_nlmsg_parse(nlh, rule) < 0) + goto out; + + switch (cb->type) { + case CALLBACK_RETURN_HANDLE: + cb->value = nftnl_rule_get_u64(rule, NFTNL_RULE_HANDLE); + cb->success = true; + break; + + case CALLBACK_RETURN_BYTE_COUNTER: + nftnl_expr_foreach(rule, rule_expr_cb, cb); + break; + + default: + DBG("unhandled callback type %d\n", cb->type); + break; + } + +out: + nftnl_rule_free(rule); + return MNL_CB_STOP; +} + +static int chain_cb(const struct nlmsghdr *nlh, int event, + struct callback_data *cb) +{ + struct nftnl_chain *chain; + + chain = nftnl_chain_alloc(); + if (!chain) + return MNL_CB_OK; + + if (nftnl_chain_nlmsg_parse(nlh, chain) < 0) + goto out; + + switch (cb->type) { + case CALLBACK_RETURN_HANDLE: + cb->value = nftnl_chain_get_u64(chain, NFTNL_CHAIN_HANDLE); + cb->success = true; + break; + + default: + DBG("unhandled callback type %d\n", cb->type); + break; + } + +out: + nftnl_chain_free(chain); + return MNL_CB_OK; +} + +static const char *event_to_str(enum nf_tables_msg_types type) +{ + const char *table[] = { + "NFT_MSG_NEWTABLE", + "NFT_MSG_GETTABLE", + "NFT_MSG_DELTABLE", + "NFT_MSG_NEWCHAIN", + "NFT_MSG_GETCHAIN", + "NFT_MSG_DELCHAIN", + "NFT_MSG_NEWRULE", + "NFT_MSG_GETRULE", + "NFT_MSG_DELRULE", + "NFT_MSG_NEWSET", + "NFT_MSG_GETSET", + "NFT_MSG_DELSET", + "NFT_MSG_NEWSETELEM", + "NFT_MSG_GETSETELEM", + "NFT_MSG_DELSETELEM", + "NFT_MSG_NEWGEN", + "NFT_MSG_GETGEN", + "NFT_MSG_TRACE" + }; + + if (type < sizeof(table)/sizeof(table[0])) + return table[type]; + + return "unknown"; +} + +static int events_cb(const struct nlmsghdr *nlh, void *data) +{ + int event = NFNL_MSG_TYPE(nlh->nlmsg_type); + struct callback_data *cb = data; + int err = MNL_CB_OK; + + if (!cb || cb->type == CALLBACK_RETURN_NONE) + return err; + + DBG("handle event %s", event_to_str(event)); + + switch(event) { + case NFT_MSG_NEWCHAIN: + err = chain_cb(nlh, event, cb); + break; + + case NFT_MSG_NEWRULE: + err = rule_cb(nlh, event, cb); + break; + default: + DBG("unhandled event type %s", event_to_str(event)); + break; + } + + return err; +} + +static int socket_open_and_bind(struct mnl_socket **n) +{ + + struct mnl_socket *nl = NULL; + int err; + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (!nl) + return -errno; + + err = mnl_socket_bind(nl, 1 << (NFNLGRP_NFTABLES-1), + MNL_SOCKET_AUTOPID); + if (err < 0) { + err = errno; + mnl_socket_close(nl); + return -err; + } + + *n = nl; + return 0; +} + +static int send_and_dispatch(struct mnl_socket *nl, const void *req, + size_t req_size, enum callback_return_type callback_type, + uint64_t *callback_value) +{ + struct callback_data cb = {}; + uint32_t portid; + int err; + + debug_mnl_dump_rule(req, req_size); + + err = mnl_socket_sendto(nl, req, req_size); + if (err < 0) + return -errno; + + portid = mnl_socket_get_portid(nl); + cb.type = callback_type; + + for (;;) { + char buf[MNL_SOCKET_BUFFER_SIZE]; + + err = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (err <= 0) + break; + + err = mnl_cb_run(buf, err, 0, portid, events_cb, &cb); + if (err <= 0) + break; + } + + if (err < 0) + return -errno; + + if (callback_type == CALLBACK_RETURN_NONE) + return 0; + + if (cb.success) { + if (callback_value) + *callback_value = cb.value; + + return 0; + } + + return -ENOENT; +} + +static void put_batch_headers(char *buf, uint16_t type, uint32_t seq) +{ + + struct nlmsghdr *nlh; + struct nfgenmsg *nfg; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = NLM_F_REQUEST; + nlh->nlmsg_seq = seq; + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = NFNL_SUBSYS_NFTABLES; +} + +static int add_payload(struct nftnl_rule *rule, uint32_t base, + uint32_t dreg, uint32_t offset, uint32_t len) +{ + struct nftnl_expr *expr; + + expr = nftnl_expr_alloc("payload"); + if (!expr) + return -ENOMEM; + + nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_BASE, base); + nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_DREG, dreg); + nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_OFFSET, offset); + nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_LEN, len); + + nftnl_rule_add_expr(rule, expr); + + return 0; +} + +static int add_bitwise(struct nftnl_rule *rule, int reg, const void *mask, + size_t len) +{ + struct nftnl_expr *expr; + uint8_t *xor; + + expr = nftnl_expr_alloc("bitwise"); + if (!expr) + return -ENOMEM; + + xor = alloca(len); + memset(xor, 0, len); + + nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_SREG, reg); + nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_DREG, reg); + nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_LEN, len); + nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_MASK, mask, len); + nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_XOR, xor, len); + + nftnl_rule_add_expr(rule, expr); + + return 0; +} + +static int add_cmp(struct nftnl_rule *rule, uint32_t sreg, uint32_t op, + const void *data, uint32_t data_len) +{ + struct nftnl_expr *expr; + + expr = nftnl_expr_alloc("cmp"); + if (!expr) + return -ENOMEM; + + nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_SREG, sreg); + nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_OP, op); + nftnl_expr_set(expr, NFTNL_EXPR_CMP_DATA, data, data_len); + + nftnl_rule_add_expr(rule, expr); + + return 0; +} + +static int table_cmd(struct mnl_socket *nl, struct nftnl_table *t, + uint16_t cmd, uint16_t family, uint16_t type) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct mnl_nlmsg_batch *batch; + struct nlmsghdr *nlh; + uint32_t seq = 0; + int err; + + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + cmd, family, type, seq++); + nftnl_table_nlmsg_build_payload(nlh, t); + nftnl_table_free(t); + mnl_nlmsg_batch_next(batch); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + /* The current table commands do not support any callback returns. */ + err = send_and_dispatch(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch), 0, NULL); + + mnl_nlmsg_batch_stop(batch); + return err; +} + +static int chain_cmd(struct mnl_socket *nl, struct nftnl_chain *chain, + uint16_t cmd, int family, uint16_t type, + enum callback_return_type cb_type, uint64_t *cb_val) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct mnl_nlmsg_batch *batch; + struct nlmsghdr *nlh; + uint32_t seq = 0; + int err; + + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + cmd, family, type, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain); + nftnl_chain_free(chain); + mnl_nlmsg_batch_next(batch); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + err = send_and_dispatch(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch), cb_type, cb_val); + + mnl_nlmsg_batch_stop(batch); + return err; +} + +static int rule_cmd(struct mnl_socket *nl, struct nftnl_rule *rule, + uint16_t cmd, uint16_t family, uint16_t type, + enum callback_return_type callback_type, + uint64_t *callback_value) +{ + + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct mnl_nlmsg_batch *batch; + struct nlmsghdr *nlh; + uint32_t seq = 0; + int err; + + debug_netlink_dump_rule(rule); + + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + put_batch_headers(mnl_nlmsg_batch_current(batch), + NFNL_MSG_BATCH_BEGIN, seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + cmd, family, type, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule); + mnl_nlmsg_batch_next(batch); + + put_batch_headers(mnl_nlmsg_batch_current(batch), + NFNL_MSG_BATCH_END, seq++); + mnl_nlmsg_batch_next(batch); + + err = send_and_dispatch(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch), + callback_type, callback_value); + mnl_nlmsg_batch_stop(batch); + + return err; +} + +static int rule_delete(struct firewall_handle *handle) +{ + struct nftnl_rule *rule; + struct mnl_socket *nl; + int err; + + DBG(""); + + rule = nftnl_rule_alloc(); + if (!rule) + return -ENOMEM; + + nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set(rule, NFTNL_RULE_CHAIN, handle->chain); + nftnl_rule_set_u64(rule, NFTNL_RULE_HANDLE, handle->handle); + + err = socket_open_and_bind(&nl); + if (err < 0) { + nftnl_rule_free(rule); + return err; + } + + err = rule_cmd(nl, rule, NFT_MSG_DELRULE, NFPROTO_IPV4, + NLM_F_ACK, 0, NULL); + nftnl_rule_free(rule); + mnl_socket_close(nl); + + return err; +} + +struct firewall_context *__connman_firewall_create(void) +{ + struct firewall_context *ctx; + + DBG(""); + + ctx = g_new0(struct firewall_context, 1); + + return ctx; +} + +void __connman_firewall_destroy(struct firewall_context *ctx) +{ + DBG(""); + + g_free(ctx); +} + +static int build_rule_nat(const char *address, unsigned char prefixlen, + const char *interface, struct nftnl_rule **res) +{ + struct nftnl_rule *rule; + struct in_addr ipv4_addr, ipv4_mask; + struct nftnl_expr *expr; + int err; + + /* + * # nft --debug netlink add rule connman nat-postrouting \ + * oifname eth0 ip saddr 10.10.0.0/24 masquerade + * + * ip connman nat-postrouting + * [ meta load oifname => reg 1 ] + * [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ] + * [ payload load 4b @ network header + 12 => reg 1 ] + * [ bitwise reg 1 = (reg=1 & 0x00ffffff ) ^ 0x00000000 ] + * [ cmp eq reg 1 0x00000a0a ] + * [ masq ] + */ + + rule = nftnl_rule_alloc(); + if (!rule) + return -ENOMEM; + + nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); + + /* family ipv4 */ + nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, NFPROTO_IPV4); + + /* oifname */ + expr = nftnl_expr_alloc("meta"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_OIFNAME); + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_DREG, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + err = add_cmp(rule, NFT_REG_1, NFT_CMP_EQ, interface, + strlen(interface) + 1); + if (err < 0) + goto err; + + /* source */ + ipv4_mask.s_addr = htonl((0xffffffff << (32 - prefixlen)) & 0xffffffff); + ipv4_addr.s_addr = inet_addr(address); + ipv4_addr.s_addr &= ipv4_mask.s_addr; + + err = add_payload(rule, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, + offsetof(struct iphdr, saddr), sizeof(struct in_addr)); + if (err < 0) + goto err; + err = add_bitwise(rule, NFT_REG_1, &ipv4_mask.s_addr, + sizeof(struct in_addr)); + if (err < 0) + goto err; + err = add_cmp(rule, NFT_REG_1, NFT_CMP_EQ, &ipv4_addr.s_addr, + sizeof(struct in_addr)); + if (err < 0) + goto err; + + /* masquerade */ + expr = nftnl_expr_alloc("masq"); + if (!expr) + goto err; + nftnl_rule_add_expr(rule, expr); + + *res = rule; + return 0; + +err: + nftnl_rule_free(rule); + return -ENOMEM; +} + +int __connman_firewall_enable_nat(struct firewall_context *ctx, + char *address, unsigned char prefixlen, + char *interface) +{ + struct mnl_socket *nl; + struct nftnl_rule *rule; + int err; + + DBG("address %s/%d interface %s", address, (int)prefixlen, interface); + + err = socket_open_and_bind(&nl); + if (err < 0) + return err; + + err = build_rule_nat(address, prefixlen, interface, &rule); + if (err) + goto out; + + ctx->rule.chain = CONNMAN_CHAIN_NAT_POST; + err = rule_cmd(nl, rule, NFT_MSG_NEWRULE, NFPROTO_IPV4, + NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, + CALLBACK_RETURN_HANDLE, &ctx->rule.handle); + nftnl_rule_free(rule); +out: + mnl_socket_close(nl); + return err; +} + +int __connman_firewall_disable_nat(struct firewall_context *ctx) +{ + return rule_delete(&ctx->rule); +} + +static int build_rule_snat(int index, const char *address, + struct nftnl_rule **res) +{ + struct nftnl_rule *rule; + struct nftnl_expr *expr; + uint32_t snat; + int err; + + /* + * # nft --debug netlink add rule connman nat-postrouting \ + * oif eth0 snat 1.2.3.4 + * ip connman nat-postrouting + * [ meta load oif => reg 1 ] + * [ cmp eq reg 1 0x0000000b ] + * [ immediate reg 1 0x04030201 ] + * [ nat snat ip addr_min reg 1 addr_max reg 0 ] + */ + + rule = nftnl_rule_alloc(); + if (!rule) + return -ENOMEM; + + nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_NAT_POST); + + /* IOF */ + expr = nftnl_expr_alloc("meta"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_OIF); + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_DREG, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + err = add_cmp(rule, NFT_REG_1, NFT_CMP_EQ, &index, sizeof(index)); + if (err < 0) + goto err; + + /* snat */ + expr = nftnl_expr_alloc("immediate"); + if (!expr) + goto err; + snat = inet_addr(address); + nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_1); + nftnl_expr_set(expr, NFTNL_EXPR_IMM_DATA, &snat, sizeof(snat)); + nftnl_rule_add_expr(rule, expr); + + expr = nftnl_expr_alloc("nat"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_NAT_TYPE, NFT_NAT_SNAT); + nftnl_expr_set_u32(expr, NFTNL_EXPR_NAT_FAMILY, NFPROTO_IPV4); + nftnl_expr_set_u32(expr, NFTNL_EXPR_NAT_REG_ADDR_MIN, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + + *res = rule; + return 0; + +err: + nftnl_rule_free(rule); + return -ENOMEM; +} + +int __connman_firewall_enable_snat(struct firewall_context *ctx, + int index, const char *ifname, const char *addr) +{ + struct nftnl_rule *rule; + struct mnl_socket *nl; + int err; + + DBG(""); + + err = socket_open_and_bind(&nl); + if (err < 0) + return err; + + err = build_rule_snat(index, addr, &rule); + if (err) + goto out; + + ctx->rule.chain = CONNMAN_CHAIN_NAT_POST; + err = rule_cmd(nl, rule, NFT_MSG_NEWRULE, NFPROTO_IPV4, + NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, + CALLBACK_RETURN_HANDLE, &ctx->rule.handle); + nftnl_rule_free(rule); +out: + mnl_socket_close(nl); + return err; +} + +int __connman_firewall_disable_snat(struct firewall_context *ctx) +{ + DBG(""); + + return rule_delete(&ctx->rule); +} + +static int build_rule_marking(uid_t uid, uint32_t mark, struct nftnl_rule **res) +{ + struct nftnl_rule *rule; + struct nftnl_expr *expr; + int err; + + /* + * http://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation + * http://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation + * + * # nft --debug netlink add rule connman route-output \ + * meta skuid wagi mark set 1234 + * + * ip connman route-output + * [ meta load skuid => reg 1 ] + * [ cmp eq reg 1 0x000003e8 ] + * [ immediate reg 1 0x000004d2 ] + * [ meta set mark with reg 1 ] + */ + + rule = nftnl_rule_alloc(); + if (!rule) + return -ENOMEM; + + nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); + + expr = nftnl_expr_alloc("meta"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_SKUID); + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_DREG, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + err = add_cmp(rule, NFT_REG_1, NFT_CMP_EQ, &uid, sizeof(uid)); + if (err < 0) + goto err; + + expr = nftnl_expr_alloc("immediate"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_1); + nftnl_expr_set(expr, NFTNL_EXPR_IMM_DATA, &mark, sizeof(mark)); + nftnl_rule_add_expr(rule, expr); + + expr = nftnl_expr_alloc("meta"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_MARK); + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_SREG, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + + *res = rule; + return 0; + +err: + return -ENOMEM; +} + +static int build_rule_src_ip(const char *src_ip, uint32_t mark, struct nftnl_rule **res) +{ + struct nftnl_rule *rule; + struct nftnl_expr *expr; + int err; + in_addr_t s_addr; + + /* + * # nft --debug netlink add rule connman route-output \ + * ip saddr 192.168.10.31 mark set 1234 + * + * ip connman route-output + * [ payload load 4b @ network header + 12 => reg 1 ] + * [ cmp eq reg 1 0x1f0aa8c0 ] + * [ immediate reg 1 0x000004d2 ] + * [ meta set mark with reg 1 ] + */ + + rule = nftnl_rule_alloc(); + if (!rule) + return -ENOMEM; + + nftnl_rule_set(rule, NFTNL_RULE_TABLE, CONNMAN_TABLE); + nftnl_rule_set(rule, NFTNL_RULE_CHAIN, CONNMAN_CHAIN_ROUTE_OUTPUT); + + /* family ipv4 */ + nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, NFPROTO_IPV4); + + /* source IP */ + err = add_payload(rule, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, + offsetof(struct iphdr, saddr), sizeof(struct in_addr)); + if (err < 0) + goto err; + + s_addr = inet_addr(src_ip); + err = add_cmp(rule, NFT_REG_1, NFT_CMP_EQ, &s_addr, sizeof(s_addr)); + if (err < 0) + goto err; + + expr = nftnl_expr_alloc("immediate"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_1); + nftnl_expr_set(expr, NFTNL_EXPR_IMM_DATA, &mark, sizeof(mark)); + nftnl_rule_add_expr(rule, expr); + + expr = nftnl_expr_alloc("meta"); + if (!expr) + goto err; + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_MARK); + nftnl_expr_set_u32(expr, NFTNL_EXPR_META_SREG, NFT_REG_1); + nftnl_rule_add_expr(rule, expr); + + *res = rule; + return 0; + +err: + return -ENOMEM; +} + +int __connman_firewall_enable_marking(struct firewall_context *ctx, + enum connman_session_id_type id_type, + char *id, const char *src_ip, + uint32_t mark) +{ + struct nftnl_rule *rule; + struct mnl_socket *nl; + struct passwd *pw; + uid_t uid; + int err; + + DBG(""); + + if (id_type == CONNMAN_SESSION_ID_TYPE_UID) { + pw = getpwnam(id); + if (!pw) + return -EINVAL; + uid = pw->pw_uid; + } + else if (!src_ip) + return -ENOTSUP; + + err = socket_open_and_bind(&nl); + if (err < 0) + return err; + + if (id_type == CONNMAN_SESSION_ID_TYPE_UID) { + err = build_rule_marking(uid, mark, &rule); + if (err < 0) + goto out; + + ctx->rule.chain = CONNMAN_CHAIN_ROUTE_OUTPUT; + err = rule_cmd(nl, rule, NFT_MSG_NEWRULE, NFPROTO_IPV4, + NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, + CALLBACK_RETURN_HANDLE, &ctx->rule.handle); + + nftnl_rule_free(rule); + } + + if (src_ip) { + err = build_rule_src_ip(src_ip, mark, &rule); + if (err < 0) + goto out; + + ctx->rule.chain = CONNMAN_CHAIN_ROUTE_OUTPUT; + err = rule_cmd(nl, rule, NFT_MSG_NEWRULE, NFPROTO_IPV4, + NLM_F_APPEND|NLM_F_CREATE|NLM_F_ACK, + CALLBACK_RETURN_HANDLE, &ctx->rule.handle); + + nftnl_rule_free(rule); + } +out: + mnl_socket_close(nl); + return err; +} + +int __connman_firewall_disable_marking(struct firewall_context *ctx) +{ + int err; + + DBG(""); + + err = rule_delete(&ctx->rule); + return err; +} + +static struct nftnl_table *build_table(const char *name, uint16_t family) +{ + struct nftnl_table *table; + + table = nftnl_table_alloc(); + if (!table) + return NULL; + + nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, family); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, name); + + return table; +} + + +static struct nftnl_chain *build_chain(const char *name, const char *table, + const char *type, int hooknum, int prio) +{ + struct nftnl_chain *chain; + + chain = nftnl_chain_alloc(); + if (!chain) + return NULL; + + nftnl_chain_set(chain, NFTNL_CHAIN_TABLE, table); + nftnl_chain_set(chain, NFTNL_CHAIN_NAME, name); + + if (type) + nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, type); + + if (hooknum >= 0) + nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, hooknum); + + if (prio >= 0) + nftnl_chain_set_u32(chain, NFTNL_CHAIN_PRIO, prio); + + return chain; +} + +static int create_table_and_chains(struct nftables_info *nft_info) +{ + struct mnl_socket *nl; + struct nftnl_table *table; + struct nftnl_chain *chain; + int err; + + + DBG(""); + + err = socket_open_and_bind(&nl); + if (err < 0) + return err; + + /* + * Add table + * http://wiki.nftables.org/wiki-nftables/index.php/Configuring_tables + */ + + /* + * # nft add table connman + */ + table = build_table(CONNMAN_TABLE, NFPROTO_IPV4); + if (!table) { + err = -ENOMEM; + goto out; + } + + err = table_cmd(nl, table, NFT_MSG_NEWTABLE, NFPROTO_IPV4, + NLM_F_CREATE|NLM_F_ACK); + if (err < 0) + goto out; + + /* + * Add basic chains + * http://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains + */ + + /* + * # nft add chain connman nat-prerouting \ + * { type nat hook prerouting priortiy 0 ; } + */ + chain = build_chain(CONNMAN_CHAIN_NAT_PRE, CONNMAN_TABLE, + "nat", NF_INET_PRE_ROUTING, 0); + if (!chain) { + err = -ENOMEM; + goto out; + } + + err = chain_cmd(nl, chain, NFT_MSG_NEWCHAIN, + NFPROTO_IPV4, NLM_F_CREATE | NLM_F_ACK, + CALLBACK_RETURN_NONE, NULL); + if (err < 0) + goto out; + + /* + * # nft add chain connman nat-postrouting \ + * { type nat hook postrouting priortiy 0 ; } + */ + chain = build_chain(CONNMAN_CHAIN_NAT_POST, CONNMAN_TABLE, + "nat", NF_INET_POST_ROUTING, 0); + if (!chain) { + err = -ENOMEM; + goto out; + } + + err = chain_cmd(nl, chain, NFT_MSG_NEWCHAIN, + NFPROTO_IPV4, NLM_F_CREATE | NLM_F_ACK, + CALLBACK_RETURN_NONE, NULL); + if (err < 0) + goto out; + + /* + * # nft add chain connman route-output \ + * { type route hook output priority 0 ; } + */ + chain = build_chain(CONNMAN_CHAIN_ROUTE_OUTPUT, CONNMAN_TABLE, + "route", NF_INET_LOCAL_OUT, 0); + if (!chain) { + err = -ENOMEM; + goto out; + } + + err = chain_cmd(nl, chain, NFT_MSG_NEWCHAIN, + NFPROTO_IPV4, NLM_F_CREATE | NLM_F_ACK, + CALLBACK_RETURN_NONE, NULL); + if (err < 0) + goto out; + +out: + if (err) + connman_warn("Failed to create basic chains: %s", + strerror(-err)); + mnl_socket_close(nl); + return err; +} + +static int cleanup_table_and_chains(void) +{ + struct nftnl_table *table; + struct mnl_socket *nl; + int err; + + DBG(""); + + err = socket_open_and_bind(&nl); + if (err < 0) + return -ENOMEM; + + /* + * Cleanup everythying in one go. There is little point in + * step-by-step removal of rules and chains if you can get it + * as simple as this. + */ + /* + * # nft delete table connman + */ + table = build_table(CONNMAN_TABLE, NFPROTO_IPV4); + err = table_cmd(nl, table, NFT_MSG_DELTABLE, NFPROTO_IPV4, NLM_F_ACK); + + mnl_socket_close(nl); + return err; +} + +int __connman_firewall_init(void) +{ + int err; + + DBG(""); + + if (getenv("CONNMAN_NFTABLES_DEBUG")) + debug_enabled = true; + + /* + * EAFNOSUPPORT is return whenever the nf_tables_ipv4 hasn't been + * loaded yet. ENOENT is return in case the table is missing. + */ + err = cleanup_table_and_chains(); + if (err < 0 && (err != EAFNOSUPPORT && err != -ENOENT)) { + connman_warn("initializing nftable failed with '%s' %d. Check if kernel module nf_tables_ipv4 is missing\n", + strerror(-err), err); + return err; + } + + nft_info = g_new0(struct nftables_info, 1); + err = create_table_and_chains(nft_info); + if (err) { + g_free(nft_info); + nft_info = NULL; + } + + return err; +} + +void __connman_firewall_cleanup(void) +{ + int err; + + DBG(""); + + err = cleanup_table_and_chains(); + if (err < 0) + connman_warn("cleanup table and chains failed with '%s' %d\n", + strerror(-err), err); + + g_free(nft_info); + nft_info = NULL; +} |