diff --git a/CHANGES b/CHANGES index aec6862400..5ffeff242a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,9 @@ +2.1-86 | 2012-10-24 15:37:11 -0700 + + * Add parsing rules for IPv4/IPv6 subnet literal constants. + Addresses #888. (Jon Siwek) + 2.1-84 | 2012-10-19 15:12:56 -0700 * Added a BiF strptime() to wrap the corresponding C function. (Seth diff --git a/VERSION b/VERSION index c69ab9646c..8892e94465 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1-84 +2.1-86 diff --git a/src/IPAddr.cc b/src/IPAddr.cc index 0ba5589fff..51fb37c4d5 100644 --- a/src/IPAddr.cc +++ b/src/IPAddr.cc @@ -248,10 +248,10 @@ IPPrefix::IPPrefix(const in6_addr& in6, uint8_t length) prefix.Mask(this->length); } -IPPrefix::IPPrefix(const IPAddr& addr, uint8_t length) +IPPrefix::IPPrefix(const IPAddr& addr, uint8_t length, bool len_is_v6_relative) : prefix(addr) { - if ( prefix.GetFamily() == IPv4 ) + if ( prefix.GetFamily() == IPv4 && ! len_is_v6_relative ) { if ( length > 32 ) reporter->InternalError("Bad IPAddr(v4) IPPrefix length : %d", diff --git a/src/IPAddr.h b/src/IPAddr.h index 6d26ef3fa8..5ddee70fb8 100644 --- a/src/IPAddr.h +++ b/src/IPAddr.h @@ -496,8 +496,15 @@ public: * @param addr The IP address. * * @param length The prefix length in the range from 0 to 128 + * + * @param len_is_v6_relative Whether \a length is relative to the full + * 128 bits of an IPv6 address. If false and \a addr is an IPv4 + * address, then \a length is expected to range from 0 to 32. If true + * \a length is expected to range from 0 to 128 even if \a addr is IPv4, + * meaning that the mask is to apply to the IPv4-mapped-IPv6 representation. */ - IPPrefix(const IPAddr& addr, uint8_t length); + IPPrefix(const IPAddr& addr, uint8_t length, + bool len_is_v6_relative = false); /** * Copy constructor. diff --git a/src/rule-scan.l b/src/rule-scan.l index d516a98e89..9c755d04e3 100644 --- a/src/rule-scan.l +++ b/src/rule-scan.l @@ -11,16 +11,6 @@ #include "rule-parse.h" int rules_line_number = 0; - -static string extract_ipv6(string s) - { - if ( s.substr(0, 3) == "[0x" ) - s = s.substr(3, s.find("]") - 3); - else - s = s.substr(1, s.find("]") - 1); - - return s; - } %} %x PS @@ -49,15 +39,14 @@ PID ([0-9a-zA-Z_-]|"::")+ } {IP6} { - rules_lval.prefixval = new IPPrefix(IPAddr(extract_ipv6(yytext)), 128); + rules_lval.prefixval = new IPPrefix(IPAddr(extract_ip(yytext)), 128, true); return TOK_IP6; } {IP6}{OWS}"/"{OWS}{D} { - char* l = strchr(yytext, '/'); - *l++ = '\0'; - int len = atoi(l); - rules_lval.prefixval = new IPPrefix(IPAddr(extract_ipv6(yytext)), len); + int len = 0; + string ip = extract_ip_and_len(yytext, &len); + rules_lval.prefixval = new IPPrefix(IPAddr(ip), len, true); return TOK_IP6; } diff --git a/src/scan.l b/src/scan.l index 6c87766781..8ff33e7d24 100644 --- a/src/scan.l +++ b/src/scan.l @@ -148,6 +148,7 @@ D [0-9]+ HEX [0-9a-fA-F]+ IDCOMPONENT [A-Za-z_][A-Za-z_0-9]* ID {IDCOMPONENT}(::{IDCOMPONENT})* +IP6 ("["({HEX}:){7}{HEX}"]")|("["0x{HEX}({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*({D}"."){3}{D}"]") FILE [^ \t\n]+ PREFIX [^ \t\n]+ FLOAT (({D}*"."?{D})|({D}"."?{D}*))([eE][-+]?{D})? @@ -229,21 +230,23 @@ ESCSEQ (\\([^\n]|[0-7]+|x[[:xdigit:]]+)) } /* IPv6 literal constant patterns */ -"["({HEX}:){7}{HEX}"]" { - string s(yytext+1); - RET_CONST(new AddrVal(s.erase(s.size()-1))) +{IP6} { + RET_CONST(new AddrVal(extract_ip(yytext))) } -"["0x{HEX}({HEX}|:)*"::"({HEX}|:)*"]" { - string s(yytext+3); - RET_CONST(new AddrVal(s.erase(s.size()-1))) + +{IP6}{OWS}"/"{OWS}{D} { + int len = 0; + string ip = extract_ip_and_len(yytext, &len); + RET_CONST(new SubNetVal(IPPrefix(IPAddr(ip), len, true))) } -"["({HEX}|:)*"::"({HEX}|:)*"]" { - string s(yytext+1); - RET_CONST(new AddrVal(s.erase(s.size()-1))) -} -"["({HEX}|:)*"::"({HEX}|:)*({D}"."){3}{D}"]" { - string s(yytext+1); - RET_CONST(new AddrVal(s.erase(s.size()-1))) + + /* IPv4 literal constant patterns */ +({D}"."){3}{D} RET_CONST(new AddrVal(yytext)) + +({D}"."){3}{D}{OWS}"/"{OWS}{D} { + int len = 0; + string ip = extract_ip_and_len(yytext, &len); + RET_CONST(new SubNetVal(IPPrefix(IPAddr(ip), len))) } [!%*/+\-,:;<=>?()\[\]{}~$|] return yytext[0]; @@ -484,8 +487,6 @@ F RET_CONST(new Val(false, TYPE_BOOL)) {FLOAT}{OWS}msec(s?) RET_CONST(new IntervalVal(atof(yytext),Milliseconds)) {FLOAT}{OWS}usec(s?) RET_CONST(new IntervalVal(atof(yytext),Microseconds)) -({D}"."){3}{D} RET_CONST(new AddrVal(yytext)) - "0x"{HEX}+ RET_CONST(new Val(static_cast(strtoull(yytext, 0, 16)), TYPE_COUNT)) {H}("."{H})+ RET_CONST(dns_mgr->LookupHost(yytext)) diff --git a/src/util.cc b/src/util.cc index 76ca7729df..80cd3a0685 100644 --- a/src/util.cc +++ b/src/util.cc @@ -43,6 +43,40 @@ #include "Net.h" #include "Reporter.h" +/** + * Return IP address without enclosing brackets and any leading 0x. + */ +std::string extract_ip(const std::string& i) + { + std::string s(skip_whitespace(i.c_str())); + if ( s.size() > 0 && s[0] == '[' ) + s.erase(0, 1); + + if ( s.size() > 1 && s.substr(0, 2) == "0x" ) + s.erase(0, 2); + + size_t pos = 0; + if ( (pos = s.find(']')) != std::string::npos ) + s = s.substr(0, pos); + + return s; + } + +/** + * Given a subnet string, return IP address and subnet length separately. + */ +std::string extract_ip_and_len(const std::string& i, int* len) + { + size_t pos = i.find('/'); + if ( pos == std::string::npos ) + return i; + + if ( len ) + *len = atoi(i.substr(pos + 1).c_str()); + + return extract_ip(i.substr(0, pos)); + } + /** * Takes a string, unescapes all characters that are escaped as hex codes * (\x##) and turns them into the equivalent ascii-codes. Returns a string diff --git a/src/util.h b/src/util.h index e69167abce..71b9c494e8 100644 --- a/src/util.h +++ b/src/util.h @@ -91,6 +91,9 @@ void delete_each(T* t) delete *it; } +std::string extract_ip(const std::string& i); +std::string extract_ip_and_len(const std::string& i, int* len); + std::string get_unescaped_string(const std::string& str); std::string get_escaped_string(const std::string& str, bool escape_all); diff --git a/testing/btest/Baseline/language.addr/out b/testing/btest/Baseline/language.addr/out index b04aac5ce3..b0ecdd3605 100644 --- a/testing/btest/Baseline/language.addr/out +++ b/testing/btest/Baseline/language.addr/out @@ -13,3 +13,5 @@ IPv6 address not case-sensitive (PASS) size of IPv6 address (PASS) IPv6 address type inference (PASS) IPv4 and IPv6 address inequality (PASS) +IPv4-mapped-IPv6 equality to IPv4 (PASS) +IPv4-mapped-IPv6 is IPv4 (PASS) diff --git a/testing/btest/Baseline/language.subnet/out b/testing/btest/Baseline/language.subnet/out index 45900a291e..e8c4ba354f 100644 --- a/testing/btest/Baseline/language.subnet/out +++ b/testing/btest/Baseline/language.subnet/out @@ -10,3 +10,11 @@ IPv6 subnet !in operator (PASS) IPv6 subnet type inference (PASS) IPv4 and IPv6 subnet inequality (PASS) IPv4 address and IPv6 subnet (PASS) +IPv4 in IPv4-mapped-IPv6 subnet (PASS) +IPv6 !in IPv4-mapped-IPv6 subnet (PASS) +IPv4-mapped-IPv6 in IPv4-mapped-IPv6 subnet (PASS) +IPv4-mapped-IPv6 subnet equality (PASS) +subnet literal const whitespace (PASS) +subnet literal const whitespace (PASS) +subnet literal const whitespace (PASS) +subnet literal const whitespace (PASS) diff --git a/testing/btest/language/addr.bro b/testing/btest/language/addr.bro index 1cd93bad03..dd7e5e1dff 100644 --- a/testing/btest/language/addr.bro +++ b/testing/btest/language/addr.bro @@ -1,4 +1,4 @@ -# @TEST-EXEC: bro %INPUT >out +# @TEST-EXEC: bro -b %INPUT >out # @TEST-EXEC: btest-diff out function test_case(msg: string, expect: bool) @@ -43,5 +43,10 @@ event bro_init() test_case( "IPv4 and IPv6 address inequality", a1 != b1 ); + # IPv4-mapped-IPv6 (internally treated as IPv4) + local c1: addr = [::ffff:1.2.3.4]; + + test_case( "IPv4-mapped-IPv6 equality to IPv4", c1 == 1.2.3.4 ); + test_case( "IPv4-mapped-IPv6 is IPv4", is_v4_addr(c1) == T ); } diff --git a/testing/btest/language/subnet.bro b/testing/btest/language/subnet.bro index ea641f6983..b3b50e085f 100644 --- a/testing/btest/language/subnet.bro +++ b/testing/btest/language/subnet.bro @@ -1,4 +1,4 @@ -# @TEST-EXEC: bro %INPUT >out +# @TEST-EXEC: bro -b %INPUT >out # @TEST-EXEC: btest-diff out function test_case(msg: string, expect: bool) @@ -43,5 +43,22 @@ event bro_init() test_case( "IPv4 and IPv6 subnet inequality", s1 != t1 ); test_case( "IPv4 address and IPv6 subnet", a1 !in t2 ); + # IPv4-mapped-IPv6 subnets + local u1: subnet = [::ffff:0:0]/96; + + test_case( "IPv4 in IPv4-mapped-IPv6 subnet", 1.2.3.4 in u1 ); + test_case( "IPv6 !in IPv4-mapped-IPv6 subnet", [fe80::1] !in u1 ); + test_case( "IPv4-mapped-IPv6 in IPv4-mapped-IPv6 subnet", + [::ffff:1.2.3.4] in u1 ); + test_case( "IPv4-mapped-IPv6 subnet equality", + [::ffff:1.2.3.4]/112 == 1.2.0.0/16 ); + test_case( "subnet literal const whitespace", + [::ffff:1.2.3.4] / 112 == 1.2.0.0 / 16 ); + test_case( "subnet literal const whitespace", + [::ffff:1.2.3.4]/ 128 == 1.2.3.4/ 32 ); + test_case( "subnet literal const whitespace", + [::ffff:1.2.3.4] /96 == 1.2.3.4 /0 ); + test_case( "subnet literal const whitespace", + [::ffff:1.2.3.4] / 92 == [::fffe:1.2.3.4] / 92 ); }