diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/datatype.c | 105 | ||||
-rw-r--r-- | src/evaluate.c | 184 | ||||
-rw-r--r-- | src/netlink_delinearize.c | 67 | ||||
-rw-r--r-- | src/netlink_linearize.c | 5 | ||||
-rw-r--r-- | src/parser.y | 54 | ||||
-rw-r--r-- | src/payload.c | 31 | ||||
-rw-r--r-- | src/scanner.l | 3 | ||||
-rw-r--r-- | src/statement.c | 31 |
8 files changed, 470 insertions, 10 deletions
diff --git a/src/datatype.c b/src/datatype.c index 7090b0ba..8ad211c1 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -24,6 +24,9 @@ #include <gmputil.h> #include <erec.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> + static const struct datatype *datatypes[TYPE_MAX + 1] = { [TYPE_INVALID] = &invalid_type, [TYPE_VERDICT] = &verdict_type, @@ -41,6 +44,9 @@ static const struct datatype *datatypes[TYPE_MAX + 1] = { [TYPE_TIME] = &time_type, [TYPE_MARK] = &mark_type, [TYPE_ARPHRD] = &arphrd_type, + [TYPE_ICMP_CODE] = &icmp_code_type, + [TYPE_ICMPV6_CODE] = &icmpv6_code_type, + [TYPE_ICMPX_CODE] = &icmpx_code_type, }; void datatype_register(const struct datatype *dtype) @@ -685,6 +691,105 @@ const struct datatype mark_type = { .flags = DTYPE_F_PREFIX, }; +static const struct symbol_table icmp_code_tbl = { + .symbols = { + SYMBOL("net-unreachable", ICMP_NET_UNREACH), + SYMBOL("host-unreachable", ICMP_HOST_UNREACH), + SYMBOL("prot-unreachable", ICMP_PROT_UNREACH), + SYMBOL("port-unreachable", ICMP_PORT_UNREACH), + SYMBOL("net-prohibited", ICMP_NET_ANO), + SYMBOL("host-prohibited", ICMP_HOST_ANO), + SYMBOL("admin-prohibited", ICMP_PKT_FILTERED), + SYMBOL_LIST_END + }, +}; + +static void icmp_code_type_print(const struct expr *expr) +{ + return symbolic_constant_print(&icmp_code_tbl, expr); +} + +static struct error_record *icmp_code_type_parse(const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(sym, &icmp_code_tbl, res); +} + +const struct datatype icmp_code_type = { + .type = TYPE_ICMP_CODE, + .name = "icmp code", + .desc = "icmp code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .print = icmp_code_type_print, + .parse = icmp_code_type_parse, +}; + +static const struct symbol_table icmpv6_code_tbl = { + .symbols = { + SYMBOL("no-route", ICMP6_DST_UNREACH_NOROUTE), + SYMBOL("admin-prohibited", ICMP6_DST_UNREACH_ADMIN), + SYMBOL("addr-unreachable", ICMP6_DST_UNREACH_ADDR), + SYMBOL("port-unreachable", ICMP6_DST_UNREACH_NOPORT), + SYMBOL_LIST_END + }, +}; + +static void icmpv6_code_type_print(const struct expr *expr) +{ + return symbolic_constant_print(&icmpv6_code_tbl, expr); +} + +static struct error_record *icmpv6_code_type_parse(const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(sym, &icmpv6_code_tbl, res); +} + +const struct datatype icmpv6_code_type = { + .type = TYPE_ICMPV6_CODE, + .name = "icmpv6 code", + .desc = "icmpv6 code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .print = icmpv6_code_type_print, + .parse = icmpv6_code_type_parse, +}; + +static const struct symbol_table icmpx_code_tbl = { + .symbols = { + SYMBOL("port-unreachable", NFT_REJECT_ICMPX_PORT_UNREACH), + SYMBOL("admin-prohibited", NFT_REJECT_ICMPX_ADMIN_PROHIBITED), + SYMBOL("no-route", NFT_REJECT_ICMPX_NO_ROUTE), + SYMBOL("host-unreachable", NFT_REJECT_ICMPX_HOST_UNREACH), + SYMBOL_LIST_END + }, +}; + +static void icmpx_code_type_print(const struct expr *expr) +{ + return symbolic_constant_print(&icmpx_code_tbl, expr); +} + +static struct error_record *icmpx_code_type_parse(const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(sym, &icmpx_code_tbl, res); +} + +const struct datatype icmpx_code_type = { + .type = TYPE_ICMPX_CODE, + .name = "icmpx code", + .desc = "icmpx code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .print = icmpx_code_type_print, + .parse = icmpx_code_type_parse, +}; + static void time_type_print(const struct expr *expr) { uint64_t days, hours, minutes, seconds; diff --git a/src/evaluate.c b/src/evaluate.c index 52ce5487..602148c8 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -17,6 +17,8 @@ #include <linux/netfilter.h> #include <linux/netfilter_arp.h> #include <linux/netfilter/nf_tables.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> #include <expression.h> #include <statement.h> @@ -1126,12 +1128,192 @@ static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt) return 0; } -static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt) +static int reject_payload_gen_dependency_tcp(struct eval_ctx *ctx, + struct stmt *stmt, + struct expr **payload) +{ + const struct proto_desc *desc; + + desc = ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR].desc; + if (desc != NULL) + return 0; + *payload = payload_expr_alloc(&stmt->location, &proto_tcp, + TCPHDR_UNSPEC); + return 0; +} + +static int reject_payload_gen_dependency_family(struct eval_ctx *ctx, + struct stmt *stmt, + struct expr **payload) +{ + const struct proto_desc *base; + + base = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + if (base != NULL) + return 0; + + if (stmt->reject.icmp_code < 0) + return stmt_error(ctx, stmt, "missing icmp error type"); + + /* Generate a network dependency */ + switch (stmt->reject.family) { + case NFPROTO_IPV4: + *payload = payload_expr_alloc(&stmt->location, &proto_ip, + IPHDR_PROTOCOL); + break; + case NFPROTO_IPV6: + *payload = payload_expr_alloc(&stmt->location, &proto_ip6, + IP6HDR_NEXTHDR); + break; + default: + BUG("unknown reject family"); + } + return 0; +} + +static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt, + struct expr *expr) +{ + struct expr *payload = NULL; + struct stmt *nstmt; + + switch (stmt->reject.type) { + case NFT_REJECT_TCP_RST: + if (reject_payload_gen_dependency_tcp(ctx, stmt, &payload) < 0) + return -1; + break; + case NFT_REJECT_ICMP_UNREACH: + if (reject_payload_gen_dependency_family(ctx, stmt, + &payload) < 0) + return -1; + break; + default: + BUG("cannot generate reject dependency for type %d", + stmt->reject.type); + } + + if (payload_gen_dependency(ctx, payload, &nstmt) < 0) + return -1; + + list_add(&nstmt->list, &ctx->cmd->rule->stmts); + return 0; +} + +static int stmt_evaluate_reject_family(struct eval_ctx *ctx, struct stmt *stmt, + struct expr *expr) { + switch (ctx->pctx.family) { + case NFPROTO_ARP: + return stmt_error(ctx, stmt, "cannot use reject with arp"); + case NFPROTO_IPV4: + case NFPROTO_IPV6: + switch (stmt->reject.type) { + case NFT_REJECT_TCP_RST: + if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0) + return -1; + break; + case NFT_REJECT_ICMPX_UNREACH: + return stmt_error(ctx, stmt, + "abstracted ICMP unreachable not supported"); + case NFT_REJECT_ICMP_UNREACH: + if (stmt->reject.family != ctx->pctx.family) + return stmt_error(ctx, stmt, + "conflicting protocols specified: ip vs ip6"); + break; + } + break; + case NFPROTO_INET: + case NFPROTO_BRIDGE: + if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) + break; + if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0) + return -1; + break; + } + stmt->flags |= STMT_F_TERMINAL; return 0; } +static int stmt_evaluate_reject_default(struct eval_ctx *ctx, + struct stmt *stmt) +{ + switch (ctx->pctx.family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + stmt->reject.type = NFT_REJECT_ICMP_UNREACH; + stmt->reject.family = ctx->pctx.family; + if (ctx->pctx.family == NFPROTO_IPV4) + stmt->reject.icmp_code = ICMP_PORT_UNREACH; + else + stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT; + break; + case NFPROTO_INET: + case NFPROTO_BRIDGE: + stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; + stmt->reject.icmp_code = NFT_REJECT_ICMPX_PORT_UNREACH; + break; + } + return 0; +} + +static int stmt_evaluate_reject_icmp(struct eval_ctx *ctx, struct stmt *stmt) +{ + struct error_record *erec; + struct expr *code; + + erec = symbol_parse(stmt->reject.expr, &code); + if (erec != NULL) { + erec_queue(erec, ctx->msgs); + return -1; + } + stmt->reject.icmp_code = mpz_get_uint8(code->value); + return 0; +} + +static int stmt_evaluate_reset(struct eval_ctx *ctx, struct stmt *stmt) +{ + int protonum; + const struct proto_desc *desc, *base; + struct proto_ctx *pctx = &ctx->pctx; + + base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; + desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc; + if (desc == NULL) + return 0; + + protonum = proto_find_num(base, desc); + switch (protonum) { + case IPPROTO_TCP: + break; + default: + if (stmt->reject.type == NFT_REJECT_TCP_RST) { + return stmt_error(ctx, stmt, + "you cannot use tcp reset with this protocol"); + } + break; + } + return 0; +} + +static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt) +{ + struct expr *expr = ctx->cmd->expr; + + if (stmt->reject.icmp_code < 0) { + if (stmt_evaluate_reject_default(ctx, stmt) < 0) + return -1; + } else if (stmt->reject.expr != NULL) { + if (stmt_evaluate_reject_icmp(ctx, stmt) < 0) + return -1; + } else { + if (stmt_evaluate_reset(ctx, stmt) < 0) + return -1; + } + + return stmt_evaluate_reject_family(ctx, stmt, expr); +} + static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) { struct proto_ctx *pctx = &ctx->pctx; diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 796b6327..397b65c0 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -14,6 +14,9 @@ #include <string.h> #include <limits.h> #include <linux/netfilter/nf_tables.h> +#include <arpa/inet.h> +#include <linux/netfilter.h> +#include <net/ethernet.h> #include <netlink.h> #include <rule.h> #include <statement.h> @@ -472,8 +475,15 @@ static void netlink_parse_reject(struct netlink_parse_ctx *ctx, const struct nft_rule_expr *expr) { struct stmt *stmt; + uint8_t icmp_code; stmt = reject_stmt_alloc(loc); + stmt->reject.type = nft_rule_expr_get_u32(expr, NFT_EXPR_REJECT_TYPE); + icmp_code = nft_rule_expr_get_u8(expr, NFT_EXPR_REJECT_CODE); + stmt->reject.icmp_code = icmp_code; + stmt->reject.expr = constant_expr_alloc(loc, &integer_type, + BYTEORDER_HOST_ENDIAN, 8, + &icmp_code); list_add_tail(&stmt->list, &ctx->rule->stmts); } @@ -899,6 +909,60 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, } } +static void stmt_reject_postprocess(struct rule_pp_ctx rctx, struct stmt *stmt) +{ + const struct proto_desc *desc, *base; + int protocol; + + switch (rctx.pctx.family) { + case NFPROTO_IPV4: + stmt->reject.family = rctx.pctx.family; + stmt->reject.expr->dtype = &icmp_code_type; + break; + case NFPROTO_IPV6: + stmt->reject.family = rctx.pctx.family; + stmt->reject.expr->dtype = &icmpv6_code_type; + break; + case NFPROTO_INET: + if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) + break; + base = rctx.pctx.protocol[PROTO_BASE_LL_HDR].desc; + desc = rctx.pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + protocol = proto_find_num(base, desc); + switch (protocol) { + case NFPROTO_IPV4: + stmt->reject.expr->dtype = &icmp_code_type; + break; + case NFPROTO_IPV6: + stmt->reject.expr->dtype = &icmpv6_code_type; + break; + } + stmt->reject.family = protocol; + break; + case NFPROTO_BRIDGE: + if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) + break; + base = rctx.pctx.protocol[PROTO_BASE_LL_HDR].desc; + desc = rctx.pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + protocol = proto_find_num(base, desc); + switch (protocol) { + case __constant_htons(ETH_P_IP): + stmt->reject.family = NFPROTO_IPV4; + stmt->reject.expr->dtype = &icmp_code_type; + break; + case __constant_htons(ETH_P_IPV6): + stmt->reject.family = NFPROTO_IPV6; + stmt->reject.expr->dtype = &icmpv6_code_type; + break; + default: + break; + } + break; + default: + break; + } +} + static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *rule) { struct rule_pp_ctx rctx; @@ -926,6 +990,9 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r if (stmt->nat.proto != NULL) expr_postprocess(&rctx, stmt, &stmt->nat.proto); break; + case STMT_REJECT: + stmt_reject_postprocess(rctx, stmt); + break; default: break; } diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index c46b6d47..29f8e9ae 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -612,7 +612,10 @@ static void netlink_gen_reject_stmt(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("reject"); nft_rule_expr_set_u32(nle, NFT_EXPR_REJECT_TYPE, stmt->reject.type); - nft_rule_expr_set_u8(nle, NFT_EXPR_REJECT_CODE, 0); + if (stmt->reject.icmp_code != -1) + nft_rule_expr_set_u8(nle, NFT_EXPR_REJECT_CODE, + stmt->reject.icmp_code); + nft_rule_add_expr(ctx->nlr, nle); } diff --git a/src/parser.y b/src/parser.y index 4a8df7b7..03d6d138 100644 --- a/src/parser.y +++ b/src/parser.y @@ -19,6 +19,8 @@ #include <linux/netfilter.h> #include <linux/netfilter/nf_tables.h> #include <linux/netfilter/nf_conntrack_tuple_common.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> #include <libnftnl/common.h> #include <libnftnl/set.h> @@ -368,6 +370,9 @@ static void location_update(struct location *loc, struct location *rhs, int n) %token WEEK "week" %token _REJECT "reject" +%token RESET "reset" +%token WITH "with" +%token ICMPX "icmpx" %token SNAT "snat" %token DNAT "dnat" @@ -431,8 +436,8 @@ static void location_update(struct location *loc, struct location *rhs, int n) %type <stmt> limit_stmt %destructor { stmt_free($$); } limit_stmt %type <val> time_unit -%type <stmt> reject_stmt -%destructor { stmt_free($$); } reject_stmt +%type <stmt> reject_stmt reject_stmt_alloc +%destructor { stmt_free($$); } reject_stmt reject_stmt_alloc %type <stmt> nat_stmt nat_stmt_alloc %destructor { stmt_free($$); } nat_stmt nat_stmt_alloc %type <stmt> queue_stmt queue_stmt_alloc @@ -1374,12 +1379,55 @@ time_unit : SECOND { $$ = 1ULL; } | WEEK { $$ = 1ULL * 60 * 60 * 24 * 7; } ; -reject_stmt : _REJECT +reject_stmt : reject_stmt_alloc reject_opts + ; + +reject_stmt_alloc : _REJECT { $$ = reject_stmt_alloc(&@$); } ; +reject_opts : /* empty */ + { + $<stmt>0->reject.type = -1; + $<stmt>0->reject.icmp_code = -1; + } + | WITH ICMP TYPE STRING + { + $<stmt>0->reject.family = NFPROTO_IPV4; + $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; + $<stmt>0->reject.expr = + symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), + $4); + $<stmt>0->reject.expr->dtype = &icmp_code_type; + } + | WITH ICMP6 TYPE STRING + { + $<stmt>0->reject.family = NFPROTO_IPV6; + $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; + $<stmt>0->reject.expr = + symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), + $4); + $<stmt>0->reject.expr->dtype = &icmpv6_code_type; + } + | WITH ICMPX TYPE STRING + { + $<stmt>0->reject.type = NFT_REJECT_ICMPX_UNREACH; + $<stmt>0->reject.expr = + symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), + $4); + $<stmt>0->reject.expr->dtype = &icmpx_code_type; + } + | WITH TCP RESET + { + $<stmt>0->reject.type = NFT_REJECT_TCP_RST; + } + ; + nat_stmt : nat_stmt_alloc nat_stmt_args ; diff --git a/src/payload.c b/src/payload.c index b7b74edd..ebf8079b 100644 --- a/src/payload.c +++ b/src/payload.c @@ -198,11 +198,32 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, } desc = ctx->pctx.protocol[expr->payload.base - 1].desc; - /* Special case for mixed IPv4/IPv6 tables: use meta L4 proto */ - if (desc == NULL && - ctx->pctx.family == NFPROTO_INET && - expr->payload.base == PROTO_BASE_TRANSPORT_HDR) - desc = &proto_inet_service; + /* Special case for mixed IPv4/IPv6 and bridge tables */ + if (desc == NULL) { + switch (ctx->pctx.family) { + case NFPROTO_INET: + switch (expr->payload.base) { + case PROTO_BASE_TRANSPORT_HDR: + desc = &proto_inet_service; + break; + case PROTO_BASE_LL_HDR: + desc = &proto_inet; + break; + default: + break; + } + break; + case NFPROTO_BRIDGE: + switch (expr->payload.base) { + case PROTO_BASE_LL_HDR: + desc = &proto_eth; + break; + default: + break; + } + break; + } + } if (desc == NULL) return expr_error(ctx->msgs, expr, diff --git a/src/scanner.l b/src/scanner.l index 35c9446f..9f054fdf 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -313,6 +313,9 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "week" { return WEEK; } "reject" { return _REJECT; } +"with" { return WITH; } +"reset" { return RESET; } +"icmpx" { return ICMPX; } "snat" { return SNAT; } "dnat" { return DNAT; } diff --git a/src/statement.c b/src/statement.c index 8e4b49e2..357f0948 100644 --- a/src/statement.c +++ b/src/statement.c @@ -16,6 +16,10 @@ #include <string.h> #include <syslog.h> +#include <arpa/inet.h> +#include <linux/netfilter.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> #include <statement.h> #include <utils.h> #include <list.h> @@ -227,6 +231,33 @@ struct stmt *queue_stmt_alloc(const struct location *loc) static void reject_stmt_print(const struct stmt *stmt) { printf("reject"); + switch (stmt->reject.type) { + case NFT_REJECT_TCP_RST: + printf(" with tcp reset"); + break; + case NFT_REJECT_ICMPX_UNREACH: + if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH) + break; + printf(" with icmpx type "); + expr_print(stmt->reject.expr); + break; + case NFT_REJECT_ICMP_UNREACH: + switch (stmt->reject.family) { + case NFPROTO_IPV4: + if (stmt->reject.icmp_code == ICMP_PORT_UNREACH) + break; + printf(" with icmp type "); + expr_print(stmt->reject.expr); + break; + case NFPROTO_IPV6: + if (stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT) + break; + printf(" with icmpv6 type "); + expr_print(stmt->reject.expr); + break; + } + break; + } } static const struct stmt_ops reject_stmt_ops = { |