diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1421,8 +1421,7 @@ with multiple addresses) is provided for convenience only and its use is discouraged. .It Ar addr : Oo Cm not Oc Bro -.Cm any | me | me6 | -.Cm table Ns Pq Ar name Ns Op , Ns Ar value +.Cm any | me | me6 | Ar table-ref .Ar | addr-list | addr-set .Brc .Bl -tag -width indent @@ -1434,16 +1433,32 @@ Matches any IPv6 address configured on an interface in the system. The address list is evaluated at the time the packet is analysed. -.It Cm table Ns Pq Ar name Ns Op , Ns Ar value +.El +.It Ar table-ref : +A table lookup can be specified in one of the following ways: +.Bl -tag -width indent +.It table Ns Pq Ar name Ns Matches any IPv4 or IPv6 address for which an entry exists in the lookup table .Ar number . -If an optional 32-bit unsigned +.It table Ns Pq Ar name , Ns Ar value +Matches any IPv4 or IPv6 address for which an entry exists in the lookup table +.Ar number +and 32-bit unsigned +.Ar value +specified matchess entry value. +.It table Ns Pq Ar name , Ns Ar value-type Ns = Ns Ar value +Matches any IPv4 or IPv6 address for which an entry exists in the lookup table +.Ar number +and 32-bit unsigned .Ar value -is also specified, an entry will match only if it has this value. +specified matches corresponding +.Ar value-type +field for the record found. +.El +.Pp See the .Sx LOOKUP TABLES section below for more information on lookup tables. -.El .It Ar addr-list : ip-addr Ns Op , Ns Ar addr-list .It Ar ip-addr : A host or subnet address specified in one of the following ways: @@ -1664,9 +1679,9 @@ .It Cm fib Ar fibnum Matches a packet that has been tagged to use the given FIB (routing table) number. -.It Cm flow Ar table Ns Pq Ar name Ns Op , Ns Ar value -Search for the flow entry in lookup table -.Ar name . +.It Cm flow Ar table-ref +Search for the flow entry in lookup table specified by +.Ar table-ref . If not found, the match fails. Otherwise, the match succeeds and .Cm tablearg @@ -1682,16 +1697,16 @@ .Ar labels . .Ar labels is a comma separated list of numeric flow labels. -.It Cm dst-mac Ar table Ns Pq Ar name Ns Op , Ns Ar value -Search for the destination MAC address entry in lookup table -.Ar name . +.It Cm dst-mac Ar table-ref +Search for the destination MAC address entry in lookup table specified by +.Ar table-ref . If not found, the match fails. Otherwise, the match succeeds and .Cm tablearg is set to the value extracted from the table. -.It Cm src-mac Ar table Ns Pq Ar name Ns Op , Ns Ar value -Search for the source MAC address entry in lookup table -.Ar name . +.It Cm src-mac Ar table-ref +Search for the source MAC address entry in lookup table specified by +.Ar table-ref . If not found, the match fails. Otherwise, the match succeeds and .Cm tablearg @@ -1909,8 +1924,9 @@ One or more of source and destination addresses and ports can be specified. -.It Cm lookup Bro Cm dst-ip | dst-port | dst-mac | src-ip | src-port | src-mac | uid | -.Cm jail | dscp | mark Brc Ar name +.It Cm lookup Bro Cm dst-ip | dst-ip4 | dst-ip6 | dst-port | dst-mac | src-ip | +.Cm src-ip4 | src-ip6 | src-port | src-mac | uid | jail | dscp | mark +.Brc Ns Oo : Ns Ar bitmask Oc Ar name Search an entry in lookup table .Ar name that matches the field specified as argument. @@ -1919,8 +1935,56 @@ .Cm tablearg is set to the value extracted from the table. .Pp +If an optional +.Ar bitmask +is specified, value of the field is altered by bitwize AND with +.Ar bitmask +and resulting value is being searched instead of the original one. +The +.Ar bitmask +is accepted in the following formats: +.Bl -enum -width indent +.It +In a dotted-quad form, e.g. 127.88.34.0. +This form can be used for IPv4 lookups as well as for all numeric lookup +types. +.It +As a 32-bit number, e.g. 0xf00baa1 or 255. +This form can be used for IPv4 lookups as well as for all numeric lookup +types. +.It +As an IPv6 address when specified alongwith +.Cm dst-ip6 +or +.Cm src-ip6 +field. +If used, the rule will match IPv6 packets only. +Example: src-ip6:afff:ff00:ffff:ffff:0:0:0:0f0f. +.It +As a Ethernet mac address when specified alongwith +.Cm dst-mac +or +.Cm src-mac +field. E.g. 00:11:22:33:44:55. +.El +.Pp +The +.Ar bitmask +can not be specified for +.Cm dst-ip +or +.Cm src-ip +as these field specifiers lookup both IPv4 and IPv6 addresses. +.Pp This option can be useful to quickly dispatch traffic based on certain packet fields. +The +.Ar bitmask +allows to implement wildcard lookups by inserting into table masked prefix and +appying +.Ar bitmask +upon each lookup. +.Pp See the .Sx LOOKUP TABLES section below for more information on lookup tables. @@ -1983,7 +2047,7 @@ .Cm check-state in contrast to .Cm keep-state . -.It Cm recv | xmit | via Brq Ar ifX | Ar ifmask | Ar table Ns Po Ar name Ns Oo , Ns Ar value Oc Pc | Ar ipno | Ar any +.It Cm recv | xmit | via Brq Ar ifX | Ar ifmask | Ar table-ref | Ar ipno | Ar any Matches packets received, transmitted or going through, respectively, the interface specified by exact name .Po Ar ifX Pc , @@ -2001,8 +2065,8 @@ .Sx EXAMPLES section. .Pp -Table -.Ar name +A lookup table specified by +.Ar table-ref may be used to match interface by its kernel ifindex. See the .Sx LOOKUP TABLES @@ -4309,7 +4373,8 @@ .Xr route 4 socket, that were logged using rules with .Cm log Cm logdst Ar rtsock -opcode. Optional +opcode. +Optional .Ar filter-comment can be specified to show only those messages, that were logged by rules with specific rule comment. @@ -4664,10 +4729,41 @@ The following example illustrate usage of flow tables: .Pp .Dl "ipfw table fl create type flow:src-ip,proto,dst-ip,dst-port" -.Dl "ipfw table fl add 2a02:6b8:77::88,tcp,2a02:6b8:77::99,80 11" +.Dl "ipfw table fl add 2001:db8:77::88,tcp,2001:db8:77::99,80 11" .Dl "ipfw table fl add 10.0.0.1,udp,10.0.0.2,53 12" .Dl ".." .Dl "ipfw add 100 allow ip from any to any flow 'table(fl,11)' recv ix0" +.Pp +The following example illustrate masked table lookups to aid uniform client +distribution among multiple NAT instances: +.Bd -literal -offset indent +# Configure NAT instances +ipfw nat 10 config ip 192.0.2.0 +ipfw nat 11 config ip 192.0.2.1 +ipfw nat 12 config ip 192.0.2.2 +ipfw nat 13 config ip 192.0.2.3 + +ipfw table mynats create type addr valtype nat +# Map external NAT address to NAT instance +ipfw table mynats add 192.0.2.0 10 +ipfw table mynats add 192.0.2.1 11 +ipfw table mynats add 192.0.2.2 12 +ipfw table mynats add 192.0.2.3 13 + +# Map last 2 bits of client's IP address to NAT instance +ipfw table mynats add 0.0.0.0 10 +ipfw table mynats add 0.0.0.1 11 +ipfw table mynats add 0.0.0.2 12 +ipfw table mynats add 0.0.0.3 13 + +# In -> Out NAT, zero out all bits in a client's IP exept +# 2 least significant prior to table lookup +ipfw add nat tablearg ip from 10.0.0.0/24 to any + lookup src-ip4:0.0.0.3 mynats +# Out -> In NAT +ipfw add nat tablearg ip from any to 192.0.2.0/30 + lookup dst-ip mynats +.Ed .Ss SETS OF RULES To add a set of rules atomically, e.g.\& set 18: .Pp diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -317,6 +317,10 @@ static struct _s_x lookup_keys[] = { { "dst-ip", LOOKUP_DST_IP }, { "src-ip", LOOKUP_SRC_IP }, + { "dst-ip6", LOOKUP_DST_IP6 }, + { "src-ip6", LOOKUP_SRC_IP6 }, + { "dst-ip4", LOOKUP_DST_IP4 }, + { "src-ip4", LOOKUP_SRC_IP4 }, { "dst-port", LOOKUP_DST_PORT }, { "src-port", LOOKUP_SRC_PORT }, { "dst-mac", LOOKUP_DST_MAC }, @@ -342,6 +346,7 @@ { "fib", TVALUE_FIB }, { "nat", TVALUE_NAT }, { "nh4", TVALUE_NH4 }, + { "nh6", TVALUE_NH6 }, { "dscp", TVALUE_DSCP }, { "limit", TVALUE_LIMIT }, { "mark", TVALUE_MARK }, @@ -1319,12 +1324,27 @@ } static void -print_tvalue(struct buf_pr *bp, const ipfw_insn_table *cmd) +print_tvalue(struct buf_pr *bp, const ipfw_insn_lookup *cmd) { + char maskbuf[INET6_ADDRSTRLEN]; const char *name; name = match_value(tvalue_names, IPFW_TVALUE_TYPE(&cmd->o)); - bprintf(bp, ",%s=%u", name != NULL ? name: "", cmd->value); + switch(IPFW_TVALUE_TYPE(&cmd->o)) { + case TVALUE_NH6: + if (inet_ntop(AF_INET6, &insntoc(&cmd->o, lookup)->ip6, + maskbuf, sizeof(maskbuf)) == NULL) + strcpy(maskbuf, ""); + bprintf(bp, ",%s=%s", name != NULL ? name: "", + maskbuf); + return; + case TVALUE_NH4: + bprintf(bp, ",%s=%s", name != NULL ? name: "", + inet_ntoa(cmd->ip4)); + return; + } + bprintf(bp, ",%s=%u", name != NULL ? name: "", + cmd->u32); } @@ -1335,11 +1355,14 @@ print_ip(struct buf_pr *bp, const struct format_opts *fo, const ipfw_insn_ip *cmd) { + char maskbuf[INET6_ADDRSTRLEN]; + const uint32_t *a = insntoc(cmd, u32)->d; struct hostent *he = NULL; const struct in_addr *ia; - const uint32_t *a = ((const ipfw_insn_u32 *)cmd)->d; - uint32_t len = F_LEN(&cmd->o); + const ipfw_insn_lookup *l = insntoc(cmd, lookup); + const char *key; char *t; + uint32_t len = F_LEN(&cmd->o); bprintf(bp, " "); switch (cmd->o.opcode) { @@ -1348,32 +1371,65 @@ bprintf(bp, "me"); return; - case O_IP_DST_LOOKUP: - if ((len == F_INSN_SIZE(ipfw_insn_kidx) || - len == F_INSN_SIZE(ipfw_insn_table)) && - IPFW_LOOKUP_TYPE(&cmd->o) != LOOKUP_NONE) { - const char *key; - - key = match_value(lookup_keys, - IPFW_LOOKUP_TYPE(&cmd->o)); - t = table_search_ctlv(fo->tstate, - insntoc(&cmd->o, kidx)->kidx); - if (len == F_INSN_SIZE(ipfw_insn_table)) { - bprintf(bp, "lookup %s:%#x %s", - (key != NULL ? key : ""), - insntoc(&cmd->o, table)->value, t); - } else - bprintf(bp, "lookup %s %s", key != NULL ? key: - "", t); + case O_TABLE_LOOKUP: { + key = match_value(lookup_keys, + IPFW_LOOKUP_TYPE(&cmd->o)); + t = table_search_ctlv(fo->tstate, + insntoc(&cmd->o, kidx)->kidx); + if (IPFW_LOOKUP_MASKING(&cmd->o) == 0 || + len != F_INSN_SIZE(ipfw_insn_lookup)) { + bprintf(bp, "lookup %s %s", + (key != NULL ? key : ""), t); return; } - /* FALLTHROUGH */ + switch (IPFW_LOOKUP_TYPE(&cmd->o)) { + case LOOKUP_DST_IP6: + case LOOKUP_SRC_IP6: + if (inet_ntop(AF_INET6, &l->ip6, + maskbuf, sizeof(maskbuf)) == NULL) + strcpy(maskbuf, ""); + bprintf(bp, "lookup %s:%s %s", key, maskbuf, t); + break; + case LOOKUP_DST_IP: + case LOOKUP_SRC_IP: + case LOOKUP_DST_IP4: + case LOOKUP_SRC_IP4: + bprintf(bp, "lookup %s:%s %s", key, + inet_ntoa(l->ip4), t); + break; + case LOOKUP_DST_MAC: + case LOOKUP_SRC_MAC: + bprintf(bp, "lookup %s:%s %s", key, + ether_ntoa((const struct ether_addr *)&l->mac), t); + break; + default: + bprintf(bp, "lookup %s:%#x %s", + (key != NULL ? key : ""), + l->u32, t); + } + return; + } + case O_IP_DST_LOOKUP: case O_IP_SRC_LOOKUP: t = table_search_ctlv(fo->tstate, insntoc(&cmd->o, kidx)->kidx); + /* + * D53694 compatibility layer, to be removed. + * Properly show rules loaded into new kernel modules by + * an old ipfw binary. + */ + if (IPFW_LOOKUP_MASKING(&cmd->o) != 0 && + len == F_INSN_SIZE(ipfw_insn_table)) { + key = match_value(lookup_keys, + IPFW_LOOKUP_TYPE(&cmd->o)); + bprintf(bp, "lookup %s:%#x %s", + (key != NULL ? key : ""), + insntoc(&cmd->o, table)->value, t); + return; + } bprintf(bp, "table(%s", t); - if (len == F_INSN_SIZE(ipfw_insn_table)) - print_tvalue(bp, insntoc(&cmd->o, table)); + if (IPFW_LOOKUP_MATCH_TVALUE(&cmd->o) != 0) + print_tvalue(bp, l); bprintf(bp, ")"); return; } @@ -1478,15 +1534,14 @@ print_mac_lookup(struct buf_pr *bp, const struct format_opts *fo, const ipfw_insn *cmd) { - uint32_t len = F_LEN(cmd); char *t; bprintf(bp, " "); t = table_search_ctlv(fo->tstate, insntoc(cmd, kidx)->kidx); bprintf(bp, "table(%s", t); - if (len == F_INSN_SIZE(ipfw_insn_table)) - print_tvalue(bp, insntoc(cmd, table)); + if (IPFW_LOOKUP_MATCH_TVALUE(cmd) != 0) + print_tvalue(bp, insntoc(cmd, lookup)); bprintf(bp, ")"); } @@ -1651,6 +1706,8 @@ case O_IP_SRC_SET: if (state->flags & HAVE_SRCIP) bprintf(bp, " src-ip"); + /* FALLTHROUGH */ + case O_TABLE_LOOKUP: print_ip(bp, fo, insntoc(cmd, ip)); break; case O_IP_DST: @@ -1775,8 +1832,8 @@ s = table_search_ctlv(fo->tstate, insntoc(cmd, kidx)->kidx); bprintf(bp, " flow table(%s", s); - if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_table)) - print_tvalue(bp, insntoc(cmd, table)); + if (IPFW_LOOKUP_MATCH_TVALUE(cmd) != 0) + print_tvalue(bp, insntoc(cmd, lookup)); bprintf(bp, ")"); break; case O_IPID: @@ -3312,8 +3369,11 @@ return (pack_object(tstate, name, IPFW_TLV_TBL_NAME)); } +/* + * Parse table(NAME, value) and table(NAME,key=value) + */ static void -fill_table_value(ipfw_insn *cmd, char *s) +fill_table_value(ipfw_insn_lookup *cmd, char *s) { char *p; int i; @@ -3330,8 +3390,20 @@ p = s; } - IPFW_SET_TVALUE_TYPE(cmd, i); - insntod(cmd, table)->value = strtoul(p, NULL, 0); + IPFW_SET_TVALUE_TYPE(&cmd->o, i); + + if (i == TVALUE_NH6) { + if (inet_pton(AF_INET6, p, &cmd->ip6) != 1) + errx(EX_USAGE, "invalid IPv6 address provided"); + /* mask in a dotted-quad notation */ + } else if (strchr(p, '.') != NULL) { + if (inet_aton(p, &cmd->ip4) != 1) + errx(EX_USAGE, "invalid IPv4 address provided"); + if (i == TVALUE_NH4) + return; + cmd->u32 = ntohl(cmd->u32); + } else + cmd->u32 = strtoul(p, NULL, 0); } void @@ -3352,9 +3424,11 @@ cmd->opcode = opcode; if (p) { - cmd->len |= F_INSN_SIZE(ipfw_insn_table); - fill_table_value(cmd, p); + cmd->len |= F_INSN_SIZE(ipfw_insn_lookup); + IPFW_SET_LOOKUP_MATCH_TVALUE(cmd, 1); + fill_table_value(insntod(cmd, lookup), p); } else { + /* table(NAME) */ IPFW_SET_LOOKUP_TYPE(cmd, LOOKUP_NONE); cmd->len |= F_INSN_SIZE(ipfw_insn_kidx); } @@ -4133,6 +4207,38 @@ return (ntohs(s->s_port)); } +static void +get_lookup_bitmask(int opcode, ipfw_insn_lookup *cmd, const char *src) +{ + struct ether_addr *mac; + const char *macset = "0123456789abcdefABCDEF:"; + + if (opcode == LOOKUP_SRC_IP6 || opcode == LOOKUP_DST_IP6) { + if (inet_pton(AF_INET6, src, &cmd->ip6) != 1) + errx(EX_USAGE, "invalid IPv6 mask provided"); + return; + } else if (opcode == LOOKUP_SRC_MAC || opcode == LOOKUP_DST_MAC) { + if (strspn(src, macset) != strlen(src) || + (mac = ether_aton(src)) == NULL) + errx(EX_DATAERR, "Incorrect MAC address"); + + bcopy(mac, cmd->mac, ETHER_ADDR_LEN); + return; + /* mask in a dotted-quad notation */ + } else if (strchr(src, '.') != NULL) { + if (inet_aton(src, &cmd->ip4) != 1) + errx(EX_USAGE, "invalid dotted-quad mask provided"); + switch (opcode) { + case LOOKUP_SRC_IP4: + case LOOKUP_DST_IP4: + return; + } + cmd->u32 = ntohl(cmd->u32); + return; + } + cmd->u32 = strtoul(src, NULL, 0); +} + /* * Parse arguments and assemble the microinstructions which make up a rule. * Rules are added into the 'rulebuf' and then copied in the correct order @@ -5469,13 +5575,13 @@ case TOK_LOOKUP: { /* optional mask for some LOOKUP types */ - ipfw_insn_table *c = insntod(cmd, table); + ipfw_insn_lookup *c = insntod(cmd, lookup); char *lkey; if (!av[0] || !av[1]) errx(EX_USAGE, "format: lookup argument tablenum"); - cmd->opcode = O_IP_DST_LOOKUP; + cmd->opcode = O_TABLE_LOOKUP; lkey = strsep(av, ":"); i = match_token(lookup_keys, lkey); @@ -5492,18 +5598,21 @@ case LOOKUP_DSCP: case LOOKUP_MARK: case LOOKUP_RULENUM: + case LOOKUP_SRC_MAC: + case LOOKUP_DST_MAC: + case LOOKUP_SRC_IP6: + case LOOKUP_DST_IP6: + case LOOKUP_SRC_IP4: + case LOOKUP_DST_IP4: break; default: errx(EX_USAGE, "masked lookup is not supported " "for %s", lkey); } - cmd->len |= F_INSN_SIZE(ipfw_insn_table); - c->value = strtoul(*av, NULL, 0); - if (c->value == 0) - errx(EX_USAGE, - "all-zeroes bitmask for lookup " - "is meaningless"); + cmd->len |= F_INSN_SIZE(ipfw_insn_lookup); + IPFW_SET_LOOKUP_MASKING(cmd, 1); + get_lookup_bitmask(i, c, *av); } else { cmd->len |= F_INSN_SIZE(ipfw_insn_kidx); } diff --git a/sbin/ipfw/tests/test_add_rule.py b/sbin/ipfw/tests/test_add_rule.py --- a/sbin/ipfw/tests/test_add_rule.py +++ b/sbin/ipfw/tests/test_add_rule.py @@ -37,7 +37,11 @@ from atf_python.sys.netpfil.ipfw.insns import InsnReject from atf_python.sys.netpfil.ipfw.insns import InsnTable from atf_python.sys.netpfil.ipfw.insns import InsnU32 +from atf_python.sys.netpfil.ipfw.insns import InsnKidx +from atf_python.sys.netpfil.ipfw.insns import InsnLookup from atf_python.sys.netpfil.ipfw.insns import IpFwOpcode +from atf_python.sys.netpfil.ipfw.insn_headers import IpFwTableLookupType +from atf_python.sys.netpfil.ipfw.insn_headers import IpFwTableValueType from atf_python.sys.netpfil.ipfw.ioctl import CTlv from atf_python.sys.netpfil.ipfw.ioctl import CTlvRule from atf_python.sys.netpfil.ipfw.ioctl import IpFwTlvType @@ -153,14 +157,226 @@ NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), ], "insns": [ - InsnU32(IpFwOpcode.O_IP_SRC_LOOKUP, u32=1), - InsnU32(IpFwOpcode.O_IP_DST_LOOKUP, u32=2), + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnKidx(IpFwOpcode.O_IP_DST_LOOKUP, kidx=2), InsnEmpty(IpFwOpcode.O_ACCEPT), ], }, }, id="test_tables", ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup dst-ip BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_DST_IP), + ), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_no_mask", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup mark:0xf00baa1 BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_MARK, bitmask=True), + bitmask=0xf00baa1), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_u32_mask", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup src-mac:1a:2b:3c:4d:5e:6f BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_SRC_MAC, bitmask=True), + bitmask="1a:2b:3c:4d:5e:6f"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_mac_mask", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup dst-ip4:1715004 BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_DST_IP4, bitmask=True), + bitmask="60.43.26.0"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_dst_ip4_numeric", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup src-ip4:0.0.0.255 BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_SRC_IP4, bitmask=True), + bitmask="0.0.0.255"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_src_ip4_addr", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup jail:0.0.252.128 BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_JAIL, bitmask=True), + bitmask=64640), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_jail_addr", + ), + pytest.param( + { + "in": "add allow ip from table(AAA) to any lookup dst-ip6:ffff:ffff:f00:baaa:b00c:: BBB", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"), + ], + "insns": [ + InsnKidx(IpFwOpcode.O_IP_SRC_LOOKUP, kidx=1), + InsnLookup(IpFwOpcode.O_TABLE_LOOKUP, + kidx=2, + arg1=InsnLookup.compile_arg1(lookup_type=IpFwTableLookupType.LOOKUP_DST_IP6, bitmask=True), + bitmask="ffff:ffff:f00:baaa:b00c::"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_lookup_dst_ip6", + ), + pytest.param( + { + "in": "add allow ip from table(AAA,16777215) to any", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + ], + "insns": [ + InsnLookup(IpFwOpcode.O_IP_SRC_LOOKUP, + kidx=1, + arg1=InsnLookup.compile_arg1(value_type=IpFwTableValueType.TVALUE_TAG), + value=16777215), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_check_value_legacy", + ), + pytest.param( + { + "in": "add allow ip from table(AAA,nat=1231) to any", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + ], + "insns": [ + InsnLookup(IpFwOpcode.O_IP_SRC_LOOKUP, + kidx=1, + arg1=InsnLookup.compile_arg1(value_type=IpFwTableValueType.TVALUE_NAT), + value=1231), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_check_value_nat", + ), + pytest.param( + { + "in": "add allow ip from table(AAA,nh4=10.20.30.40) to any", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + ], + "insns": [ + InsnLookup(IpFwOpcode.O_IP_SRC_LOOKUP, + kidx=1, + arg1=InsnLookup.compile_arg1(value_type=IpFwTableValueType.TVALUE_NH4), + value="10.20.30.40"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_check_value_nh4", + ), + pytest.param( + { + "in": "add allow ip from table(AAA,nh6=ff02:1234:b00c::abcd) to any", + "out": { + "objs": [ + NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"), + ], + "insns": [ + InsnLookup(IpFwOpcode.O_IP_SRC_LOOKUP, + kidx=1, + arg1=InsnLookup.compile_arg1(value_type=IpFwTableValueType.TVALUE_NH6), + value="ff02:1234:b00c::abcd"), + InsnEmpty(IpFwOpcode.O_ACCEPT), + ], + }, + }, + id="test_tables_check_value_nh6", + ), pytest.param( { "in": "add allow ip from any to 1.2.3.4 // test comment", @@ -183,7 +399,7 @@ ], "insns": [ InsnIp(IpFwOpcode.O_IP_DST, ip="1.2.3.4"), - InsnU32(IpFwOpcode.O_EXTERNAL_ACTION, u32=1), + InsnKidx(IpFwOpcode.O_EXTERNAL_ACTION, kidx=1), Insn(IpFwOpcode.O_EXTERNAL_DATA, arg1=123), ], }, @@ -192,20 +408,20 @@ ), pytest.param( { - "in": "add eaction ntpv6 AAA ip from any to 1.2.3.4", + "in": "add eaction nptv6 AAA ip from any to 1.2.3.4", "out": { "objs": [ - NTlv(IpFwTlvType.IPFW_TLV_EACTION, idx=1, name="ntpv6"), + NTlv(IpFwTlvType.IPFW_TLV_EACTION, idx=1, name="nptv6"), NTlv(0, idx=2, name="AAA"), ], "insns": [ InsnIp(IpFwOpcode.O_IP_DST, ip="1.2.3.4"), - InsnU32(IpFwOpcode.O_EXTERNAL_ACTION, u32=1), - InsnU32(IpFwOpcode.O_EXTERNAL_INSTANCE, u32=2), + InsnKidx(IpFwOpcode.O_EXTERNAL_ACTION, kidx=1), + InsnKidx(IpFwOpcode.O_EXTERNAL_INSTANCE, kidx=2), ], }, }, - id="test_eaction_ntp", + id="test_eaction_nptv6", ), pytest.param( { @@ -228,7 +444,7 @@ ], "insns": [ InsnComment(comment="test comment"), - InsnU32(IpFwOpcode.O_CHECK_STATE, u32=1), + InsnKidx(IpFwOpcode.O_CHECK_STATE, kidx=1), ], }, }, @@ -242,9 +458,9 @@ NTlv(IpFwTlvType.IPFW_TLV_STATE_NAME, idx=1, name="OUT"), ], "insns": [ - InsnU32(IpFwOpcode.O_PROBE_STATE, u32=1), + InsnKidx(IpFwOpcode.O_PROBE_STATE, kidx=1), Insn(IpFwOpcode.O_PROTO, arg1=6), - InsnU32(IpFwOpcode.O_KEEP_STATE, u32=1), + InsnKidx(IpFwOpcode.O_KEEP_STATE, kidx=1), InsnEmpty(IpFwOpcode.O_ACCEPT), ], }, @@ -260,7 +476,7 @@ ], "insns": [ Insn(IpFwOpcode.O_PROTO, arg1=6), - InsnU32(IpFwOpcode.O_KEEP_STATE, u32=1), + InsnKidx(IpFwOpcode.O_KEEP_STATE, kidx=1), InsnEmpty(IpFwOpcode.O_ACCEPT), ], }, @@ -371,7 +587,10 @@ ), pytest.param(("pipe 42", Insn(IpFwOpcode.O_PIPE, arg1=42)), id="pipe_42"), pytest.param( - ("skipto 42", InsnU32(IpFwOpcode.O_SKIPTO, u32=42)), id="skipto_42" + ("skipto 42", InsnU32(IpFwOpcode.O_SKIPTO, arg1=0, u32=42)), id="skipto_42" + ), + pytest.param( + ("skipto 4200", InsnU32(IpFwOpcode.O_SKIPTO, arg1=0, u32=4200)), id="skipto_4200" ), pytest.param( ("netgraph 42", Insn(IpFwOpcode.O_NETGRAPH, arg1=42)), id="netgraph_42" @@ -387,7 +606,7 @@ ), pytest.param(("tee 42", Insn(IpFwOpcode.O_TEE, arg1=42)), id="tee_42"), pytest.param( - ("call 420", InsnU32(IpFwOpcode.O_CALLRETURN, u32=420)), id="call_420" + ("call 420", InsnU32(IpFwOpcode.O_CALLRETURN, arg1=0, u32=420)), id="call_420" ), # TOK_FORWARD pytest.param( diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -235,7 +235,7 @@ O_REJECT = 47, /* arg1=icmp arg (same as deny) */ O_COUNT = 48, /* none */ O_SKIPTO = 49, /* v0:arg1=next rule number */ - /* v1:kidx= next rule number */ + /* v1:u32= next rule number */ O_PIPE = 50, /* arg1=pipe number */ O_QUEUE = 51, /* arg1=queue number */ O_DIVERT = 52, /* arg1=port number */ @@ -250,9 +250,11 @@ */ O_IPSEC = 58, /* has ipsec history */ O_IP_SRC_LOOKUP = 59, /* v0:arg1=table number, u32=value */ - /* v1:kidx=name, u32=value, arg1=key */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ O_IP_DST_LOOKUP = 60, /* arg1=table number, u32=value */ - /* v1:kidx=name, u32=value, arg1=key */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ O_ANTISPOOF = 61, /* none */ O_JAIL = 62, /* u32 = id */ O_ALTQ = 63, /* u32 = altq classif. qid */ @@ -288,14 +290,15 @@ O_SOCKARG = 84, /* socket argument */ O_CALLRETURN = 85, /* v0:arg1=called rule number */ - /* v1:kidx=called rule number */ + /* v1:u32=called rule number */ O_FORWARD_IP6 = 86, /* fwd sockaddr_in6 */ O_DSCP = 87, /* 2 u32 = DSCP mask */ O_SETDSCP = 88, /* arg1=DSCP value */ O_IP_FLOW_LOOKUP = 89, /* v0:arg1=table number, u32=value */ - /* v1:kidx=name, u32=value */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ O_EXTERNAL_ACTION = 90, /* v0:arg1=id of external action handler */ /* v1:kidx=id of external action handler */ @@ -306,12 +309,20 @@ O_SKIP_ACTION = 93, /* none */ O_TCPMSS = 94, /* arg1=MSS value */ - O_MAC_SRC_LOOKUP = 95, /* kidx=name, u32=value, arg1=key */ - O_MAC_DST_LOOKUP = 96, /* kidx=name, u32=value, arg1=key */ + O_MAC_SRC_LOOKUP = 95, /* kidx=name */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ + O_MAC_DST_LOOKUP = 96, /* kidx=name */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ O_SETMARK = 97, /* u32 = value */ O_MARK = 98, /* 2 u32 = value, bitmask */ + O_TABLE_LOOKUP = 99, /* kidx=name */ + /* v1:lookup: kidx=name, arg1=key and */ + /* flags, bitmask */ + O_LAST_OPCODE /* not an opcode! */ }; @@ -409,12 +420,32 @@ uint32_t value; /* table value */ } ipfw_insn_table; -#define IPFW_LOOKUP_TYPE_MASK 0x00FF +typedef struct _ipfw_insn_lookup { + ipfw_insn o; /* arg1 is flags and lookup key */ + uint32_t kidx; /* table name index */ + union { /* lookup mask */ + uint64_t __mask64[2]; + struct in6_addr ip6; + struct in_addr ip4; + char mac[6]; + uint32_t u32; + }; +} ipfw_insn_lookup; + +#define IPFW_LOOKUP_TYPE_MASK 0x007F #define IPFW_LOOKUP_TYPE(insn) ((insn)->arg1 & IPFW_LOOKUP_TYPE_MASK) #define IPFW_SET_LOOKUP_TYPE(insn, type) do { \ (insn)->arg1 &= ~IPFW_LOOKUP_TYPE_MASK; \ (insn)->arg1 |= (type) & IPFW_LOOKUP_TYPE_MASK; \ } while (0) +#define IPFW_LOOKUP_F_MASKING 0x0080 +#define IPFW_LOOKUP_MASKING(insn) ((insn)->arg1 & IPFW_LOOKUP_F_MASKING) +#define IPFW_SET_LOOKUP_MASKING(insn, value) do { \ + if (value) \ + (insn)->arg1 |= IPFW_LOOKUP_F_MASKING; \ + else \ + (insn)->arg1 &= ~IPFW_LOOKUP_F_MASKING; \ +} while (0) /* * Defines key types used by lookup instruction @@ -432,6 +463,10 @@ LOOKUP_SRC_MAC, LOOKUP_MARK, LOOKUP_RULENUM, + LOOKUP_DST_IP4, + LOOKUP_SRC_IP4, + LOOKUP_DST_IP6, + LOOKUP_SRC_IP6, }; enum ipfw_return_type { @@ -941,12 +976,20 @@ } a; }; -#define IPFW_TVALUE_TYPE_MASK 0xFF00 +#define IPFW_TVALUE_TYPE_MASK 0x7F00 #define IPFW_TVALUE_TYPE(insn) (((insn)->arg1 & IPFW_TVALUE_TYPE_MASK) >> 8) #define IPFW_SET_TVALUE_TYPE(insn, type) do { \ (insn)->arg1 &= ~IPFW_TVALUE_TYPE_MASK; \ (insn)->arg1 |= ((type) << 8) & IPFW_TVALUE_TYPE_MASK; \ } while (0) +#define IPFW_LOOKUP_F_MATCH_TVALUE 0x8000 +#define IPFW_LOOKUP_MATCH_TVALUE(insn) ((insn)->arg1 & IPFW_LOOKUP_F_MATCH_TVALUE) +#define IPFW_SET_LOOKUP_MATCH_TVALUE(insn, value) do { \ + if (value) \ + (insn)->arg1 |= IPFW_LOOKUP_F_MATCH_TVALUE; \ + else \ + (insn)->arg1 &= ~IPFW_LOOKUP_F_MATCH_TVALUE; \ +} while (0) enum ipfw_table_value_type { TVALUE_TAG = 0, @@ -960,6 +1003,7 @@ TVALUE_DSCP, TVALUE_LIMIT, TVALUE_MARK, + TVALUE_NH6, }; /* 64-byte structure representing multi-field table value */ diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -1317,7 +1317,7 @@ #define TARG(k, f) IP_FW_ARG_TABLEARG(chain, k, f) static inline int -tvalue_match(struct ip_fw_chain *ch, const ipfw_insn_table *cmd, +tvalue_match(struct ip_fw_chain *ch, const ipfw_insn_lookup *cmd, uint32_t tablearg) { uint32_t tvalue; @@ -1344,6 +1344,11 @@ case TVALUE_NH4: tvalue = TARG_VAL(ch, tablearg, nh4); break; + case TVALUE_NH6: + if (F_LEN(&cmd->o) != F_INSN_SIZE(ipfw_insn_lookup)) + return (0); + return (0 == memcmp(&TARG_VAL(ch, tablearg, nh6), + &cmd->ip6, sizeof(struct in6_addr))); case TVALUE_DSCP: tvalue = TARG_VAL(ch, tablearg, dscp); break; @@ -1358,7 +1363,13 @@ tvalue = TARG_VAL(ch, tablearg, tag); break; } - return (tvalue == cmd->value); + /* + * D53694 compatibility layer, to be removed. + * Match u32 values specified as ipfw_insn_table structure. + */ + if (F_LEN(&cmd->o) == F_INSN_SIZE(ipfw_insn_table)) + return (tvalue == insntoc(cmd, table)->value); + return (tvalue == cmd->u32); } /* @@ -2102,45 +2113,101 @@ break; case O_IP_DST_LOOKUP: - if (IPFW_LOOKUP_TYPE(cmd) != LOOKUP_NONE) { - void *pkey = NULL; - uint32_t key, vidx; - uint16_t keylen = 0; /* zero if can't match the packet */ - uint8_t lookup_type; + case O_IP_SRC_LOOKUP: + /* + * D53694 compatibility layer, to be removed. + * The following if and subsequent fallthrough + * are here for backward opcode compatibility + * used for lookup opcode until O_TABLE_LOOKUP + * appeared. + */ + if (IPFW_LOOKUP_TYPE(cmd) == LOOKUP_NONE) { + void *pkey; + uint32_t vidx; + uint16_t keylen; + + if (is_ipv4) { + keylen = sizeof(in_addr_t); + if (cmd->opcode == O_IP_DST_LOOKUP) + pkey = &dst_ip; + else + pkey = &src_ip; + } else if (is_ipv6) { + keylen = sizeof(struct in6_addr); + if (cmd->opcode == O_IP_DST_LOOKUP) + pkey = &args->f_id.dst_ip6; + else + pkey = &args->f_id.src_ip6; + } else + break; + match = ipfw_lookup_table(chain, + insntod(cmd, kidx)->kidx, + keylen, pkey, &vidx); + if (!match) + break; + if (IPFW_LOOKUP_MATCH_TVALUE(cmd) != 0) { + match = tvalue_match(chain, + insntod(cmd, lookup), vidx); + if (!match) + break; + } + tablearg = vidx; + break; + } + /* FALLTHROUGH */ + case O_TABLE_LOOKUP: + { + ipfw_insn_lookup key; + uint32_t vidx; + uint16_t keylen = 0; /* zero if can't match the packet */ + uint8_t lookup_type; lookup_type = IPFW_LOOKUP_TYPE(cmd); switch (lookup_type) { case LOOKUP_DST_IP: + case LOOKUP_DST_IP4: + if (is_ipv4) { + keylen = sizeof(in_addr_t); + key.ip4 = dst_ip; + break; + } + if (lookup_type == LOOKUP_DST_IP4) + break; + /* FALLTHOUGH */ + case LOOKUP_DST_IP6: + if (is_ipv6 == 0) + break; + keylen = sizeof(struct in6_addr); + key.ip6 = args->f_id.dst_ip6; + break; case LOOKUP_SRC_IP: + case LOOKUP_SRC_IP4: if (is_ipv4) { keylen = sizeof(in_addr_t); - if (lookup_type == LOOKUP_DST_IP) - pkey = &dst_ip; - else - pkey = &src_ip; - } else if (is_ipv6) { - keylen = sizeof(struct in6_addr); - if (lookup_type == LOOKUP_DST_IP) - pkey = &args->f_id.dst_ip6; - else - pkey = &args->f_id.src_ip6; + key.ip4 = src_ip; + break; } + if (lookup_type == LOOKUP_SRC_IP4) + break; + /* FALLTHOUGH */ + case LOOKUP_SRC_IP6: + if (is_ipv6 == 0) + break; + keylen = sizeof(struct in6_addr); + key.ip6 = args->f_id.src_ip6; break; case LOOKUP_DSCP: if (is_ipv4) - key = ip->ip_tos >> 2; + key.u32 = ip->ip_tos >> 2; else if (is_ipv6) - key = IPV6_DSCP( + key.u32 = IPV6_DSCP( (struct ip6_hdr *)ip) >> 2; else break; /* only for L3 */ - key &= 0x3f; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) - key &= insntod(cmd, table)->value; - pkey = &key; - keylen = sizeof(key); + keylen = sizeof(key.u32); + key.u32 &= 0x3f; break; case LOOKUP_DST_PORT: case LOOKUP_SRC_PORT: @@ -2159,23 +2226,26 @@ proto != IPPROTO_SCTP) break; if (lookup_type == LOOKUP_DST_PORT) - key = dst_port; + key.u32 = dst_port; else - key = src_port; - pkey = &key; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) - key &= insntod(cmd, table)->value; - keylen = sizeof(key); + key.u32 = src_port; + keylen = sizeof(key.u32); break; case LOOKUP_DST_MAC: + /* only for L2 */ + if ((args->flags & IPFW_ARGS_ETHER) == 0) + break; + keylen = ETHER_ADDR_LEN; + memcpy(key.mac, eh->ether_dhost, + sizeof(key.mac)); + break; case LOOKUP_SRC_MAC: /* only for L2 */ if ((args->flags & IPFW_ARGS_ETHER) == 0) break; - - pkey = lookup_type == LOOKUP_DST_MAC ? - eh->ether_dhost : eh->ether_shost; keylen = ETHER_ADDR_LEN; + memcpy(key.mac, eh->ether_shost, + sizeof(key.mac)); break; #ifndef USERSPACE case LOOKUP_UID: @@ -2185,84 +2255,61 @@ #ifdef __FreeBSD__ &ucred_cache); if (lookup_type == LOOKUP_UID) - key = ucred_cache->cr_uid; + key.u32 = ucred_cache->cr_uid; else if (lookup_type == LOOKUP_JAIL) - key = ucred_cache->cr_prison->pr_id; + key.u32 = ucred_cache->cr_prison->pr_id; #else /* !__FreeBSD__ */ (void *)&ucred_cache); if (lookup_type == LOOKUP_UID) - key = ucred_cache.uid; + key.u32 = ucred_cache.uid; else if (lookup_type == LOOKUP_JAIL) - key = ucred_cache.xid; + key.u32 = ucred_cache.xid; #endif /* !__FreeBSD__ */ - pkey = &key; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) - key &= insntod(cmd, table)->value; - keylen = sizeof(key); + keylen = sizeof(key.u32); break; #endif /* !USERSPACE */ case LOOKUP_MARK: - key = args->rule.pkt_mark; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) - key &= insntod(cmd, table)->value; - pkey = &key; - keylen = sizeof(key); + key.u32 = args->rule.pkt_mark; + keylen = sizeof(key.u32); break; case LOOKUP_RULENUM: - key = f->rulenum; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) - key &= insntod(cmd, table)->value; - pkey = &key; - keylen = sizeof(key); + key.u32 = f->rulenum; + keylen = sizeof(key.u32); break; } /* unknown key type */ if (keylen == 0) break; - match = ipfw_lookup_table(chain, - insntod(cmd, kidx)->kidx, keylen, - pkey, &vidx); - if (match) - tablearg = vidx; - break; - } - /* LOOKUP_NONE */ - /* FALLTHROUGH */ - case O_IP_SRC_LOOKUP: - { - void *pkey; - uint32_t vidx; - uint16_t keylen; + if (IPFW_LOOKUP_MASKING(cmd) == 0) { + /* no masking needed */ + } else if (cmdlen == + F_INSN_SIZE(ipfw_insn_table)) { + /* + * D53694 compatibility layer, + * to be removed. + */ + key.u32 &= insntod(cmd, table)->value; + } else { + key.__mask64[0] &= + insntod(cmd, lookup)->__mask64[0]; + key.__mask64[1] &= + insntod(cmd, lookup)->__mask64[1]; + } - if (is_ipv4) { - keylen = sizeof(in_addr_t); - if (cmd->opcode == O_IP_DST_LOOKUP) - pkey = &dst_ip; - else - pkey = &src_ip; - } else if (is_ipv6) { - keylen = sizeof(struct in6_addr); - if (cmd->opcode == O_IP_DST_LOOKUP) - pkey = &args->f_id.dst_ip6; - else - pkey = &args->f_id.src_ip6; - } else - break; match = ipfw_lookup_table(chain, - insntod(cmd, kidx)->kidx, - keylen, pkey, &vidx); + insntod(cmd, kidx)->kidx, keylen, + key.__mask64, &vidx); + if (!match) break; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) { - match = tvalue_match(chain, - insntod(cmd, table), vidx); - if (!match) - break; - } + /* + * XXX should we support check for value + * simultaneously with masked lookup? + */ tablearg = vidx; break; - } + } /* O_TABLE_LOOKUP */ case O_MAC_SRC_LOOKUP: case O_MAC_DST_LOOKUP: @@ -2285,9 +2332,9 @@ keylen, pkey, &vidx); if (!match) break; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) { + if (IPFW_LOOKUP_MATCH_TVALUE(cmd) != 0) { match = tvalue_match(chain, - insntod(cmd, table), vidx); + insntod(cmd, lookup), vidx); if (!match) break; } @@ -2304,9 +2351,9 @@ &args->f_id, &vidx); if (!match) break; - if (cmdlen == F_INSN_SIZE(ipfw_insn_table)) + if (IPFW_LOOKUP_MATCH_TVALUE(cmd) != 0) match = tvalue_match(chain, - insntod(cmd, table), vidx); + insntod(cmd, lookup), vidx); if (match) tablearg = vidx; break; diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -1469,6 +1469,7 @@ case O_MAC_SRC_LOOKUP: case O_MAC_DST_LOOKUP: if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx) && + cmdlen != F_INSN_SIZE(ipfw_insn_lookup) && cmdlen != F_INSN_SIZE(ipfw_insn_table)) return (BAD_SIZE); if (insntod(cmd, kidx)->kidx >= V_fw_tables_max) { @@ -1477,6 +1478,37 @@ return (FAILED); } ci->object_opcodes++; + /* + * D53694 compatibility layer, to be removed. + + * ipfw_insn_table was used to match table value for u32 + * values and cmdlen was used to detect such intention. + * A special flag is now used for that in module so + * adopt legacy sbin/ipfw behavior and set it for all + * lookup instructions with ipfw_insn_table opcode. + * + * Lookup type different from LOOKUP_NONE was used for + * 32-bit bitmasking prior to lookup. + * Table value matching was expected otherwise. + */ + if (cmdlen != F_INSN_SIZE(ipfw_insn_table)) + break; + + if (IPFW_LOOKUP_TYPE(cmd) != LOOKUP_NONE) + IPFW_SET_LOOKUP_MASKING(cmd, 1); + else + IPFW_SET_LOOKUP_MATCH_TVALUE(cmd, 1); + break; + case O_TABLE_LOOKUP: + if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx) && + cmdlen != F_INSN_SIZE(ipfw_insn_lookup)) + return (BAD_SIZE); + if (insntod(cmd, kidx)->kidx >= V_fw_tables_max) { + printf("ipfw: invalid table index %u\n", + insntod(cmd, kidx)->kidx); + return (FAILED); + } + ci->object_opcodes++; break; case O_MACADDR2: if (cmdlen != F_INSN_SIZE(ipfw_insn_mac)) diff --git a/sys/netpfil/ipfw/ip_fw_table.c b/sys/netpfil/ipfw/ip_fw_table.c --- a/sys/netpfil/ipfw/ip_fw_table.c +++ b/sys/netpfil/ipfw/ip_fw_table.c @@ -2422,14 +2422,18 @@ static int classify_srcdst(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype) { - ipfw_insn_table *cmd; + ipfw_insn_lookup *cmd; /* Basic IPv4/IPv6 or u32 lookups */ - cmd = insntod(cmd0, table); + cmd = insntod(cmd0, lookup); *puidx = cmd->kidx; - switch(cmd0->arg1) { + switch(IPFW_LOOKUP_TYPE(cmd0)) { case LOOKUP_DST_IP: case LOOKUP_SRC_IP: + case LOOKUP_DST_IP4: + case LOOKUP_SRC_IP4: + case LOOKUP_DST_IP6: + case LOOKUP_SRC_IP6: default: /* IPv4 src/dst */ *ptype = IPFW_TABLE_ADDR; @@ -2469,7 +2473,7 @@ static int classify_flow(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype) { - *puidx = insntod(cmd0, table)->kidx; + *puidx = insntod(cmd0, kidx)->kidx; *ptype = IPFW_TABLE_FLOW; return (0); } @@ -2477,7 +2481,7 @@ static int classify_mac_lookup(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype) { - *puidx = insntod(cmd0, table)->kidx; + *puidx = insntod(cmd0, kidx)->kidx; *ptype = IPFW_TABLE_MAC; return (0); } @@ -2485,7 +2489,7 @@ static void update_kidx(ipfw_insn *cmd0, uint32_t idx) { - insntod(cmd0, table)->kidx = idx; + insntod(cmd0, kidx)->kidx = idx; } static void @@ -2618,6 +2622,16 @@ .create_object = create_table_compat, .manage_sets = table_manage_sets, }, + { + .opcode = O_TABLE_LOOKUP, + .etlv = IPFW_TLV_TBL_NAME, + .classifier = classify_srcdst, + .update = update_kidx, + .find_byname = table_findbyname, + .find_bykidx = table_findbykidx, + .create_object = create_table_compat, + .manage_sets = table_manage_sets, + }, { .opcode = O_IP_FLOW_LOOKUP, .etlv = IPFW_TLV_TBL_NAME, diff --git a/tests/atf_python/sys/netpfil/ipfw/insn_headers.py b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py --- a/tests/atf_python/sys/netpfil/ipfw/insn_headers.py +++ b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py @@ -101,7 +101,8 @@ O_MAC_DST_LOOKUP = 96 O_SETMARK = 97 O_MARK = 98 - O_LAST_OPCODE = 99 + O_TABLE_LOOKUP = 99 + O_LAST_OPCODE = 100 class Op3CmdType(Enum): @@ -196,3 +197,37 @@ ICMP6_DST_UNREACH_SRCROUTE = 7 ICMP6_UNREACH_RST = 256 ICMP6_UNREACH_ABORT = 257 + + +class IpFwTableLookupType(Enum): + LOOKUP_NONE = 0 + LOOKUP_DST_IP = 1 + LOOKUP_SRC_IP = 2 + LOOKUP_DST_PORT = 3 + LOOKUP_SRC_PORT = 4 + LOOKUP_UID = 5 + LOOKUP_JAIL = 6 + LOOKUP_DSCP = 7 + LOOKUP_DST_MAC = 8 + LOOKUP_SRC_MAC = 9 + LOOKUP_MARK = 10 + LOOKUP_RULENUM = 11 + LOOKUP_DST_IP4 = 12 + LOOKUP_SRC_IP4 = 13 + LOOKUP_DST_IP6 = 14 + LOOKUP_SRC_IP6 = 15 + + +class IpFwTableValueType(Enum): + TVALUE_TAG = 0 + TVALUE_PIPE = 1 + TVALUE_DIVERT = 2 + TVALUE_SKIPTO = 3 + TVALUE_NETGRAPH = 4 + TVALUE_FIB = 5 + TVALUE_NAT = 6 + TVALUE_NH4 = 7 + TVALUE_DSCP = 8 + TVALUE_LIMIT = 9 + TVALUE_MARK = 10 + TVALUE_NH6 = 11 diff --git a/tests/atf_python/sys/netpfil/ipfw/insns.py b/tests/atf_python/sys/netpfil/ipfw/insns.py --- a/tests/atf_python/sys/netpfil/ipfw/insns.py +++ b/tests/atf_python/sys/netpfil/ipfw/insns.py @@ -25,6 +25,8 @@ from atf_python.sys.netpfil.ipfw.insn_headers import IpFwOpcode from atf_python.sys.netpfil.ipfw.insn_headers import IcmpRejectCode from atf_python.sys.netpfil.ipfw.insn_headers import Icmp6RejectCode +from atf_python.sys.netpfil.ipfw.insn_headers import IpFwTableLookupType +from atf_python.sys.netpfil.ipfw.insn_headers import IpFwTableValueType from atf_python.sys.netpfil.ipfw.utils import AttrDescr from atf_python.sys.netpfil.ipfw.utils import enum_or_int from atf_python.sys.netpfil.ipfw.utils import enum_from_int @@ -165,7 +167,7 @@ hdr = IpFwInsn.from_buffer_copy(data[off : off + sizeof(IpFwInsn)]) insn_len = (hdr.length & 0x3F) * 4 if off + insn_len > len(data): - raise ValueError("wrng length") + raise ValueError("wrong length") # print("GET insn type {} len {}".format(hdr.opcode, insn_len)) attr = attr_map.get(hdr.opcode, None) if attr is None: @@ -309,6 +311,22 @@ return " arg1={} u32={}".format(self.arg1, self.u32) +class InsnKidx(InsnU32): + def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0): + super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, u32=kidx) + + @property + def kidx(self): + return self.u32 + + @kidx.setter + def kidx(self, kidx): + self.u32 = kidx + + def _print_obj_value(self): + return " arg1={}, kidx={}".format(self.arg1, self.kidx) + + class InsnProb(InsnU32): def __init__( self, @@ -353,18 +371,23 @@ return " ip={}".format(self.ip) -class InsnTable(Insn): +class InsnTable(InsnKidx): + def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0, value=None): + super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, kidx=kidx) + self.val = value + @classmethod def _validate(cls, data): - cls._validate_len(data, [4, 8]) + cls._validate_len(data, [8, 12]) @classmethod def _parse(cls, data): self = super()._parse(data) - if len(data) == 8: - (self.val,) = struct.unpack("@I", data[4:8]) - self.ilen = 2 + (self.kidx,) = struct.unpack("@I", data[4:8]) + if len(data) == 12: + (self.val,) = struct.unpack("@I", data[8:12]) + self.ilen = 3 else: self.val = None return self @@ -377,9 +400,128 @@ def _print_obj_value(self): if getattr(self, "val", None) is not None: - return " table={} value={}".format(self.arg1, self.val) + return " table={} value={}".format(self.kidx, self.val) else: - return " table={}".format(self.arg1) + return " table={}".format(self.kidx) + + +class InsnLookup(InsnKidx): + def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0, value=None, bitmask=None): + super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, kidx=kidx) + self.val = value + self.bm = bitmask + self.decompile_arg1() + + @classmethod + def _validate(cls, data): + cls._validate_len(data, [8, 24]) + + @classmethod + def _parse(cls, data): + self = super()._parse(data) + + if len(data) == 24: + self.decompile_arg1() + self.ilen = 8 + if self.do_masking != 0: + if self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP4, IpFwTableLookupType.LOOKUP_SRC_IP4]: + self.bm = socket.inet_ntop(socket.AF_INET, data[8:12]) + elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP6, IpFwTableLookupType.LOOKUP_SRC_IP6]: + self.bm = socket.inet_ntop(socket.AF_INET6, data[8:24]) + elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_MAC, IpFwTableLookupType.LOOKUP_SRC_MAC]: + self.bm = ':'.join(f'{b:02x}' for b in struct.unpack('!6B', data[8:14])) + elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP, IpFwTableLookupType.LOOKUP_SRC_IP]: + raise ValueError(f"maksing LOOKUP_SRC/DST_IP is invalid") + else: + (self.bm,) = struct.unpack("@I", data[8:12]) + elif self.do_matching != 0: + match self.tvalue_type: + case IpFwTableValueType.TVALUE_NH4: + self.val = socket.inet_ntop(socket.AF_INET, data[8:12]) + case IpFwTableValueType.TVALUE_NH6: + self.val = socket.inet_ntop(socket.AF_INET6, data[8:24]) + case _: + (self.val,) = struct.unpack("@I", data[8:12]) + else: + raise ValueError(f"insn_lookup is used but neither masking or matching are set") + + return self + + @classmethod + def compile_arg1(cls, value_type=None, value=None, lookup_type=None, bitmask=None): + arg1 = 0 + if value_type is not None: + arg1 |= value_type.value << 8 + arg1 |= 0x8000 + if lookup_type is not None: + arg1 |= lookup_type.value + if bitmask is not None: + arg1 |= 0x0080 + return arg1 + + + def decompile_arg1(self): + self.do_masking = ((self.arg1 & 0x0080) != 0) + self.do_matching = ((self.arg1 & 0x8000) != 0) + self.lookup_int_type = self.arg1 & 0x007F + self.tvalue_int_type = (self.arg1 & 0x7F00) >> 8 + self.lookup_type = enum_from_int(IpFwTableLookupType, self.lookup_int_type) + self.tvalue_type = enum_from_int(IpFwTableValueType, self.tvalue_int_type) + + if self.do_matching != 0 and self.do_masking != 0: + raise ValueError(f"masking and matching can not be combined") + elif self.do_matching == 0 and self.do_masking == 0: + return + self.ilen = 8 + + def _validate_arg1(self, value, bitmask): + if self.do_matching == 0 and value is not None: + raise ValueError(f"matching bit is NOT set yet value is set to {value}") + elif self.do_matching != 0 and value is None: + raise ValueError("matching bit is set yet value is None") + elif self.do_matching != 0 and self.tvalue_type is None: + raise ValueError(f"matching bit is set but unknown table value ({self.tvalue_type}) is set") + + if self.do_masking == 0 and bitmask is not None: + raise ValueError(f"masking bit is NOT set yet bitmask is set to {bitmask}") + elif self.do_masking != 0 and bitmask is None: + raise ValueError("masking bit is set yet bitmask is None") + elif self.do_masking != 0 and self.lookup_type is None: + raise ValueError(f"masking bit is set but unknown lookup type ({self.lookup_int_type}) is set") + elif self.do_masking != 0 and self.lookup_type == IpFwTableLookupType.LOOKUP_NONE: + raise ValueError(f"masking bit is set but LOOKUP_NONE lookup type is set") + + def __bytes__(self): + ret = super().__bytes__() + if self.do_masking != 0: + if (isinstance(self.bm, str) and + self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP4, IpFwTableLookupType.LOOKUP_SRC_IP4]): + ret += socket.inet_pton(socket.AF_INET, self.bm) + elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP6, IpFwTableLookupType.LOOKUP_SRC_IP6]: + ret += socket.inet_pton(socket.AF_INET6, self.bm) + elif self.lookup_type in [IpFwTableLookupType.LOOKUP_SRC_MAC, IpFwTableLookupType.LOOKUP_SRC_MAC]: + ret += struct.pack('!6B10x', *(int(b, 16) for b in self.bm.split(':'))) + else: + ret += struct.pack("@I12x", self.bm) + elif self.do_matching != 0: + match self.tvalue_type: + case IpFwTableValueType.TVALUE_NH4: + ret += socket.inet_pton(socket.AF_INET, self.val) + case IpFwTableValueType.TVALUE_NH6: + ret += socket.inet_pton(socket.AF_INET6, self.val) + case _: + ret += struct.pack("@I12x", self.val) + return ret + + def _print_obj_value(self): + if self.do_matching != 0: + return " arg1={}, lookup_type={}, tvalue_type={}, kidx={}, value={}".format( + self.arg1, self.lookup_type, self.tvalue_type, self.kidx, self.val) + elif self.do_masking != 0: + return " arg1={}, lookup_type{}, tvalue_type={}, kidx={}, bitmask={}".format( + self.arg1, self.lookup_type, self.tvalue_type, self.kidx, self.bm) + else: + return " arg1={}, kidx={}".format(self.arg1, self.kidx) class InsnReject(Insn): @@ -510,7 +652,7 @@ insn_attrs = prepare_attrs_map( [ - AttrDescr(IpFwOpcode.O_CHECK_STATE, InsnU32), + AttrDescr(IpFwOpcode.O_CHECK_STATE, InsnKidx), AttrDescr(IpFwOpcode.O_ACCEPT, InsnEmpty), AttrDescr(IpFwOpcode.O_COUNT, InsnEmpty), @@ -530,10 +672,9 @@ AttrDescr(IpFwOpcode.O_SETFIB, Insn), AttrDescr(IpFwOpcode.O_SETDSCP, Insn), AttrDescr(IpFwOpcode.O_REASS, InsnEmpty), - AttrDescr(IpFwOpcode.O_SETMARK, InsnU32), - - AttrDescr(IpFwOpcode.O_EXTERNAL_ACTION, InsnU32), - AttrDescr(IpFwOpcode.O_EXTERNAL_INSTANCE, InsnU32), + AttrDescr(IpFwOpcode.O_SETMARK, Insn), + AttrDescr(IpFwOpcode.O_EXTERNAL_ACTION, InsnKidx), + AttrDescr(IpFwOpcode.O_EXTERNAL_INSTANCE, InsnKidx), @@ -548,11 +689,12 @@ AttrDescr(IpFwOpcode.O_IP_DST, InsnIp), AttrDescr(IpFwOpcode.O_IP6_DST, InsnIp6), AttrDescr(IpFwOpcode.O_IP6_SRC, InsnIp6), - AttrDescr(IpFwOpcode.O_IP_SRC_LOOKUP, InsnU32), - AttrDescr(IpFwOpcode.O_IP_DST_LOOKUP, InsnU32), + AttrDescr(IpFwOpcode.O_IP_SRC_LOOKUP, InsnLookup), + AttrDescr(IpFwOpcode.O_IP_DST_LOOKUP, InsnLookup), AttrDescr(IpFwOpcode.O_IP_SRCPORT, InsnPorts), AttrDescr(IpFwOpcode.O_IP_DSTPORT, InsnPorts), - AttrDescr(IpFwOpcode.O_PROBE_STATE, InsnU32), - AttrDescr(IpFwOpcode.O_KEEP_STATE, InsnU32), + AttrDescr(IpFwOpcode.O_PROBE_STATE, InsnKidx), + AttrDescr(IpFwOpcode.O_KEEP_STATE, InsnKidx), + AttrDescr(IpFwOpcode.O_TABLE_LOOKUP, InsnLookup), ] ) diff --git a/tests/atf_python/sys/netpfil/ipfw/ioctl.py b/tests/atf_python/sys/netpfil/ipfw/ioctl.py --- a/tests/atf_python/sys/netpfil/ipfw/ioctl.py +++ b/tests/atf_python/sys/netpfil/ipfw/ioctl.py @@ -26,6 +26,7 @@ from atf_python.sys.netpfil.ipfw.insns import BaseInsn from atf_python.sys.netpfil.ipfw.insns import insn_attrs from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTableLookupType +from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTableValueType from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTlvType from atf_python.sys.netpfil.ipfw.ioctl_headers import Op3CmdType from atf_python.sys.netpfil.ipfw.utils import align8 diff --git a/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py b/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py --- a/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py +++ b/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py @@ -61,16 +61,37 @@ class IpFwTableLookupType(Enum): - LOOKUP_DST_IP = 0 - LOOKUP_SRC_IP = 1 - LOOKUP_DST_PORT = 2 - LOOKUP_SRC_PORT = 3 - LOOKUP_UID = 4 - LOOKUP_JAIL = 5 - LOOKUP_DSCP = 6 - LOOKUP_DST_MAC = 7 - LOOKUP_SRC_MAC = 8 - LOOKUP_MARK = 9 + LOOKUP_NONE = 0 + LOOKUP_DST_IP = 1 + LOOKUP_SRC_IP = 2 + LOOKUP_DST_PORT = 3 + LOOKUP_SRC_PORT = 4 + LOOKUP_UID = 5 + LOOKUP_JAIL = 6 + LOOKUP_DSCP = 7 + LOOKUP_DST_MAC = 8 + LOOKUP_SRC_MAC = 9 + LOOKUP_MARK = 10 + LOOKUP_RULENUM = 11 + LOOKUP_DST_IP4 = 12 + LOOKUP_SRC_IP4 = 13 + LOOKUP_DST_IP6 = 14 + LOOKUP_SRC_IP6 = 15 + + +class IpFwTableValueType(Enum): + TVALUE_TAG = 0 + TVALUE_PIPE = 1 + TVALUE_DIVERT = 2 + TVALUE_SKIPTO = 3 + TVALUE_NETGRAPH = 4 + TVALUE_FIB = 5 + TVALUE_NAT = 6 + TVALUE_NH4 = 8 + TVALUE_DSCP = 9 + TVALUE_LIMIT = 10 + TVALUE_MARK = 11 + TVALUE_NH6 = 12 class IpFwTlvType(Enum):