Implemented IPv4/IPv6 subnet and range identities
authorAndreas Steffen <andreas.steffen@strongswan.org>
Tue, 8 Mar 2016 21:27:30 +0000 (22:27 +0100)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Thu, 10 Mar 2016 12:59:37 +0000 (13:59 +0100)
The IKEv1 IPV4_ADDR_SUBNET, IPV6_ADDR_SUBNET, IPV4_ADDR_RANGE and
IPV6_ADDR_RANGE identities have been fully implemented and can be
used as owners of shared secrets (PSKs).

src/libstrongswan/tests/suites/test_identification.c
src/libstrongswan/utils/identification.c

index b5afa00..c0a21fe 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * Copyright (C) 2013-2015 Tobias Brunner
+ * Copyright (C) 2016 Andreas Steffen
  * Copyright (C) 2009 Martin Willi
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -122,67 +123,122 @@ static struct {
                } data;
        } result;
 } string_data[] = {
-       {NULL,                                          ID_ANY,                 { .type = ENC_CHUNK }},
-       {"",                                            ID_ANY,                 { .type = ENC_CHUNK }},
-       {"%any",                                        ID_ANY,                 { .type = ENC_CHUNK }},
-       {"%any6",                                       ID_ANY,                 { .type = ENC_CHUNK }},
-       {"0.0.0.0",                                     ID_ANY,                 { .type = ENC_CHUNK }},
-       {"0::0",                                        ID_ANY,                 { .type = ENC_CHUNK }},
-       {"::",                                          ID_ANY,                 { .type = ENC_CHUNK }},
-       {"*",                                           ID_ANY,                 { .type = ENC_CHUNK }},
-       {"any",                                         ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"any6",                                        ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"0",                                           ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"**",                                          ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"192.168.1.1",                         ID_IPV4_ADDR,   { .type = ENC_CHUNK,
+       {NULL,                                          ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"",                                            ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"%any",                                        ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"%any6",                                       ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"0.0.0.0",                                     ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"0::0",                                        ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"::",                                          ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"*",                                           ID_ANY,                                 { .type = ENC_CHUNK  }},
+       {"any",                                         ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"any6",                                        ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"0",                                           ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"**",                                          ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"192.168.1.1",                         ID_IPV4_ADDR,                   { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x01) }},
-       {"192.168.",                            ID_FQDN,                { .type = ENC_SIMPLE }},
-       {".",                                           ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"fec0::1",                                     ID_IPV6_ADDR,   { .type = ENC_CHUNK,
+       {"192.168.",                            ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {".",                                           ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"192.168.1.1/33",                      ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"192.168.1.1/32",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x01,0xff,0xff,0xff,0xff)  }},
+       {"192.168.1.1/31",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x00,0xff,0xff,0xff,0xfe)  }},
+       {"192.168.1.8/30",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x08,0xff,0xff,0xff,0xfc)  }},
+       {"192.168.1.128/25",            ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x80,0xff,0xff,0xff,0x80)  }},
+       {"192.168.1.0/24",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x00,0xff,0xff,0xff,0x00)  }},
+       {"192.168.1.0/23",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x00,0x00,0xff,0xff,0xfe,0x00)  }},
+       {"192.168.4.0/22",                      ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x04,0x00,0xff,0xff,0xfc,0x00)  }},
+       {"0.0.0.0/0",                           ID_IPV4_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00)  }},
+       {"192.168.1.0-192.168.1.40",ID_IPV4_ADDR_RANGE,         { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x00,0xc0,0xa8,0x01,0x28)  }},
+       {"0.0.0.0-255.255.255.255",     ID_IPV4_ADDR_RANGE,             { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff)  }},
+       {"192.168.1.40-192.168.1.0",ID_FQDN,                            { .type = ENC_SIMPLE }},
+       {"fec0::1",                                     ID_IPV6_ADDR,                   { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
-                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01) }},
-       {"fec0::",                                      ID_IPV6_ADDR,   { .type = ENC_CHUNK,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01)  }},
+       {"fec0::",                                      ID_IPV6_ADDR,                   { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
-                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00) }},
-       {"fec0:",                                       ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {":",                                           ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {"alice@strongswan.org",        ID_RFC822_ADDR, { .type = ENC_SIMPLE }},
-       {"alice@strongswan",            ID_RFC822_ADDR, { .type = ENC_SIMPLE }},
-       {"alice@",                                      ID_RFC822_ADDR, { .type = ENC_SIMPLE }},
-       {"alice",                                       ID_FQDN,                { .type = ENC_SIMPLE }},
-       {"@",                                           ID_FQDN,                { .type = ENC_CHUNK }},
-       {" @",                                          ID_RFC822_ADDR, { .type = ENC_SIMPLE }},
-       {"@strongswan.org",                     ID_FQDN,                { .type = ENC_STRING,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00)  }},
+       {"fec0:",                                       ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {":",                                           ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"fec0::1/129",                         ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"fec0::1/128",                         ID_IPV6_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff ) }},
+       {"fec0::1/127",                         ID_IPV6_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe ) }},
+       {"fec0::4/126",                         ID_IPV6_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc ) }},
+       {"fec0::100/120",                       ID_IPV6_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+                                                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00 ) }},
+       {"::/0",                                        ID_IPV6_ADDR_SUBNET,    { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ) }},
+       {"fec0::1-fec0::4fff",          ID_IPV6_ADDR_RANGE,             { .type = ENC_CHUNK,
+               .data.c = chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
+                                                                  0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                                  0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xff ) }},
+       {"fec0::4fff-fec0::1",          ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"fec0::1-",                            ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"alice@strongswan.org",        ID_RFC822_ADDR,                 { .type = ENC_SIMPLE }},
+       {"alice@strongswan",            ID_RFC822_ADDR,                 { .type = ENC_SIMPLE }},
+       {"alice@",                                      ID_RFC822_ADDR,                 { .type = ENC_SIMPLE }},
+       {"alice",                                       ID_FQDN,                                { .type = ENC_SIMPLE }},
+       {"@",                                           ID_FQDN,                                { .type = ENC_CHUNK }},
+       {" @",                                          ID_RFC822_ADDR,                 { .type = ENC_SIMPLE }},
+       {"@strongswan.org",                     ID_FQDN,                                { .type = ENC_STRING,
                .data.s = "strongswan.org" }},
-       {"@#deadbeef",                          ID_KEY_ID,              { .type = ENC_CHUNK,
+       {"@#deadbeef",                          ID_KEY_ID,                              { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xde,0xad,0xbe,0xef) }},
-       {"@#deadbee",                           ID_KEY_ID,              { .type = ENC_CHUNK,
+       {"@#deadbee",                           ID_KEY_ID,                              { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0x0d,0xea,0xdb,0xee) }},
-       {"foo=bar",                                     ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {"foo=",                                        ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {"=bar",                                        ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {"C=",                                          ID_DER_ASN1_DN, { .type = ENC_CHUNK,
+       {"foo=bar",                                     ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"foo=",                                        ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"=bar",                                        ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"C=",                                          ID_DER_ASN1_DN,                 { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0x30,0x0b,0x31,0x09,0x30,0x07,0x06,
                                                                   0x03,0x55,0x04,0x06,0x13,0x00) }},
-       {"C=CH",                                        ID_DER_ASN1_DN, { .type = ENC_CHUNK,
+       {"C=CH",                                        ID_DER_ASN1_DN,                 { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0x30,0x0d,0x31,0x0b,0x30,0x09,0x06,
                                                                   0x03,0x55,0x04,0x06,0x13,0x02,0x43,0x48) }},
-       {"C=CH,",                                       ID_DER_ASN1_DN, { .type = ENC_CHUNK,
+       {"C=CH,",                                       ID_DER_ASN1_DN,                 { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0x30,0x0d,0x31,0x0b,0x30,0x09,0x06,
                                                                   0x03,0x55,0x04,0x06,0x13,0x02,0x43,0x48) }},
-       {"C=CH, ",                                      ID_DER_ASN1_DN, { .type = ENC_CHUNK,
+       {"C=CH, ",                                      ID_DER_ASN1_DN,                 { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0x30,0x0d,0x31,0x0b,0x30,0x09,0x06,
                                                                   0x03,0x55,0x04,0x06,0x13,0x02,0x43,0x48) }},
-       {"C=CH, O",                                     ID_KEY_ID,              { .type = ENC_SIMPLE }},
-       {"IPv4:#c0a80101",                      ID_IPV4_ADDR,   { .type = ENC_CHUNK,
+       {"C=CH, O",                                     ID_KEY_ID,                              { .type = ENC_SIMPLE }},
+       {"IPv4:#c0a80101",                      ID_IPV4_ADDR,                   { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x01) }},
-       { "email:tester",                       ID_RFC822_ADDR, { .type = ENC_STRING,
+       { "email:tester",                       ID_RFC822_ADDR,                 { .type = ENC_STRING,
                .data.s = "tester" }},
-       { "{1}:#c0a80101",                      ID_IPV4_ADDR,   { .type = ENC_CHUNK,
+       { "{1}:#c0a80101",                      ID_IPV4_ADDR,                   { .type = ENC_CHUNK,
                .data.c = chunk_from_chars(0xc0,0xa8,0x01,0x01) }},
-       { "{0x02}:tester",                      ID_FQDN,                { .type = ENC_STRING,
+       { "{0x02}:tester",                      ID_FQDN,                                { .type = ENC_STRING,
                .data.s = "tester" }},
-       { "{99}:somedata",                      99,                             { .type = ENC_STRING,
+       { "{99}:somedata",                      99,                                             { .type = ENC_STRING,
                .data.s = "somedata" }},
 };
 
@@ -264,14 +320,33 @@ START_TEST(test_printf_hook)
 
        string_equals("192.168.1.1", "192.168.1.1");
        string_equals_id("(invalid ID_IPV4_ADDR)",
-                               identification_create_from_encoding(ID_IPV4_ADDR, chunk_empty));
+                       identification_create_from_encoding(ID_IPV4_ADDR, chunk_empty));
+       string_equals("192.168.1.1/32", "192.168.1.1/32");
+       string_equals("192.168.1.2/31", "192.168.1.2/31");
+       string_equals("192.168.1.0/24", "192.168.1.0/24");
+       string_equals("192.168.2.0/23", "192.168.2.0/23");
+       string_equals("0.0.0.0/0", "0.0.0.0/0");
+       string_equals_id("(invalid ID_IPV4_ADDR_SUBNET)",
+                       identification_create_from_encoding(ID_IPV4_ADDR_SUBNET, chunk_empty));
+       string_equals("192.168.1.1-192.168.1.254", "192.168.1.1-192.168.1.254");
+       string_equals("0.0.0.0-255.255.255.255", "0.0.0.0-255.255.255.255");
+       string_equals_id("(invalid ID_IPV4_ADDR_RANGE)",
+                       identification_create_from_encoding(ID_IPV4_ADDR_RANGE, chunk_empty));
        string_equals("fec0::1", "fec0::1");
        string_equals("fec0::1", "fec0:0:0::1");
        string_equals_id("(invalid ID_IPV6_ADDR)",
-                               identification_create_from_encoding(ID_IPV6_ADDR, chunk_empty));
-
+                       identification_create_from_encoding(ID_IPV6_ADDR, chunk_empty));
+       string_equals("fec0::1/128", "fec0::1/128");
+       string_equals("fec0::2/127", "fec0::2/127");
+       string_equals("fec0::100/120", "fec0::100/120");
+       string_equals("::/0", "::/0");
+       string_equals_id("(invalid ID_IPV6_ADDR_SUBNET)",
+                       identification_create_from_encoding(ID_IPV6_ADDR_SUBNET, chunk_empty));
+       string_equals("fec0::1-fec0::4fff", "fec0::1-fec0::4fff");
+       string_equals_id("(invalid ID_IPV6_ADDR_RANGE)",
+                       identification_create_from_encoding(ID_IPV6_ADDR_RANGE, chunk_empty));
        string_equals_id("(unknown ID type: 255)",
-                               identification_create_from_encoding(255, chunk_empty));
+                       identification_create_from_encoding(255, chunk_empty));
 
        string_equals("moon@strongswan.org", "moon@strongswan.org");
        string_equals("MOON@STRONGSWAN.ORG", "MOON@STRONGSWAN.ORG");
@@ -595,6 +670,89 @@ START_TEST(test_matches_binary)
 }
 END_TEST
 
+START_TEST(test_matches_range)
+{
+       identification_t *a, *b;
+
+       /* IPv4 addresses */
+       a = identification_create_from_string("192.168.1.1");
+       ck_assert(a->get_type(a) == ID_IPV4_ADDR);
+       ck_assert(id_matches(a, "%any", ID_MATCH_ANY));
+       ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_MAX_WILDCARDS));
+       ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "192.168.1.2", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "192.168.1.1/32", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "192.168.1.0/32", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "192.168.1.0/24", ID_MATCH_ONE_WILDCARD));
+       ck_assert(id_matches(a, "192.168.0.0/24", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "192.168.1.0-192.168.1.64", ID_MATCH_ONE_WILDCARD));
+       ck_assert(id_matches(a, "192.168.1.2-192.168.1.64", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "192.168.0.240-192.168.1.0", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE));
+
+       /* Malformed IPv4 subnet and range encoding */
+       b = identification_create_from_encoding(ID_IPV4_ADDR_SUBNET, chunk_empty);
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+       b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, chunk_empty);
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+       b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE,
+                       chunk_from_chars(0xc0,0xa8,0x01,0x28,0xc0,0xa8,0x01,0x00));
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+
+       a->destroy(a);
+
+       /* IPv6 addresses */
+       a = identification_create_from_string("fec0::1");
+       ck_assert(a->get_type(a) == ID_IPV6_ADDR);
+       ck_assert(id_matches(a, "%any", ID_MATCH_ANY));
+       ck_assert(id_matches(a, "::/0", ID_MATCH_MAX_WILDCARDS));
+       ck_assert(id_matches(a, "fec0::1", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "fec0::2", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "fec0::1/128", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "fec0::/128", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "fec0::/120", ID_MATCH_ONE_WILDCARD));
+       ck_assert(id_matches(a, "fec0::100/120", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "fec0::1-fec0::1", ID_MATCH_PERFECT));
+       ck_assert(id_matches(a, "fec0::0-fec0::5", ID_MATCH_ONE_WILDCARD));
+       ck_assert(id_matches(a, "fec0::4001-fec0::4ffe", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "feb0::1-fec0::0", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE));
+
+       /* Malformed IPv6 subnet and range encoding */
+       b = identification_create_from_encoding(ID_IPV6_ADDR_SUBNET, chunk_empty);
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+       b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, chunk_empty);
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+       b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE,
+                       chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                        0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xff,
+                                                        0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,
+                                                        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 ));
+       ck_assert(a->matches(a, b) == ID_MATCH_NONE);
+       b->destroy(b);
+
+       a->destroy(a);
+
+       /* Malformed IPv4 address encoding */
+       a = identification_create_from_encoding(ID_IPV4_ADDR, chunk_empty);
+       ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "0.0.0.0-255.255.255.255", ID_MATCH_NONE));
+       a->destroy(a);
+
+       /* Malformed IPv6 address encoding */
+       a = identification_create_from_encoding(ID_IPV6_ADDR, chunk_empty);
+       ck_assert(id_matches(a, "::/0", ID_MATCH_NONE));
+       ck_assert(id_matches(a, "::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ID_MATCH_NONE));
+       a->destroy(a);
+}
+END_TEST
+
 START_TEST(test_matches_string)
 {
        identification_t *a;
@@ -929,6 +1087,7 @@ Suite *identification_suite_create()
        tcase_add_test(tc, test_matches);
        tcase_add_test(tc, test_matches_any);
        tcase_add_test(tc, test_matches_binary);
+       tcase_add_test(tc, test_matches_range);
        tcase_add_test(tc, test_matches_string);
        tcase_add_loop_test(tc, test_matches_empty, ID_ANY, ID_KEY_ID + 1);
        tcase_add_loop_test(tc, test_matches_empty_reverse, ID_ANY, ID_KEY_ID + 1);
index 1b0d055..9842458 100644 (file)
@@ -1,8 +1,9 @@
 /*
+ * Copyright (C) 2016 Andreas Steffen
  * Copyright (C) 2009-2015 Tobias Brunner
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2005 Jan Hutter
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -824,6 +825,154 @@ METHOD(identification_t, matches_dn, id_match_t,
 }
 
 /**
+ * Transform netmask to CIDR bits
+ */
+static int netmask_to_cidr(char *netmask, size_t address_size)
+{
+       uint8_t byte;
+       int i, netbits = 0;
+
+       for (i = 0; i < address_size; i++)
+       {
+               byte = netmask[i];
+
+               if (byte == 0x00)
+               {
+                       break;
+               }
+               if (byte == 0xff)
+               {
+                       netbits += 8;
+               }
+               else
+               {
+                       while (byte & 0x80)
+                       {
+                               netbits++;
+                               byte <<= 1;
+                       }
+               }
+       }
+       return netbits;
+}
+
+METHOD(identification_t, matches_range, id_match_t,
+       private_identification_t *this, identification_t *other)
+{
+       chunk_t other_encoding;
+       uint8_t *address, *from, *to, *network, *netmask;
+       size_t address_size = 0;
+       int netbits, range_sign, i;
+
+       if (other->get_type(other) == ID_ANY)
+       {
+               return ID_MATCH_ANY;
+       }
+       if (this->type == other->get_type(other) &&
+               chunk_equals(this->encoded, other->get_encoding(other)))
+       {
+               return ID_MATCH_PERFECT;
+       }
+       if ((this->type == ID_IPV4_ADDR &&
+                other->get_type(other) == ID_IPV4_ADDR_SUBNET))
+       {
+               address_size = sizeof(struct in_addr);
+       }
+       else if ((this->type == ID_IPV6_ADDR &&
+                other->get_type(other) == ID_IPV6_ADDR_SUBNET))
+       {
+               address_size = sizeof(struct in6_addr);
+       }
+       if (address_size)
+       {
+               other_encoding = other->get_encoding(other);
+               if (this->encoded.len != address_size ||
+                       other_encoding.len != 2 * address_size)
+               {
+                       return ID_MATCH_NONE;
+               }
+               address = this->encoded.ptr;
+               network = other_encoding.ptr;
+               netmask = other_encoding.ptr + address_size;
+               netbits = netmask_to_cidr(netmask, address_size);
+
+               if (netbits == 0)
+               {
+                       return ID_MATCH_MAX_WILDCARDS;
+               }
+               if (netbits == 8 * address_size)
+               {
+                       return memeq(address, network, address_size) ?
+                                  ID_MATCH_PERFECT : ID_MATCH_NONE;
+               }
+               for (i = 0; i < (netbits + 7)/8; i++)
+               {
+                       if ((address[i] ^ network[i]) & netmask[i])
+                       {
+                               return ID_MATCH_NONE;
+                       }
+               }
+               return ID_MATCH_ONE_WILDCARD;
+       }
+       if ((this->type == ID_IPV4_ADDR &&
+                other->get_type(other) == ID_IPV4_ADDR_RANGE))
+       {
+               address_size = sizeof(struct in_addr);
+       }
+       else if ((this->type == ID_IPV6_ADDR &&
+                other->get_type(other) == ID_IPV6_ADDR_RANGE))
+       {
+               address_size = sizeof(struct in6_addr);
+       }
+       if (address_size)
+       {
+               other_encoding = other->get_encoding(other);
+               if (this->encoded.len != address_size ||
+                       other_encoding.len != 2 * address_size)
+               {
+                       return ID_MATCH_NONE;
+               }
+               address = this->encoded.ptr;
+               from = other_encoding.ptr;
+               to = other_encoding.ptr + address_size;
+
+               range_sign = memcmp(to, from, address_size);
+               if (range_sign < 0)
+               {       /* to is smaller than from */
+                       return ID_MATCH_NONE;
+               }
+
+               /* check lower bound */
+               for (i = 0; i < address_size; i++)
+               {
+                       if (address[i] != from[i])
+                       {
+                               if (address[i] < from[i])
+                               {
+                                       return ID_MATCH_NONE;
+                               }
+                               break;
+                       }
+               }
+
+               /* check upper bound */
+               for (i = 0; i < address_size; i++)
+               {
+                       if (address[i] != to[i])
+                       {
+                               if (address[i] > to[i])
+                               {
+                                       return ID_MATCH_NONE;
+                               }
+                               break;
+                       }
+               }
+               return range_sign ? ID_MATCH_ONE_WILDCARD : ID_MATCH_PERFECT;
+       }
+       return ID_MATCH_NONE;
+}
+
+/**
  * Described in header.
  */
 int identification_printf_hook(printf_hook_data_t *data,
@@ -831,7 +980,9 @@ int identification_printf_hook(printf_hook_data_t *data,
 {
        private_identification_t *this = *((private_identification_t**)(args[0]));
        chunk_t proper;
-       char buf[512];
+       char buf[BUF_LEN];
+       char *pos;
+       size_t written, len, address_size;
 
        if (this == NULL)
        {
@@ -841,49 +992,115 @@ int identification_printf_hook(printf_hook_data_t *data,
        switch (this->type)
        {
                case ID_ANY:
-                       snprintf(buf, sizeof(buf), "%%any");
+                       snprintf(buf, BUF_LEN, "%%any");
                        break;
                case ID_IPV4_ADDR:
                        if (this->encoded.len < sizeof(struct in_addr) ||
-                               inet_ntop(AF_INET, this->encoded.ptr, buf, sizeof(buf)) == NULL)
+                               inet_ntop(AF_INET, this->encoded.ptr, buf, BUF_LEN) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV4_ADDR)");
+                       }
+                       break;
+               case ID_IPV4_ADDR_SUBNET:
+                       address_size = sizeof(struct in_addr);
+                       if (this->encoded.len < 2 * address_size ||
+                               inet_ntop(AF_INET, this->encoded.ptr, buf, BUF_LEN) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV4_ADDR_SUBNET)");
+                               break;
+                       }
+                       written = strlen(buf);
+                       snprintf(buf + written, BUF_LEN - written, "/%d",
+                                        netmask_to_cidr(this->encoded.ptr + address_size,
+                                                                                                                address_size));
+                       break;
+               case ID_IPV4_ADDR_RANGE:
+                       address_size = sizeof(struct in_addr);
+                       if (this->encoded.len < 2 * address_size ||
+                               inet_ntop(AF_INET, this->encoded.ptr, buf, BUF_LEN) == NULL)
                        {
-                               snprintf(buf, sizeof(buf), "(invalid ID_IPV4_ADDR)");
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV4_ADDR_RANGE)");
+                               break;
+                       }
+                       written = strlen(buf);
+                       pos = buf + written;
+                       len = BUF_LEN - written;
+                       written = snprintf(pos, len, "-");
+                       if (written < 0 || written >= len ||
+                           inet_ntop(AF_INET, this->encoded.ptr + address_size,
+                                                 pos + written, len - written) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV4_ADDR_RANGE)");
                        }
                        break;
                case ID_IPV6_ADDR:
                        if (this->encoded.len < sizeof(struct in6_addr) ||
-                               inet_ntop(AF_INET6, this->encoded.ptr, buf, INET6_ADDRSTRLEN) == NULL)
+                               inet_ntop(AF_INET6, this->encoded.ptr, buf, BUF_LEN) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV6_ADDR)");
+                       }
+                       break;
+               case ID_IPV6_ADDR_SUBNET:
+                       address_size = sizeof(struct in6_addr);
+                       if (this->encoded.len < 2 * address_size ||
+                               inet_ntop(AF_INET6, this->encoded.ptr, buf, BUF_LEN) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV6_ADDR_SUBNET)");
+                       }
+                       else
                        {
-                               snprintf(buf, sizeof(buf), "(invalid ID_IPV6_ADDR)");
+                               written = strlen(buf);
+                               snprintf(buf + written, BUF_LEN - written, "/%d",
+                                                netmask_to_cidr(this->encoded.ptr + address_size,
+                                                                                                                        address_size));
+                       }
+                       break;
+               case ID_IPV6_ADDR_RANGE:
+                       address_size = sizeof(struct in6_addr);
+                       if (this->encoded.len < 2 * address_size ||
+                               inet_ntop(AF_INET6, this->encoded.ptr, buf, BUF_LEN) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV6_ADDR_RANGE)");
+                               break;
+                       }
+                       written = strlen(buf);
+                       pos = buf + written;
+                       len = BUF_LEN - written;
+                       written = snprintf(pos, len, "-");
+                       if (written < 0 || written >= len ||
+                           inet_ntop(AF_INET6, this->encoded.ptr + address_size,
+                                                 pos + written, len - written) == NULL)
+                       {
+                               snprintf(buf, BUF_LEN, "(invalid ID_IPV6_ADDR_RANGE)");
                        }
                        break;
                case ID_FQDN:
                case ID_RFC822_ADDR:
                case ID_DER_ASN1_GN_URI:
                        chunk_printable(this->encoded, &proper, '?');
-                       snprintf(buf, sizeof(buf), "%.*s", (int)proper.len, proper.ptr);
+                       snprintf(buf, BUF_LEN, "%.*s", (int)proper.len, proper.ptr);
                        chunk_free(&proper);
                        break;
                case ID_DER_ASN1_DN:
-                       dntoa(this->encoded, buf, sizeof(buf));
+                       dntoa(this->encoded, buf, BUF_LEN);
                        break;
                case ID_DER_ASN1_GN:
-                       snprintf(buf, sizeof(buf), "(ASN.1 general name)");
+                       snprintf(buf, BUF_LEN, "(ASN.1 general name)");
                        break;
                case ID_KEY_ID:
                        if (chunk_printable(this->encoded, NULL, '?') &&
                                this->encoded.len != HASH_SIZE_SHA1)
                        {       /* fully printable, use ascii version */
-                               snprintf(buf, sizeof(buf), "%.*s", (int)this->encoded.len,
+                               snprintf(buf, BUF_LEN, "%.*s", (int)this->encoded.len,
                                                 this->encoded.ptr);
                        }
                        else
                        {       /* not printable, hex dump */
-                               snprintf(buf, sizeof(buf), "%#B", &this->encoded);
+                               snprintf(buf, BUF_LEN, "%#B", &this->encoded);
                        }
                        break;
                default:
-                       snprintf(buf, sizeof(buf), "(unknown ID type: %d)", this->type);
+                       snprintf(buf, BUF_LEN, "(unknown ID type: %d)", this->type);
                        break;
        }
        if (spec->minus)
@@ -952,6 +1169,13 @@ static private_identification_t *identification_create(id_type_t type)
                        this->public.matches = _matches_dn;
                        this->public.contains_wildcards = _contains_wildcards_dn;
                        break;
+               case ID_IPV4_ADDR:
+               case ID_IPV6_ADDR:
+                       this->public.hash = _hash_binary;
+                       this->public.equals = _equals_binary;
+                       this->public.matches = _matches_range;
+                       this->public.contains_wildcards = return_false;
+                       break;
                default:
                        this->public.hash = _hash_binary;
                        this->public.equals = _equals_binary;
@@ -973,6 +1197,10 @@ static private_identification_t* create_from_string_with_prefix_type(char *str)
        } prefixes[] = {
                { "ipv4:",                      ID_IPV4_ADDR                    },
                { "ipv6:",                      ID_IPV6_ADDR                    },
+               { "ipv4net:",           ID_IPV4_ADDR_SUBNET             },
+               { "ipv6net:",           ID_IPV6_ADDR_SUBNET             },
+               { "ipv4range:",         ID_IPV4_ADDR_RANGE              },
+               { "ipv6range:",         ID_IPV6_ADDR_RANGE              },
                { "rfc822:",            ID_RFC822_ADDR                  },
                { "email:",                     ID_RFC822_ADDR                  },
                { "userfqdn:",          ID_USER_FQDN                    },
@@ -1038,6 +1266,115 @@ static private_identification_t* create_from_string_with_num_type(char *str)
        return this;
 }
 
+/**
+ * Convert to an IPv4/IPv6 host address, subnet or address range
+ */
+static private_identification_t* create_ip_address_from_string(char *string,
+                                                                                                                          bool is_ipv4)
+{
+       private_identification_t *this;
+       uint8_t encoding[32];
+       uint8_t *str, *pos, *address, *to_address, *netmask;
+       size_t address_size;
+       int bits, bytes, i;
+       bool has_subnet = FALSE, has_range = FALSE;
+
+       address = encoding;
+       address_size = is_ipv4 ? sizeof(struct in_addr) : sizeof(struct in6_addr);
+
+       str = strdup(string);
+       pos = strchr(str, '/');
+       if (pos)
+       {       /* separate IP address from optional netmask */
+
+               *pos = '\0';
+               has_subnet = TRUE;
+       }
+       else
+       {
+               pos = strchr(str, '-');
+               if (pos)
+               {       /* separate lower address from upper address of IP range */
+                       *pos = '\0';
+                       has_range = TRUE;
+               }
+       }
+
+       if (inet_pton(is_ipv4 ? AF_INET : AF_INET6, str, address) != 1)
+       {
+               free(str);
+               return NULL;
+       }
+
+       if (has_subnet)
+       {       /* is IP subnet */
+               bits = atoi(pos + 1);
+               if (bits > 8 * address_size)
+               {
+                       free(str);
+                       return NULL;
+               }
+               bytes = bits / 8;
+               bits -= 8 * bytes;
+               netmask = encoding + address_size;
+
+               for (i = 0; i < address_size; i++)
+               {
+                       if (bytes)
+                       {
+                               *netmask = 0xff;
+                               bytes--;
+                       }
+                       else if (bits)
+                       {
+                               *netmask = 0xff << (8 - bits);
+                               bits = 0;
+                       }
+                       else
+                       {
+                               *netmask = 0x00;
+                       }
+                       *address++ &= *netmask++;
+               }
+               this = identification_create(is_ipv4 ? ID_IPV4_ADDR_SUBNET :
+                                                                                          ID_IPV6_ADDR_SUBNET);
+               this->encoded = chunk_clone(chunk_create(encoding, 2 * address_size));
+       }
+       else if (has_range)
+       {       /* is IP range */
+               to_address = encoding + address_size;
+
+               if (inet_pton(is_ipv4 ? AF_INET : AF_INET6, pos + 1, to_address) != 1)
+               {
+                       free(str);
+                       return NULL;
+               }
+               for (i = 0; i < address_size; i++)
+               {
+                       if (address[i] != to_address[i])
+                       {
+                               if (address[i] > to_address[i])
+                               {
+                                       free(str);
+                                       return NULL;
+                               }
+                               break;
+                       }
+               }
+               this = identification_create(is_ipv4 ? ID_IPV4_ADDR_RANGE :
+                                                                                          ID_IPV6_ADDR_RANGE);
+               this->encoded = chunk_clone(chunk_create(encoding, 2 * address_size));
+       }
+       else
+       {       /* is IP host address */
+               this = identification_create(is_ipv4 ? ID_IPV4_ADDR : ID_IPV6_ADDR);
+               this->encoded = chunk_clone(chunk_create(encoding, address_size));
+       }
+       free(str);
+
+       return this;
+}
+
 /*
  * Described in header.
  */
@@ -1095,15 +1432,9 @@ identification_t *identification_create_from_string(char *string)
                {
                        if (strchr(string, ':') == NULL)
                        {
-                               struct in_addr address;
-                               chunk_t chunk = {(void*)&address, sizeof(address)};
-
-                               if (inet_pton(AF_INET, string, &address) > 0)
-                               {       /* is IPv4 */
-                                       this = identification_create(ID_IPV4_ADDR);
-                                       this->encoded = chunk_clone(chunk);
-                               }
-                               else
+                               /* IPv4 address or subnet */
+                               this = create_ip_address_from_string(string, TRUE);
+                               if (!this)
                                {       /* not IPv4, mostly FQDN */
                                        this = identification_create(ID_FQDN);
                                        this->encoded = chunk_from_str(strdup(string));
@@ -1112,15 +1443,9 @@ identification_t *identification_create_from_string(char *string)
                        }
                        else
                        {
-                               struct in6_addr address;
-                               chunk_t chunk = {(void*)&address, sizeof(address)};
-
-                               if (inet_pton(AF_INET6, string, &address) > 0)
-                               {       /* is IPv6 */
-                                       this = identification_create(ID_IPV6_ADDR);
-                                       this->encoded = chunk_clone(chunk);
-                               }
-                               else
+                               /* IPv6 address or subnet */
+                               this = create_ip_address_from_string(string, FALSE);
+                               if (!this)
                                {       /* not IPv4/6 fallback to KEY_ID */
                                        this = identification_create(ID_KEY_ID);
                                        this->encoded = chunk_from_str(strdup(string));