/* * Copyright (c) 2008 Patrick McHardy * * 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. * * Development of this code funded by Astaro AG (http://www.astaro.com/) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct stmt *stmt_alloc(const struct location *loc, const struct stmt_ops *ops) { struct stmt *stmt; stmt = xzalloc(sizeof(*stmt)); init_list_head(&stmt->list); stmt->location = *loc; stmt->ops = ops; return stmt; } void stmt_free(struct stmt *stmt) { if (stmt == NULL) return; if (stmt->ops->destroy) stmt->ops->destroy(stmt); xfree(stmt); } void stmt_list_free(struct list_head *list) { struct stmt *i, *next; list_for_each_entry_safe(i, next, list, list) { list_del(&i->list); stmt_free(i); } } void stmt_print(const struct stmt *stmt, struct output_ctx *octx) { stmt->ops->print(stmt, octx); } static void expr_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { expr_print(stmt->expr, octx); } static void expr_stmt_destroy(struct stmt *stmt) { expr_free(stmt->expr); } static const struct stmt_ops expr_stmt_ops = { .type = STMT_EXPRESSION, .name = "expression", .print = expr_stmt_print, .json = expr_stmt_json, .destroy = expr_stmt_destroy, }; struct stmt *expr_stmt_alloc(const struct location *loc, struct expr *expr) { struct stmt *stmt; stmt = stmt_alloc(loc, &expr_stmt_ops); stmt->expr = expr; return stmt; } static const struct stmt_ops verdict_stmt_ops = { .type = STMT_VERDICT, .name = "verdict", .print = expr_stmt_print, .json = verdict_stmt_json, .destroy = expr_stmt_destroy, }; struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr) { struct stmt *stmt; stmt = stmt_alloc(loc, &verdict_stmt_ops); stmt->expr = expr; return stmt; } static void meter_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; nft_print(octx, "meter "); if (stmt->meter.set) { expr_print(stmt->meter.set, octx); nft_print(octx, " "); } nft_print(octx, "size %u { ", stmt->meter.size); expr_print(stmt->meter.key, octx); nft_print(octx, " "); octx->flags |= NFT_CTX_OUTPUT_STATELESS; stmt_print(stmt->meter.stmt, octx); octx->flags = flags; nft_print(octx, "} "); } static void meter_stmt_destroy(struct stmt *stmt) { expr_free(stmt->meter.key); expr_free(stmt->meter.set); stmt_free(stmt->meter.stmt); xfree(stmt->meter.name); } static const struct stmt_ops meter_stmt_ops = { .type = STMT_METER, .name = "meter", .print = meter_stmt_print, .json = meter_stmt_json, .destroy = meter_stmt_destroy, }; struct stmt *meter_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &meter_stmt_ops); } static void connlimit_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "ct count %s%u ", stmt->connlimit.flags ? "over " : "", stmt->connlimit.count); } static const struct stmt_ops connlimit_stmt_ops = { .type = STMT_CONNLIMIT, .name = "connlimit", .print = connlimit_stmt_print, .json = connlimit_stmt_json, }; struct stmt *connlimit_stmt_alloc(const struct location *loc) { struct stmt *stmt; stmt = stmt_alloc(loc, &connlimit_stmt_ops); stmt->flags |= STMT_F_STATEFUL; return stmt; } static void counter_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "counter"); if (nft_output_stateless(octx)) return; nft_print(octx, " packets %" PRIu64 " bytes %" PRIu64, stmt->counter.packets, stmt->counter.bytes); } static const struct stmt_ops counter_stmt_ops = { .type = STMT_COUNTER, .name = "counter", .print = counter_stmt_print, .json = counter_stmt_json, }; struct stmt *counter_stmt_alloc(const struct location *loc) { struct stmt *stmt; stmt = stmt_alloc(loc, &counter_stmt_ops); stmt->flags |= STMT_F_STATEFUL; return stmt; } static const char *objref_type[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_COUNTER] = "counter", [NFT_OBJECT_QUOTA] = "quota", [NFT_OBJECT_CT_HELPER] = "ct helper", [NFT_OBJECT_LIMIT] = "limit", [NFT_OBJECT_CT_TIMEOUT] = "ct timeout", [NFT_OBJECT_SECMARK] = "secmark", }; const char *objref_type_name(uint32_t type) { if (type > NFT_OBJECT_MAX) return "unknown"; return objref_type[type]; } static void objref_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { switch (stmt->objref.type) { case NFT_OBJECT_CT_HELPER: nft_print(octx, "ct helper set "); break; case NFT_OBJECT_CT_TIMEOUT: nft_print(octx, "ct timeout set "); break; default: nft_print(octx, "%s name ", objref_type_name(stmt->objref.type)); break; } expr_print(stmt->objref.expr, octx); } static void objref_stmt_destroy(struct stmt *stmt) { expr_free(stmt->objref.expr); } static const struct stmt_ops objref_stmt_ops = { .type = STMT_OBJREF, .name = "objref", .print = objref_stmt_print, .json = objref_stmt_json, .destroy = objref_stmt_destroy, }; struct stmt *objref_stmt_alloc(const struct location *loc) { struct stmt *stmt; stmt = stmt_alloc(loc, &objref_stmt_ops); return stmt; } static const char *syslog_level[NFT_LOGLEVEL_MAX + 1] = { [NFT_LOGLEVEL_EMERG] = "emerg", [NFT_LOGLEVEL_ALERT] = "alert", [NFT_LOGLEVEL_CRIT] = "crit", [NFT_LOGLEVEL_ERR] = "err", [NFT_LOGLEVEL_WARNING] = "warn", [NFT_LOGLEVEL_NOTICE] = "notice", [NFT_LOGLEVEL_INFO] = "info", [NFT_LOGLEVEL_DEBUG] = "debug", [NFT_LOGLEVEL_AUDIT] = "audit" }; const char *log_level(uint32_t level) { if (level > NFT_LOGLEVEL_MAX) return "unknown"; return syslog_level[level]; } int log_level_parse(const char *level) { int i; for (i = 0; i <= NFT_LOGLEVEL_MAX; i++) { if (syslog_level[i] && !strcmp(level, syslog_level[i])) return i; } return -1; } static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "log"); if (stmt->log.flags & STMT_LOG_PREFIX) nft_print(octx, " prefix \"%s\"", stmt->log.prefix); if (stmt->log.flags & STMT_LOG_GROUP) nft_print(octx, " group %u", stmt->log.group); if (stmt->log.flags & STMT_LOG_SNAPLEN) nft_print(octx, " snaplen %u", stmt->log.snaplen); if (stmt->log.flags & STMT_LOG_QTHRESHOLD) nft_print(octx, " queue-threshold %u", stmt->log.qthreshold); if ((stmt->log.flags & STMT_LOG_LEVEL) && stmt->log.level != LOG_WARNING) nft_print(octx, " level %s", log_level(stmt->log.level)); if ((stmt->log.logflags & NF_LOG_MASK) == NF_LOG_MASK) { nft_print(octx, " flags all"); } else { if (stmt->log.logflags & (NF_LOG_TCPSEQ | NF_LOG_TCPOPT)) { const char *delim = " "; nft_print(octx, " flags tcp"); if (stmt->log.logflags & NF_LOG_TCPSEQ) { nft_print(octx, " sequence"); delim = ","; } if (stmt->log.logflags & NF_LOG_TCPOPT) nft_print(octx, "%soptions", delim); } if (stmt->log.logflags & NF_LOG_IPOPT) nft_print(octx, " flags ip options"); if (stmt->log.logflags & NF_LOG_UID) nft_print(octx, " flags skuid"); if (stmt->log.logflags & NF_LOG_MACDECODE) nft_print(octx, " flags ether"); } } static void log_stmt_destroy(struct stmt *stmt) { xfree(stmt->log.prefix); } static const struct stmt_ops log_stmt_ops = { .type = STMT_LOG, .name = "log", .print = log_stmt_print, .json = log_stmt_json, .destroy = log_stmt_destroy, }; struct stmt *log_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &log_stmt_ops); } const char *get_unit(uint64_t u) { switch (u) { case 1: return "second"; case 60: return "minute"; case 60 * 60: return "hour"; case 60 * 60 * 24: return "day"; case 60 * 60 * 24 * 7: return "week"; } return "error"; } static const char * const data_unit[] = { "bytes", "kbytes", "mbytes", NULL }; const char *get_rate(uint64_t byte_rate, uint64_t *rate) { int i; if (!byte_rate) { *rate = 0; return data_unit[0]; } for (i = 0; data_unit[i + 1] != NULL; i++) { if (byte_rate % 1024) break; byte_rate /= 1024; } *rate = byte_rate; return data_unit[i]; } static void limit_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { bool inv = stmt->limit.flags & NFT_LIMIT_F_INV; const char *data_unit; uint64_t rate; switch (stmt->limit.type) { case NFT_LIMIT_PKTS: nft_print(octx, "limit rate %s%" PRIu64 "/%s", inv ? "over " : "", stmt->limit.rate, get_unit(stmt->limit.unit)); if (stmt->limit.burst && stmt->limit.burst != 5) nft_print(octx, " burst %u packets", stmt->limit.burst); break; case NFT_LIMIT_PKT_BYTES: data_unit = get_rate(stmt->limit.rate, &rate); nft_print(octx, "limit rate %s%" PRIu64 " %s/%s", inv ? "over " : "", rate, data_unit, get_unit(stmt->limit.unit)); if (stmt->limit.burst > 0) { uint64_t burst; data_unit = get_rate(stmt->limit.burst, &burst); nft_print(octx, " burst %" PRIu64 " %s", burst, data_unit); } break; } } static const struct stmt_ops limit_stmt_ops = { .type = STMT_LIMIT, .name = "limit", .print = limit_stmt_print, .json = limit_stmt_json, }; struct stmt *limit_stmt_alloc(const struct location *loc) { struct stmt *stmt; stmt = stmt_alloc(loc, &limit_stmt_ops); stmt->flags |= STMT_F_STATEFUL; return stmt; } static void queue_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { const char *delim = " "; nft_print(octx, "queue"); if (stmt->queue.queue != NULL) { nft_print(octx, " num "); expr_print(stmt->queue.queue, octx); } if (stmt->queue.flags & NFT_QUEUE_FLAG_BYPASS) { nft_print(octx, "%sbypass", delim); delim = ","; } if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) nft_print(octx, "%sfanout", delim); } static void queue_stmt_destroy(struct stmt *stmt) { expr_free(stmt->queue.queue); } static const struct stmt_ops queue_stmt_ops = { .type = STMT_QUEUE, .name = "queue", .print = queue_stmt_print, .json = queue_stmt_json, .destroy = queue_stmt_destroy, }; struct stmt *queue_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &queue_stmt_ops); } static void quota_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { bool inv = stmt->quota.flags & NFT_QUOTA_F_INV; const char *data_unit; uint64_t bytes, used; data_unit = get_rate(stmt->quota.bytes, &bytes); nft_print(octx, "quota %s%" PRIu64 " %s", inv ? "over " : "", bytes, data_unit); if (!nft_output_stateless(octx) && stmt->quota.used) { data_unit = get_rate(stmt->quota.used, &used); nft_print(octx, " used %" PRIu64 " %s", used, data_unit); } } static const struct stmt_ops quota_stmt_ops = { .type = STMT_QUOTA, .name = "quota", .print = quota_stmt_print, .json = quota_stmt_json, }; struct stmt *quota_stmt_alloc(const struct location *loc) { struct stmt *stmt; stmt = stmt_alloc(loc, "a_stmt_ops); stmt->flags |= STMT_F_STATEFUL; return stmt; } static void reject_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "reject"); switch (stmt->reject.type) { case NFT_REJECT_TCP_RST: nft_print(octx, " with tcp reset"); break; case NFT_REJECT_ICMPX_UNREACH: if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH) break; nft_print(octx, " with icmpx type "); expr_print(stmt->reject.expr, octx); break; case NFT_REJECT_ICMP_UNREACH: switch (stmt->reject.family) { case NFPROTO_IPV4: if (stmt->reject.icmp_code == ICMP_PORT_UNREACH) break; nft_print(octx, " with icmp type "); expr_print(stmt->reject.expr, octx); break; case NFPROTO_IPV6: if (stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT) break; nft_print(octx, " with icmpv6 type "); expr_print(stmt->reject.expr, octx); break; } break; } } static void reject_stmt_destroy(struct stmt *stmt) { expr_free(stmt->reject.expr); } static const struct stmt_ops reject_stmt_ops = { .type = STMT_REJECT, .name = "reject", .print = reject_stmt_print, .json = reject_stmt_json, .destroy = reject_stmt_destroy, }; struct stmt *reject_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &reject_stmt_ops); } static void print_nf_nat_flags(uint32_t flags, struct output_ctx *octx) { const char *delim = " "; if (flags == 0) return; if (flags & NF_NAT_RANGE_PROTO_RANDOM) { nft_print(octx, "%srandom", delim); delim = ","; } if (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) { nft_print(octx, "%sfully-random", delim); delim = ","; } if (flags & NF_NAT_RANGE_PERSISTENT) nft_print(octx, "%spersistent", delim); } const char *nat_etype2str(enum nft_nat_etypes type) { static const char * const nat_types[] = { [NFT_NAT_SNAT] = "snat", [NFT_NAT_DNAT] = "dnat", [NFT_NAT_MASQ] = "masquerade", [NFT_NAT_REDIR] = "redirect", }; return nat_types[type]; } static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "%s", nat_etype2str(stmt->nat.type)); if (stmt->nat.addr || stmt->nat.proto) { switch (stmt->nat.family) { case NFPROTO_IPV4: nft_print(octx, " ip"); break; case NFPROTO_IPV6: nft_print(octx, " ip6"); break; } nft_print(octx, " to"); } if (stmt->nat.addr) { nft_print(octx, " "); if (stmt->nat.proto) { if (stmt->nat.addr->etype == EXPR_VALUE && stmt->nat.addr->dtype->type == TYPE_IP6ADDR) { nft_print(octx, "["); expr_print(stmt->nat.addr, octx); nft_print(octx, "]"); } else if (stmt->nat.addr->etype == EXPR_RANGE && stmt->nat.addr->left->dtype->type == TYPE_IP6ADDR) { nft_print(octx, "["); expr_print(stmt->nat.addr->left, octx); nft_print(octx, "]-["); expr_print(stmt->nat.addr->right, octx); nft_print(octx, "]"); } else { expr_print(stmt->nat.addr, octx); } } else { expr_print(stmt->nat.addr, octx); } } if (stmt->nat.proto) { if (!stmt->nat.addr) nft_print(octx, " "); nft_print(octx, ":"); expr_print(stmt->nat.proto, octx); } print_nf_nat_flags(stmt->nat.flags, octx); } static void nat_stmt_destroy(struct stmt *stmt) { expr_free(stmt->nat.addr); expr_free(stmt->nat.proto); } static const struct stmt_ops nat_stmt_ops = { .type = STMT_NAT, .name = "nat", .print = nat_stmt_print, .json = nat_stmt_json, .destroy = nat_stmt_destroy, }; struct stmt *nat_stmt_alloc(const struct location *loc, enum nft_nat_etypes type) { struct stmt *stmt = stmt_alloc(loc, &nat_stmt_ops); stmt->nat.type = type; return stmt; } const char * const set_stmt_op_names[] = { [NFT_DYNSET_OP_ADD] = "add", [NFT_DYNSET_OP_UPDATE] = "update", }; static void set_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; nft_print(octx, "%s ", set_stmt_op_names[stmt->set.op]); expr_print(stmt->set.set, octx); nft_print(octx, " { "); expr_print(stmt->set.key, octx); if (stmt->set.stmt) { nft_print(octx, " "); octx->flags |= NFT_CTX_OUTPUT_STATELESS; stmt_print(stmt->set.stmt, octx); octx->flags = flags; } nft_print(octx, " }"); } static void set_stmt_destroy(struct stmt *stmt) { expr_free(stmt->set.key); expr_free(stmt->set.set); stmt_free(stmt->set.stmt); } static const struct stmt_ops set_stmt_ops = { .type = STMT_SET, .name = "set", .print = set_stmt_print, .json = set_stmt_json, .destroy = set_stmt_destroy, }; struct stmt *set_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &set_stmt_ops); } static void map_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; nft_print(octx, "%s ", set_stmt_op_names[stmt->map.op]); expr_print(stmt->map.set, octx); nft_print(octx, " { "); expr_print(stmt->map.key, octx); if (stmt->map.stmt) { nft_print(octx, " "); octx->flags |= NFT_CTX_OUTPUT_STATELESS; stmt_print(stmt->map.stmt, octx); octx->flags = flags; } nft_print(octx, " : "); expr_print(stmt->map.data, octx); nft_print(octx, " }"); } static void map_stmt_destroy(struct stmt *stmt) { expr_free(stmt->map.key); expr_free(stmt->map.data); expr_free(stmt->map.set); stmt_free(stmt->map.stmt); } static const struct stmt_ops map_stmt_ops = { .type = STMT_MAP, .name = "map", .print = map_stmt_print, .destroy = map_stmt_destroy, }; struct stmt *map_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &map_stmt_ops); } static void dup_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "dup"); if (stmt->dup.to != NULL) { nft_print(octx, " to "); expr_print(stmt->dup.to, octx); if (stmt->dup.dev != NULL) { nft_print(octx, " device "); expr_print(stmt->dup.dev, octx); } } } static void dup_stmt_destroy(struct stmt *stmt) { expr_free(stmt->dup.to); expr_free(stmt->dup.dev); } static const struct stmt_ops dup_stmt_ops = { .type = STMT_DUP, .name = "dup", .print = dup_stmt_print, .json = dup_stmt_json, .destroy = dup_stmt_destroy, }; struct stmt *dup_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &dup_stmt_ops); } static const char * const nfproto_family_name_array[NFPROTO_NUMPROTO] = { [NFPROTO_IPV4] = "ip", [NFPROTO_IPV6] = "ip6", }; static const char *nfproto_family_name(uint8_t nfproto) { if (nfproto >= NFPROTO_NUMPROTO || !nfproto_family_name_array[nfproto]) return "unknown"; return nfproto_family_name_array[nfproto]; } static void fwd_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { if (stmt->fwd.addr) { nft_print(octx, "fwd %s to ", nfproto_family_name(stmt->fwd.family)); expr_print(stmt->fwd.addr, octx); nft_print(octx, " device "); expr_print(stmt->fwd.dev, octx); } else { nft_print(octx, "fwd to "); expr_print(stmt->fwd.dev, octx); } } static void fwd_stmt_destroy(struct stmt *stmt) { expr_free(stmt->fwd.addr); expr_free(stmt->fwd.dev); } static const struct stmt_ops fwd_stmt_ops = { .type = STMT_FWD, .name = "fwd", .print = fwd_stmt_print, .json = fwd_stmt_json, .destroy = fwd_stmt_destroy, }; struct stmt *fwd_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &fwd_stmt_ops); } static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "tproxy"); if (stmt->tproxy.table_family == NFPROTO_INET && stmt->tproxy.family != NFPROTO_UNSPEC) nft_print(octx, " %s", nfproto_family_name(stmt->tproxy.family)); nft_print(octx, " to"); if (stmt->tproxy.addr) { nft_print(octx, " "); if (stmt->tproxy.addr->etype == EXPR_VALUE && stmt->tproxy.addr->dtype->type == TYPE_IP6ADDR) { nft_print(octx, "["); expr_print(stmt->tproxy.addr, octx); nft_print(octx, "]"); } else { expr_print(stmt->tproxy.addr, octx); } } if (stmt->tproxy.port && stmt->tproxy.port->etype == EXPR_VALUE) { if (!stmt->tproxy.addr) nft_print(octx, " "); nft_print(octx, ":"); expr_print(stmt->tproxy.port, octx); } } static void tproxy_stmt_destroy(struct stmt *stmt) { expr_free(stmt->tproxy.addr); expr_free(stmt->tproxy.port); } static const struct stmt_ops tproxy_stmt_ops = { .type = STMT_TPROXY, .name = "tproxy", .print = tproxy_stmt_print, .json = tproxy_stmt_json, .destroy = tproxy_stmt_destroy, }; struct stmt *tproxy_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &tproxy_stmt_ops); } static void xt_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { xt_stmt_xlate(stmt, octx); } static const struct stmt_ops xt_stmt_ops = { .type = STMT_XT, .name = "xt", .print = xt_stmt_print, .destroy = xt_stmt_destroy, }; struct stmt *xt_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &xt_stmt_ops); }