Added --psid option to masquerade extension to specify port ranges, as described in RFC-7597 section 5.1. The PSID option needs the base field in range2, so add version 1 of the masquerade extension. Signed-off-by: Cole Dishington <Cole.Dishington@xxxxxxxxxxxxxxxxxxx> --- Notes: Changes: - Added net-next to subject. - Added patch version to subect. extensions/libipt_MASQUERADE.c | 295 +++++++++++++++++++++++++------ extensions/libipt_MASQUERADE.t | 8 + extensions/libxt_MASQUERADE.man | 6 +- include/linux/netfilter/nf_nat.h | 5 +- 4 files changed, 259 insertions(+), 55 deletions(-) diff --git a/extensions/libipt_MASQUERADE.c b/extensions/libipt_MASQUERADE.c index 90bf6065..fe9e67af 100644 --- a/extensions/libipt_MASQUERADE.c +++ b/extensions/libipt_MASQUERADE.c @@ -12,9 +12,41 @@ enum { O_TO_PORTS = 0, O_RANDOM, O_RANDOM_FULLY, + O_PSID, }; -static void MASQUERADE_help(void) +static unsigned int _log2(unsigned int x) +{ + unsigned int y = 0; + + for (; x > 1; x >>= 1) + y++; + return y; +} + +static void cpy_ipv4_range_to_range2(struct nf_nat_range2 *dst, const struct nf_nat_ipv4_range *src) +{ + memset(&dst->min_addr, 0, sizeof(dst->min_addr)); + memset(&dst->max_addr, 0, sizeof(dst->max_addr)); + memset(&dst->base_proto, 0, sizeof(dst->base_proto)); + + dst->flags = src->flags; + dst->min_addr.ip = src->min_ip; + dst->max_addr.ip = src->max_ip; + dst->min_proto = src->min; + dst->max_proto = src->max; +} + +static void cpy_range2_to_ipv4_range(struct nf_nat_ipv4_range *dst, const struct nf_nat_range2 *src) +{ + dst->flags = src->flags; + dst->min_ip = src->min_addr.ip; + dst->max_ip = src->max_addr.ip; + dst->min = src->min_proto; + dst->max = src->max_proto; +} + +static void MASQUERADE_help_v0(void) { printf( "MASQUERADE target options:\n" @@ -26,14 +58,30 @@ static void MASQUERADE_help(void) " Fully randomize source port.\n"); } -static const struct xt_option_entry MASQUERADE_opts[] = { +static void MASQUERADE_help_v1(void) +{ + MASQUERADE_help_v0(); + printf( +" --psid <offset_length>:<psid>:<psid_length>\n" +" Run in PSID mode with this PSID\n"); +} + +static const struct xt_option_entry MASQUERADE_opts_v0[] = { {.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING}, {.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE}, {.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE}, XTOPT_TABLEEND, }; -static void MASQUERADE_init(struct xt_entry_target *t) +static const struct xt_option_entry MASQUERADE_opts_v1[] = { + {.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING}, + {.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE}, + {.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE}, + {.name = "psid", .id = O_PSID, .type = XTTYPE_STRING}, + XTOPT_TABLEEND, +}; + +static void MASQUERADE_init_v0(struct xt_entry_target *t) { struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data; @@ -42,21 +90,20 @@ static void MASQUERADE_init(struct xt_entry_target *t) } /* Parses ports */ -static void -parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr) +static void parse_ports(const char *arg, struct nf_nat_range2 *r) { char *end; unsigned int port, maxport; - mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED; + r->flags |= NF_NAT_RANGE_PROTO_SPECIFIED; if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX)) xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg); switch (*end) { case '\0': - mr->range[0].min.tcp.port - = mr->range[0].max.tcp.port + r->min_proto.tcp.port + = r->max_proto.tcp.port = htons(port); return; case '-': @@ -66,8 +113,8 @@ parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr) if (maxport < port) break; - mr->range[0].min.tcp.port = htons(port); - mr->range[0].max.tcp.port = htons(maxport); + r->min_proto.tcp.port = htons(port); + r->max_proto.tcp.port = htons(maxport); return; default: break; @@ -75,11 +122,64 @@ parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr) xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg); } -static void MASQUERADE_parse(struct xt_option_call *cb) +static void range_to_psid_args(const struct nf_nat_range2 *r, unsigned int *offset_len, + unsigned int *psid, unsigned int *psid_len) +{ + unsigned int offset, power_j, psid_mask; + + power_j = ntohs(r->max_proto.all) - ntohs(r->min_proto.all) + 1; + offset = ntohs(r->base_proto.all); + if (offset == 0) + offset = 1 << 16; + + *offset_len = 16 - _log2(offset); + *psid_len = _log2(offset / power_j); + psid_mask = ((1 << *psid_len) - 1) * power_j; + *psid = (ntohs(r->min_proto.all) & psid_mask) >> _log2(power_j); +} + +static void parse_psid(const char *arg, struct nf_nat_range2 *r) +{ + char *end; + unsigned int offset_len, psid, psid_len, min, offset; + + if (!xtables_strtoui(arg, &end, &offset_len, 0, 16) || *end != ':') + xtables_param_act(XTF_BAD_VALUE, + "MASQUERADE", "--psid <offset_length> invalid", arg); + + if (!xtables_strtoui(end + 1, &end, &psid, 0, UINT16_MAX) || *end != ':') + xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--psid <psid> invalid", arg); + + if (!xtables_strtoui(end + 1, &end, &psid_len, 0, 16) || *end != '\0') + xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--psid <psid_length> invalid", arg); + + if (psid >= (1 << psid_len)) + xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", + "--psid <psid> too large for <psid_length>", arg); + + if (psid_len + offset_len >= 16) + xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", + "--psid <offset_length> and/or <psid_length> are too large", arg); + + offset = (1 << (16 - offset_len)); + psid = psid << (16 - offset_len - psid_len); + + /* Handle the special case of no offset bits (a=0), so offset loops */ + min = psid; + if (offset) + min += offset; + + r->min_proto.all = htons(min); + r->max_proto.all = htons(min + ((1 << (16 - offset_len - psid_len)) - 1)); + r->base_proto.all = htons(offset); + r->flags |= NF_NAT_RANGE_PSID; + r->flags |= NF_NAT_RANGE_PROTO_SPECIFIED; +} + +static void _MASQUERADE_parse(struct xt_option_call *cb, struct nf_nat_range2 *r, int rev) { const struct ipt_entry *entry = cb->xt_entry; int portok; - struct nf_nat_ipv4_multi_range_compat *mr = cb->data; if (entry->ip.proto == IPPROTO_TCP || entry->ip.proto == IPPROTO_UDP @@ -96,29 +196,50 @@ static void MASQUERADE_parse(struct xt_option_call *cb) if (!portok) xtables_error(PARAMETER_PROBLEM, "Need TCP, UDP, SCTP or DCCP with port specification"); - parse_ports(cb->arg, mr); + parse_ports(cb->arg, r); break; case O_RANDOM: - mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM; + r->flags |= NF_NAT_RANGE_PROTO_RANDOM; break; case O_RANDOM_FULLY: - mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY; + r->flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY; + break; + case O_PSID: + parse_psid(cb->arg, r); break; } } -static void -MASQUERADE_print(const void *ip, const struct xt_entry_target *target, - int numeric) +static void MASQUERADE_parse_v0(struct xt_option_call *cb) +{ + struct nf_nat_ipv4_multi_range_compat *mr = (void *)cb->data; + struct nf_nat_range2 r = {}; + + cpy_ipv4_range_to_range2(&r, &mr->range[0]); + _MASQUERADE_parse(cb, &r, 0); + cpy_range2_to_ipv4_range(&mr->range[0], &r); +} + +static void MASQUERADE_parse_v1(struct xt_option_call *cb) +{ + _MASQUERADE_parse(cb, (struct nf_nat_range2 *)cb->data, 1); +} + +static void _MASQUERADE_print(const struct nf_nat_range2 *r, int rev) { - const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data; - const struct nf_nat_ipv4_range *r = &mr->range[0]; if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { - printf(" masq ports: "); - printf("%hu", ntohs(r->min.tcp.port)); - if (r->max.tcp.port != r->min.tcp.port) - printf("-%hu", ntohs(r->max.tcp.port)); + if (r->flags & NF_NAT_RANGE_PSID) { + unsigned int offset, psid, psid_length; + + range_to_psid_args(r, &offset, &psid, &psid_length); + printf(" masq psid: %hu:%hu:%hu", offset, psid, psid_length); + } else { + printf(" masq ports: "); + printf("%hu", ntohs(r->min_proto.tcp.port)); + if (r->max_proto.tcp.port != r->min_proto.tcp.port) + printf("-%hu", ntohs(r->max_proto.tcp.port)); + } } if (r->flags & NF_NAT_RANGE_PROTO_RANDOM) @@ -126,18 +247,37 @@ MASQUERADE_print(const void *ip, const struct xt_entry_target *target, if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) printf(" random-fully"); + + } -static void -MASQUERADE_save(const void *ip, const struct xt_entry_target *target) +static void MASQUERADE_print_v0(const void *ip, const struct xt_entry_target *target, int numeric) { const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data; - const struct nf_nat_ipv4_range *r = &mr->range[0]; + struct nf_nat_range2 r = {}; + + cpy_ipv4_range_to_range2(&r, &mr->range[0]); + _MASQUERADE_print(&r, 0); +} + +static void MASQUERADE_print_v1(const void *ip, const struct xt_entry_target *target, int numeric) +{ + _MASQUERADE_print((const struct nf_nat_range2 *)target->data, 1); +} +static void _MASQUERADE_save(const struct nf_nat_range2 *r, int rev) +{ if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { - printf(" --to-ports %hu", ntohs(r->min.tcp.port)); - if (r->max.tcp.port != r->min.tcp.port) - printf("-%hu", ntohs(r->max.tcp.port)); + if (r->flags & NF_NAT_RANGE_PSID) { + unsigned int offset, psid, psid_length; + + range_to_psid_args(r, &offset, &psid, &psid_length); + printf(" --psid %hu:%hu:%hu", offset, psid, psid_length); + } else { + printf(" --to-ports %hu", ntohs(r->min_proto.tcp.port)); + if (r->max_proto.tcp.port != r->min_proto.tcp.port) + printf("-%hu", ntohs(r->max_proto.tcp.port)); + } } if (r->flags & NF_NAT_RANGE_PROTO_RANDOM) @@ -147,44 +287,93 @@ MASQUERADE_save(const void *ip, const struct xt_entry_target *target) printf(" --random-fully"); } -static int MASQUERADE_xlate(struct xt_xlate *xl, - const struct xt_xlate_tg_params *params) +static void MASQUERADE_save_v0(const void *ip, const struct xt_entry_target *target) { - const struct nf_nat_ipv4_multi_range_compat *mr = - (const void *)params->target->data; - const struct nf_nat_ipv4_range *r = &mr->range[0]; + const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data; + struct nf_nat_range2 r = {}; + + cpy_ipv4_range_to_range2(&r, &mr->range[0]); + _MASQUERADE_save(&r, 0); +} + +static void MASQUERADE_save_v1(const void *ip, const struct xt_entry_target *target) +{ + _MASQUERADE_save((const struct nf_nat_range2 *)target->data, 1); +} +static void _MASQUERADE_xlate(struct xt_xlate *xl, const struct nf_nat_range2 *r, int rev) +{ xt_xlate_add(xl, "masquerade"); if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { - xt_xlate_add(xl, " to :%hu", ntohs(r->min.tcp.port)); - if (r->max.tcp.port != r->min.tcp.port) - xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port)); - } + if (r->flags & NF_NAT_RANGE_PSID) { + unsigned int offset, psid, psid_length; + + range_to_psid_args(r, &offset, &psid, &psid_length); + xt_xlate_add(xl, " psid %hu:%hu:%hu", offset, psid, psid_length); + } else { + xt_xlate_add(xl, " to :%hu", ntohs(r->min_proto.tcp.port)); + if (r->max_proto.tcp.port != r->min_proto.tcp.port) + xt_xlate_add(xl, "-%hu", ntohs(r->max_proto.tcp.port)); + } + } xt_xlate_add(xl, " "); if (r->flags & NF_NAT_RANGE_PROTO_RANDOM) xt_xlate_add(xl, "random "); +} +static int MASQUERADE_xlate_v0(struct xt_xlate *xl, const struct xt_xlate_tg_params *params) +{ + const struct nf_nat_ipv4_multi_range_compat *mr = + (const void *)params->target->data; + struct nf_nat_range2 r = {}; + + cpy_ipv4_range_to_range2(&r, &mr->range[0]); + _MASQUERADE_xlate(xl, &r, 0); + + return 1; +} + +static int MASQUERADE_xlate_v1(struct xt_xlate *xl, const struct xt_xlate_tg_params *params) +{ + _MASQUERADE_xlate(xl, (const struct nf_nat_range2 *)params->target->data, 1); return 1; } -static struct xtables_target masquerade_tg_reg = { - .name = "MASQUERADE", - .version = XTABLES_VERSION, - .family = NFPROTO_IPV4, - .size = XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)), - .userspacesize = XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)), - .help = MASQUERADE_help, - .init = MASQUERADE_init, - .x6_parse = MASQUERADE_parse, - .print = MASQUERADE_print, - .save = MASQUERADE_save, - .x6_options = MASQUERADE_opts, - .xlate = MASQUERADE_xlate, +static struct xtables_target masquerade_tg_reg[] = { + { + .name = "MASQUERADE", + .version = XTABLES_VERSION, + .family = NFPROTO_IPV4, + .revision = 0, + .size = XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)), + .userspacesize = XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)), + .help = MASQUERADE_help_v0, + .init = MASQUERADE_init_v0, + .x6_parse = MASQUERADE_parse_v0, + .print = MASQUERADE_print_v0, + .save = MASQUERADE_save_v0, + .x6_options = MASQUERADE_opts_v0, + .xlate = MASQUERADE_xlate_v0, + }, + { + .name = "MASQUERADE", + .version = XTABLES_VERSION, + .family = NFPROTO_IPV4, + .revision = 1, + .size = XT_ALIGN(sizeof(struct nf_nat_range2)), + .userspacesize = XT_ALIGN(sizeof(struct nf_nat_range2)), + .help = MASQUERADE_help_v1, + .x6_parse = MASQUERADE_parse_v1, + .print = MASQUERADE_print_v1, + .save = MASQUERADE_save_v1, + .x6_options = MASQUERADE_opts_v1, + .xlate = MASQUERADE_xlate_v1, + }, }; void _init(void) { - xtables_register_target(&masquerade_tg_reg); + xtables_register_targets(masquerade_tg_reg, ARRAY_SIZE(masquerade_tg_reg)); } diff --git a/extensions/libipt_MASQUERADE.t b/extensions/libipt_MASQUERADE.t index e25d2a04..3f096e31 100644 --- a/extensions/libipt_MASQUERADE.t +++ b/extensions/libipt_MASQUERADE.t @@ -7,3 +7,11 @@ -p udp -j MASQUERADE --to-ports 1024-65535;=;OK -p udp -j MASQUERADE --to-ports 1024-65536;;FAIL -p udp -j MASQUERADE --to-ports -1;;FAIL +-j MASQUERADE --psid 0:52:8;=;OK +-j MASQUERADE --psid 6;;FAIL +-j MASQUERADE --psid 6:0;;FAIL +-j MASQUERADE --psid 6:0:8;=;OK +-j MASQUERADE --psid -6:-52:-8;=;FAIL +-j MASQUERADE --psid 6:52:8;=;OK +-j MASQUERADE --psid 6:270:8;;FAIL +-j MASQUERADE --psid 6:270:8;;FAIL diff --git a/extensions/libxt_MASQUERADE.man b/extensions/libxt_MASQUERADE.man index 7746f473..026d0b18 100644 --- a/extensions/libxt_MASQUERADE.man +++ b/extensions/libxt_MASQUERADE.man @@ -32,4 +32,8 @@ If option \fB\-\-random-fully\fP is used then port mapping will be fully randomized (kernel >= 3.13). .TP -IPv6 support available since Linux kernels >= 3.7. +\fB\-\-psid\fP \fIoffset_length\fB:\fIpsid\fB:\fIpsid_length +This specifies a range of source ports to use based on RFC-7597 PSID (kernel >= 5.13), overriding the source ports. + \fIoffset_length\fP : Excluded ports (0 to 2^(16 - \fIoffset_length\fP) - 1). + \fIpsid\fP : Selects the port ranges used by this rule. + \fIpsid_length\fP : Bit-length of the \fIpsid\fP field. diff --git a/include/linux/netfilter/nf_nat.h b/include/linux/netfilter/nf_nat.h index b600000d..0f004765 100644 --- a/include/linux/netfilter/nf_nat.h +++ b/include/linux/netfilter/nf_nat.h @@ -10,6 +10,8 @@ #define NF_NAT_RANGE_PERSISTENT (1 << 3) #define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) #define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) +#define NF_NAT_RANGE_NETMAP (1 << 6) +#define NF_NAT_RANGE_PSID (1 << 7) #define NF_NAT_RANGE_PROTO_RANDOM_ALL \ (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) @@ -17,7 +19,8 @@ #define NF_NAT_RANGE_MASK \ (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ - NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET) + NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET | \ + NF_NAT_RANGE_NETMAP | NF_NAT_RANGE_PSID) struct nf_nat_ipv4_range { unsigned int flags; -- 2.33.0