<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rfc SYSTEM "rfc2629.dtd" [
    <!ENTITY rfc2119 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml">
    <!ENTITY rfc2616 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2616.xml">
    <!ENTITY rfc2818 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2818.xml">
    <!ENTITY rfc3053 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.3053.xml">
    <!ENTITY rfc3403 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.3403.xml">
    <!ENTITY rfc3629 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.3629.xml">
    <!ENTITY rfc3912 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.3912.xml">
    <!ENTITY rfc4180 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4180.xml">
    <!ENTITY rfc4291 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4291.xml">
    <!ENTITY rfc4408 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4408.xml">
    <!ENTITY rfc4627 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4627.xml">
    <!ENTITY rfc4632 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4632.xml">
    <!ENTITY rfc4848 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.4848.xml">
    <!ENTITY rfc5139 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.5139.xml">
    <!ENTITY rfc5952 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.5952.xml">
    <!ENTITY rfc6772 PUBLIC ""
      "http://xml.resource.org/public/rfc/bibxml/reference.RFC.6772.xml">
]>

<rfc category="info"
     ipr="noModificationTrust200902"
     docName="draft-google-self-published-geofeeds-02">

<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>

<?rfc toc="yes" ?>
<?rfc symrefs="yes" ?>
<?rfc sortrefs="yes"?>
<?rfc iprnotified="no" ?>
<?rfc strict="yes" ?>
<?rfc compact="yes" ?>
<?rfc subcompact="no" ?>

  <front>
    <title>Self-published IP Geolocation Data</title>

    <author initials="E." surname="Kline" fullname="Erik Kline">
      <organization>Google Japan</organization>
      <address>
        <postal>
          <street>Roppongi 6-10-1, 26th Floor</street>
          <city>Minato</city>
          <region>Tokyo</region>
          <code>106-6126</code>
          <country>Japan</country>
        </postal>

        <phone>+81 03 6384 9000</phone>

        <email>ek@google.com</email>
      </address>
    </author>

    <author initials="K." surname="Duleba" fullname="Krzysztof Duleba">
      <organization>Google Switzerland GmbH</organization>
      <address>
        <postal>
          <street>Brandschenkestrasse 110</street>
          <code>8002</code>
          <city>Zürich</city>
          <country>Switzerland</country>
        </postal>

        <email>kduleba@google.com</email>
      </address>
    </author>

    <author initials="Z." surname="Szamonek" fullname="Zoltan Szamonek">
      <organization>Google Switzerland GmbH</organization>
      <address>
        <postal>
          <street>Brandschenkestrasse 110</street>
          <code>8002</code>
          <city>Zürich</city>
          <country>Switzerland</country>
        </postal>

        <email>zszami@google.com</email>
      </address>
    </author>

    <date/>

    <abstract>
      <t>
This document records a format whereby a network operator can publish a
mapping of IP address ranges to simplified geolocation information,
colloquially termed a geolocation "feed".  Interested parties can poll and
parse these feeds to update or merge with other geolocation data sources and
procedures.
      </t>
      <t>
Some technical organizations operating networks that move from one conference
location to the next have already experimentally published small geolocation
feeds.  At least one consumer (Google) has incorporated these ad hoc feeds
into a geolocation data pipeline.
      </t>
    </abstract>
  </front>

  <middle>
    <section title="Introduction">
      <section title="Motivation">
        <t>
Providers of services over the Internet have grown to depend on best-effort
geolocation information to improve the user experience.  Locality information
can aid in directing traffic to the nearest serving location, inferring likely
native language, and providing additional context for services involving
search queries.
        </t>
        <t>
When an ISP, for example, changes the location where an IP prefix is
deployed, services which make use of geolocation information may begin
to suffer degraded performance. This can lead to customer complaints,
possibly to the ISP directly. Dissemination of correct geolocation data
is complicated by the lack of any centralized means to coordinate and
communicate geolocation information to all interested consumers of the
data.
        </t>
        <t>
This document records a format whereby a network operator (an ISP, an
enterprise, or any organization which deems the geolocation of its IP prefixes
to be of concern) can publish a mapping of IP address ranges to simplified
geolocation information, colloquially termed a "geolocation feed".  Interested
parties can poll and parse these feeds to update or merge with other
geolocation data sources and procedures.
        </t>
        <t>
Some technical organizations operating networks that move from one conference
location to the next have already experimentally published small geolocation
feeds.  At least one consumer (Google) has incorporated these ad hoc feeds
into a geolocation data pipeline.
        </t>
      </section>

      <section title="Requirements notation">
        <t>
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in <xref target="RFC2119"/>.
        </t>
      </section>

      <section title="Implications of publication">
        <t>
This document describes both a format and a mechanism for publishing data,
with the implication that the owner of the data wishes it to be public.
Any privacy risk is bounded by the format, and data publishers MAY omit
certain fields to further protect privacy (see <xref target="spec"/>
for details about which fields exactly may be omitted).  Feed publishers
assume the responsibility of determining which data should be made public.
        </t>
        <t>
This proposal does not incorporate a mechanism to communicate acceptable
use policies for self-published data. Publication itself is inferred as
a desire by the publisher for the data to be usefully consumed, similar
to the publication of information like host names, cryptographic keys, and
SPF records <xref target="RFC4408"/> in the DNS.
        </t>
      </section>
    </section>

    <section title="Self-published IP geolocation feeds">
      <t>
The format described here was developed to address the need of network
operators to rapidly and usefully share geolocation information changes.
Originally, there arose a specific case where regional operators found it
desirable to publish location changes rather than wait for geolocation
algorithms to "learn" about them.  Later, technical conferences which
frequently use the same network prefixes advertised from different conference
locations experimented by publishing geolocation feeds, updated in advance
of network location changes, in order to better serve conference attendees.
      </t>
      <t>
At its simplest, the mechanism consists of a network operator publishing a
file (the "geolocation feed"), which contains several text entries, one per
line.  Each entry is keyed by a unique (within the feed) IP prefix (or single
IP address) followed by a sequence of network locality attributes to be
ascribed to the given prefix.
      </t>

      <section anchor="spec" title="Specification">
        <t>
For operational simplicity, every feed should contain data about all IP
addresses the provider wants to publish.  Alternatives, like publishing
only entries for IP addresses whose geolocation data has changed or differ
from current observed geolocation behavior "at large", are likely to be too
operationally complex.
        </t>
        <t>
Feeds MUST use UTF-8 <xref target="RFC3629"/> character encoding.
Text after a '#' character is treated as a comment only and ignored.
Blank lines are similarly ignored.
        </t>
        <t>
Feeds MUST be in comma separated values format as described in
<xref target="RFC4180"/>.  Each feed entry is a text line of the form:
          <figure>
            <artwork>
    ip_range,country,region,city,postal_code
            </artwork>
          </figure>
        </t>
        <t>
The IP range field is REQUIRED, all others are OPTIONAL (can be empty),
though the requisite minimum number of commas SHOULD be present.
        </t>

        <section title="Geolocation feed individual entry fields">
          <section title="IP Range">
            <t>
REQUIRED.  Each IP range field MUST be either a single IP address or an
IP prefix in CIDR notation in conformance with
<eref target="http://tools.ietf.org/html/rfc4632#section-3.1">section 3.1</eref>
of <xref target="RFC4632"/> for IPv4 or
<eref target="http://tools.ietf.org/html/rfc4291#section-2.3">section 2.3</eref>
of <xref target="RFC4291"/> for IPv6.
            </t>
            <t>
Examples include "192.0.2.1" and "192.0.2.0/24" for IPv4 and "2001:db8::1"
and "2001:db8::/32" for IPv6.
            </t>
          </section>

          <section title="Country">
            <t>
OPTIONAL.  The country field, if non-empty, MUST be a 2 letter ISO country
code conforming to ISO 3166-1 alpha 2 <xref target="ISO.3166.1alpha2"/>.
Parsers SHOULD treat this field case-insensitively.
            </t>
            <t>
Examples include "US" for the United States, "JP" for Japan, and "PL" for
Poland.
            </t>
          </section>

          <section title="Region">
            <t>
OPTIONAL.  The region field, if non-empty, MUST be a ISO region code
conforming to ISO 3166-2 <xref target="ISO.3166.2"/>.  Parsers SHOULD
treat this field case-insensitively.
            </t>
            <t>
Examples include "ID-RI" for the Riau province of Indonesia and "NG-RI" for
the Rivers province in Nigeria.
            </t>
          </section>

          <section title="City">
            <t>
OPTIONAL.  The city field, if non-empty, SHOULD be free UTF-8 text,
excluding the comma (',') character.
            </t>
            <t>
Examples include "Dublin", "New York", and "São Paulo" (specifically
"S" followed by 0xc3, 0xa3, and "o Paulo").
            </t>
          </section>

          <section anchor="postal" title="Postal code">
            <t>
OPTIONAL.  The postal code field, if non-empty, SHOULD be free UTF-8 text,
excluding the comma (',') character.  See <xref target="Privacy"/> for some
discussion of when this field must not be populated.
            </t>
            <t>
Examples include "106-6126" (in Minato ward, Tokyo, Japan).
            </t>
          </section>
        </section>

        <section title="Prefixes with no geolocation information">
          <t>
Feed publishers may indicate that some IP prefixes should not have any
associated geolocation information.  It may be that some prefixes under
their administrative control are reserved, not yet allocated or deployed,
or are in the process of being redeployed elsewhere and existing
geolocation information can, from the perspective of the publisher,
safely be discarded.
          </t>
          <t>
This special case can be indicated by explicitly leaving blank all
fields which specify any degree of geolocation information.  For
example:
          <figure>
            <artwork>
    127.0.0.0/8,,,,
    224.0.0.0/4,,,,
    240.0.0.0/4,,,,
            </artwork>
          </figure>
          </t>
          <t>
Historically, the user-assigned country identifier of "ZZ" had be used
for this same purpose.  This is not necessarily preferred, and no specific
interpretation of any of the other user-assigned country codes is
currently defined.
          </t>
        </section>

        <section title="Additional parsing requirements">
          <t>
Feed entries missing required fields, or having a required field which
fails to parse correctly MUST be discarded.  It is RECOMMENDED that such
entries also be logged for further administrative review.
          </t>
          <t>
While publishers SHOULD follow <xref target="RFC5952"/> style for IPv6
prefix fields, consumers MUST nevertheless accept all valid string
representations.
          </t>
          <t>
Duplicate IP address or prefix entries MUST be considered an error, and
consumer implementations SHOULD log the repeated entries for further
administrative review.  Publishers SHOULD take measures to ensure there is
one and only one entry per IP address and prefix.
          </t>
          <t>
Feed entries with non-empty optional fields which fail to parse,
either in part or in full, SHOULD be discarded.  It is RECOMMENDED
that they also be logged for further administrative review.
          </t>
          <t>
For compatibility with future additional fields a parser MUST ignore any
fields beyond those it expects.  The data from fields which are expected
and which parse successfully MUST still be considered valid.
          </t>
        </section>

        <section title="Looking up an IP address">
          <t>
Multiple entries which constitute nested prefixes are permitted.  Consumers
SHOULD consider the entry with the longest matching prefix (i.e. the
"most specific") to be the best matching entry for a given IP address.
          </t>
        </section>
      </section>

      <section title="Examples">
        <t>
Example entries using different IP address formats and describing locations
at country, region, city and postal code granularity level, respectively:
          <figure>
            <artwork>
    192.0.2.0/25,US,US-AL,,
    192.0.2.5,US,US-AL,Alabaster,
    192.0.2.128/25,PL,PL-MZ,,02-784
    2001:db8::/32,PL,,,
    2001:db8:cafe::/48,PL,PL-MZ,,02-784
            </artwork>
          </figure>
        </t>

        <t>
Experimentally, RIPE has published geolocation information for their conference
network prefixes, which change location in accordance with each new event.
<xref target="GEO_RIPE_NCC"/> at the time of writing contains:
          <figure>
            <artwork>
    193.0.24.0/21,IE,IE-D,Dublin,
    2001:67c:64::/48,IE,IE-D,Dublin,
            </artwork>
          </figure>
        </t>

        <t>
Similarly, ICANN has published geolocation information for their portable
conference network prefixes.  <xref target="GEO_ICANN"/> at the time of writing
contains:
          <figure>
            <artwork>
    199.91.192.0/21,US,US-CA,Los Angeles,
    2620:f:8000::/48,US,US-CA,Los Angeles,
            </artwork>
          </figure>
        </t>

        <t>
Furthermore, it is worth noting that the geolocation data of SixXS users,
already available at whois.sixxs.net, is now also accessible in the format
described here (see <xref target="GEO_SIXXS"/>).  This can be particularly
useful where tunnel broker networks <xref target="RFC3053"/> are concerned as:
          <list style="symbols">
            <t>
the geolocation attributes of users with neighboring prefixes can be quite
different and therefore not easily aggregated, and
            </t>
            <t>
attempting to learn this data by statistical analysis can be complicated by
the likely low number of samples for any given user, making satisfactory
statistical confidence difficult to achieve.
            </t>
          </list>
        </t>
      </section>

      <section title="Proposed extensions">
        <t>
Already some discussions have resulted in proposed extensions.  While the
purpose of this document is principally to record existing implementation
details, it may be that there is a larger desire to publish other "network
attributes" in a similar manner.  One such network attribute, "delegation
size", is not currently implemented but the state of the proposed
extension is recorded here to demonstrate the flexibility required of
parser implementations.
        </t>
        <t>
The following have been only informally discussed and are not in use at the
time of writing.
        </t>

        <section title="Delegation size">
          <t>
OPTIONAL.  A publisher may optionally communicate the average delegated
prefix size for subnetworks within the IP prefix of this entry.  For a network
operator this can be used to help consumers distinguish IP prefixes among
various use types such as residential prefixes, allocations to businesses,
or data center customer allocations.
          </t>
          <t>
Non-empty strings MUST be of the form required for CIDR notation suffixes,
i.e. "/" followed by the integer prefix length of the expected allocation
to the subnetworks from within the entry's prefix.  In the absence of data
to the contrary, it is common to assume that leaf networks may be delegated
a prefix ranging from /24 to /32 in IPv4 and /48 to /64 in IPv6.  Default
assumptions about delegation size are left to the consumer's implementation.
          </t>
          <t>
Examples for IPv6 include "/48", "/56", "/60", and "/64".
          </t>
        </section>
        <section title="Alternate format">
          <t>
In order to more flexibly support future extensions, use of a more
expressive feed format has been suggested.  Use of JavaScript Object
Notation (JSON, <xref target="RFC4627"/>), specifically, has been
discussed.  However, at the time of writing no such specification nor
implementation exists.
          </t>
        </section>
      </section>
    </section>

    <section title="Finding self-published IP geolocation feeds">
      <t>
The issue of finding, and later verifying, geolocation feeds is not
formally specified in this document.  At this time, only ad hoc feed
discovery and verification has a modicum of established practice (see below).
Regardless, both the ad hoc mechanics and a few proposed but not yet
implemented alternatives are discussed.
      </t>

      <section title="Ad hoc 'well known' URIs">
        <t>
To date, geolocation feeds have been shared informally in the form of HTTPS
URIs exchanged in email threads.  The two example URIs documented above
describe networks that change locations periodically, the operators and
operational practices of which are well known within their respective
technical communities.
        </t>
        <t>
The contents of the feeds are verified by a similarly ad hoc process
including:
          <list style="symbols">
            <t>
personal knowledge of the parties involved in the exchange, and
            </t>
            <t>
comparison of feed-advertised prefixes with the BGP-advertised prefixes
of Autonomous System Numbers known to be operated by the publishers.
            </t>
          </list>
        </t>
        <t>
Ad hoc mechanisms, while useful for early experimentation by producers and
consumers, are unlikely to be adequate for long-term, widespread use by
multiple parties.  Future versions of any such self-published geolocation
feed mechanism SHOULD address scalability concerns by defining a means
for automated discovery and verification of operational authority of
advertised prefixes.
        </t>
      </section>

      <section title="Using public databases of network authority">
        <t>
One possibility for enabling automation would be publication of feed URIs as
a well-known attribute in public databases of network authority, e.g. the
WHOIS service (<xref target="RFC3912"/>) operated by RIRs.  Verification
may be performed if the same or similarly authoritative service provides the
identical feed URI for queries for each CIDR prefix in the geolocation feed.
        </t>
        <t>
The burden of serving this data to all interested consumers, especially the
load imposed by any verification process, is not yet known.  The anticipation
of additional operational burden on the public resource of record (the
database of network authority) is however a noted concern.
        </t>
      </section>

      <section title="Using 'reverse' DNS with NAPTR records">
        <t>
Another possibility for automating the location and verification of a
geolocation feed is to incorporate feed URIs into the DNS, specifically the
in-addr.arpa and ip6.arpa portions of the DNS hierarchy.  A suitably formatted
query for a NAPTR (<xref target="RFC3403"/>) record, or more specifically a
U-NAPTR (<xref target="RFC4848"/>) record, could yield a transformation to
a geolocation feed URI.
        </t>
        <t>
For example, assuming a purely theoretical service name of "x-geofeed",
a 'reverse' DNS zone might contain a record of the form:
          <figure>
            <artwork>
    ;;       order pref flags
    IN NAPTR 200   10   "u"    "x-geofeed"        ( ; service
                                                    ; regexp
                               "!.*!https://example.com/ipgeo.csv!"
                               ""                   ; replacement
                               )
            </artwork>
          </figure>
        </t>
        <t>
Attempts to locate the geolocation feed for a given IP address would begin by
querying directly for a NAPTR record associated with the address's PTR-style
name. For example, 192.0.2.4 and 2001:db8::6 would cause a NAPTR record
request to be issued for "4.2.0.192.in-addr.arpa" and
"6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa",
respectively.
        </t>
        <t>
If no such record exists one further NAPTR query for the fully qualified
domain name of the SOA record in the authority section of the response to the
previous query would be performed ("2.0.192.in-addr.arpa" and
"d.0.1.0.0.2.ip6.arpa" in the examples above).
        </t>
        <t>
If one or more NAPTR records exist for the full PTR-style name but none of
them are for the required service name (e.g. "x-geofeed"), then likely no SOA
will be returned as a hint for subsequent queries.  In this case
implementations would need to first explicitly query for an SOA record for the
full PTR-style name, and then query for a NAPTR record of the SOA in the
response (assuming it differs from the previously queried name).
        </t>
        <t>
Any successfully located feed URIs could then be processed as outlined by this
document.
        </t>
        <t>
Verification of the contents of a feed would proceed in essentially the same
way.  CIDR prefixes may be verified by constructing a query for any single
address (at random) within the prefix and proceeding as above.  While not
strictly provably correct (in cases where a publisher has delegated some
portion of the advertised prefix but not excluded it from its feed), it may
nevertheless suffice for operational purposes, especially if a low-impact
on-going verification of observed client IP addresses is implemented, to
(eventually) catch any oversights.
        </t>
        <t>
This mode is untested and may prove impractical.  However, the operational
burden is more closely located with those wishing and willing to bear it,
i.e. the publishers who would likely handle serving in-addr.arpa and
ip6.arpa for the IP prefixes under their authority.
        </t>
      </section>
    </section>

    <section anchor="consumers"
             title="Consuming self-published IP geolocation feeds">
      <t>
Consumers MAY treat published feed data as a hint only and MAY choose
to prefer other sources of geolocation information for any given IP range.
Regardless of a consumer's stance with respect to a given published feed,
there are some points of note for sensibly and effectively consuming
published feeds.
      </t>

      <section title="Feed integrity">
        <t>
The integrity of published information SHOULD be protected by securing the
means of publication, for example by using HTTP over TLS
<xref target="RFC2818"/>.  Whenever possible, consumers SHOULD prefer
retrieving geolocation feeds in a manner that guarantees integrity of the feed.
        </t>
      </section>

      <section title="Verification of authority">
        <t>
Consumers of self-published IP geolocation feeds SHOULD perform some form
of verification that the publisher is in fact authoritative for the
addresses in the feed.  The actual means of verification is likely dependent
upon the way in which the feed is discovered.  Ad hoc shared URIs, for example,
will likely require an ad hoc verification process.  Future automated means of
feed discovery SHOULD have an accompanying automated means of verification.
        </t>
        <t>
A consumer MUST only trust geolocation information for IP addresses or ranges
for which the publisher has been verified as administratively authoritative.
All other geolocation feed entries MUST be ignored and SHOULD be logged for
further administrative review.
        </t>
      </section>

      <section title="Verification of accuracy">
        <t>
Errors and inaccuracies may occur at many levels, and publication and
consumption of geolocation data are no exceptions.  To the extent practical
consumers SHOULD take steps to verify the accuracy of published locality.
Verification methodology, resolution of discrepancies, and preference for
alternative sources of data are left to the discretion of the feed consumer.
        </t>
        <t>
Consumers SHOULD decide on discrepancy thresholds and SHOULD flag for
administrative review feed entries which exceed set thresholds.
        </t>
      </section>

      <section title="Refreshing feed information">
        <t>
As a publisher can change geolocation data at any time and without
notification consumers SHOULD implement mechanisms to periodically refresh
local copies of feed data.  In the absence of any other refresh timing
information it is recommended that consumers SHOULD refresh feeds no less
often than weekly.
        </t>
        <t>
For feeds available via HTTPS (or HTTP), the publisher MAY communicate
refresh timing information by means of the standard HTTP expiration model
(<eref target="http://tools.ietf.org/html/rfc2616#section-13.2">section
13.2</eref> of <xref target="RFC2616"/>).  Specifically, publishers can
include either an
<eref target="http://tools.ietf.org/html/rfc2616#section-14.21">Expires
header</eref> or a
<eref target="http://tools.ietf.org/html/rfc2616#section-14.9">Cache-Control
header</eref> specifying the max-age.  Where practical, consumers SHOULD
refresh feed information before the expiry time is reached.
        </t>
      </section>
    </section>

    <section anchor="Security" title="Security Considerations">
      <t>
As there is no true security in the obscurity of the location of any given
IP address, self-publication of this data fundamentally opens no new attack
vectors.  For publishers, self-published data merely increases the ease with
which such location data might be exploited.
      </t>
      <t>
For consumers, feed retrieval processes may receive input from potentially
hostile sources (e.g. in the event of hijacked traffic).  As such, proper
input validation and defense measures MUST be taken.
      </t>
      <t>
Similarly, consumers who do not perform sufficient verification of published
data bear the same risks as from other forms of geolocation configuration
errors.
     </t>
    </section>

    <section anchor="Privacy" title="Privacy Considerations">
      <t>
Publishers of geolocation feeds are advised to have fully considered any
and all privacy implications of the disclosure of such information for the
users of the described networks prior to publication.  A thorough
comprehension of the
<eref target="http://tools.ietf.org/html/rfc6772#section-13">security
considerations</eref> of a chosen geolocation policy is highly recommended,
including an understanding of some of the
<eref target="http://tools.ietf.org/html/rfc6772#section-13.5">limitations
of information obscurity</eref> (see also <xref target="RFC6772"/>).
      </t>
      <t>
As noted in <xref target="spec"/>, each location field in an entry is
optional, in order to support expressing only the level of specificity which
the publisher has deemed acceptable.  There is no requirement that the level
of specificity be consistent across all entries within a feed.
In particular, the Postal Code field (<xref target="postal"/>) can provide
very specific geolocation, sometimes within a building.  Such specific
Postal Code values MUST NOT be published in geo feeds without the consent
of the parties being located.
      </t>
    </section>

    <section title="Relation to other work">
      <t>
While not originally done in conjunction with the <xref target="GEOPRIV"/>
working group, Richard Barnes observed that this work is nevertheless
consistent with that which the group has defined, both for address format and
for privacy.  The data elements in geolocation feeds are equivalent to the
following XML structure (vis. <xref target="RFC5139"/>):
        <figure>
          <artwork><![CDATA[
    <civicAddress>
      <country>country</country>
      <A1>region</A1>
      <A2>city</A2>
      <PC>postal_code</PC>
    </civicAddress>
          ]]></artwork>
        </figure>
      </t>
      <t>
Providing geolocation information to this granularity is equivalent to the
following privacy policy (vis. the definition of the
<eref
 target="http://tools.ietf.org/html/draft-ietf-geopriv-policy#section-6.5.1">
'building'</eref> level of disclosure):
        <figure>
          <artwork><![CDATA[
    <ruleset>
      <rule>
        <conditions/>
        <actions/>
        <transformations>
          <provide-location profile="civic-transformation">
            <provide-civic>building</provide-civic>
          </provide-location>
        </transformations>
      </rule>
    </ruleset>
          ]]></artwork>
        </figure>
      </t>
    </section>

    <section title="Acknowledgements">
      <t>
The authors would like to express their gratitude to reviewers and early
implementers, including but not limited to

    Mikael Abrahamsson,
    Ray Bellis,
    John Bond,
    Alissa Cooper,
    Andras Erdei,
    Marco Hogewoning,
    Mike Joseph,
    Warren Kumari,
    Menno Schepers,
    Justyna Sidorska,
    Pim van Pelt,
and
    Bjoern A. Zeeb.

Richard L. Barnes in particular contributed substantial review, text, and
advice.
      </t>
    </section>
  </middle>

  <back>
      <references title="Normative References">
&rfc2119;

&rfc2616;

&rfc3629;

&rfc4180;

&rfc4291;

&rfc4632;

<reference
 anchor="ISO.3166.1alpha2"
 target="http://www.iso.org/iso/home/standards/country_codes/iso-3166-1_decoding_table.htm">
  <front>
    <title>ISO 3166-1 decoding table</title>
    <author fullname="ISO 3166 Maintenance agency">
      <organization abbrev="ISO">
International Organization for Standardization
      </organization>
    </author>
  </front>
</reference>

<reference
 anchor="ISO.3166.2"
 target="http://www.iso.org/iso/home/standards/country_codes.htm#2012_iso3166-2"
 >
  <front>
    <title>ISO 3166-2:2007</title>
    <author fullname="ISO 3166 Maintenance agency">
      <organization abbrev="ISO">
International Organization for Standardization
      </organization>
    </author>
  </front>
</reference>
      </references>

      <references title="Informative References">
&rfc2818;

&rfc3053;

&rfc3403;

&rfc3912;

&rfc4408;

&rfc4627;

&rfc4848;

&rfc5139;

&rfc5952;

&rfc6772;

<reference target="https://meetings.ripe.net/geo/google.csv"
           anchor="GEO_RIPE_NCC">
  <front>
    <title>RIPE NCC Meeting Geolocation Data</title>
    <author fullname="Menno Schepers" initials="M." surname="Schepers">
      <organization abbrev="RIPE NCC">
Réseaux IP Européens Network Coordination Centre
      </organization>
    </author>
  </front>
</reference>

<reference target="https://registration.icann.org/geo/google.csv"
           anchor="GEO_ICANN">
  <front>
    <title>ICANN Meeting Geolocation Data</title>
    <author>
      <organization abbrev="ICANN">
Internet Corporation For Assigned Names and Numbers
      </organization>
    </author>
  </front>
</reference>

<reference target="https://www.sixxs.net/export/google/"
           anchor="GEO_SIXXS">
  <front>
    <title>SixXS Geolocation Data</title>
    <author fullname="Pim van Pelt" initials="P." surname="van Pelt">
      <organization abbrev="SixXS">
SixXS IPv6 Deployment and Tunnel Broker
      </organization>
    </author>
  </front>
</reference>

<reference target="http://datatracker.ietf.org/wg/geopriv/"
           anchor="GEOPRIV">
  <front>
    <title>IETF geopriv Working Group</title>
    <author>
      <organization abbrev="IETF">
Internet Engineering Task Force
      </organization>
    </author>
  </front>
</reference>

<reference target="http://code.google.com/p/ipaddr-py/"
           anchor="IPADDR_PY">
  <front>
    <title>Python IP address manipulation library</title>
    <author fullname="Mike Shields" initials="M." surname="Shields">
      <organization abbrev="Google">
Google Inc.
      </organization>
    </author>
    <author fullname="Peter Moody" initials="P." surname="Moody">
      <organization abbrev="Google">
Google Inc.
      </organization>
    </author>
  </front>
</reference>
      </references>

      <section title="Sample Python validation code">
        <t>
Included here is a simple format validator in Python for self-published ipgeo
feeds.  This tool reads CSV data in the self-published ipgeo feed format from
the standard input and performs basic validation.  It is intended for
use by feed publishers before launching a feed.  Note that this validator does
not verify the uniqueness of every IP prefix entry within the feed as a whole,
but only verifies the syntax of each single line from within the feed.  A
complete validator MUST also ensure IP prefix uniqueness.
        </t>
        <t>
The main source file "ipgeo_feed_validator.py" follows.  It requires use of the
open source ipaddr Python library for IP address and CIDR parsing and
validation <xref target="IPADDR_PY"/>.
        </t>
        <t>
          <figure>
            <artwork><![CDATA[
#!/usr/bin/python
#
# Copyright (c) 2012 IETF Trust and the persons identified as authors of
# the code.  All rights reserved.  Redistribution and use in source and
# binary forms, with or without modification, is permitted pursuant to,
# and subject to the license terms contained in, the Simplified BSD
# License set forth in Section 4.c of the IETF Trust's Legal Provisions
# Relating to IETF Documents (http://trustee.ietf.org/license-info).


"""Simple format validator for self-published ipgeo feeds.

This tool reads CSV data in the self-published ipgeo feed format from
the standard input and performs basic validation.  It is intended for
use by feed publishers before launching a feed.
"""

import csv
import ipaddr
import re
import sys


class IPGeoFeedValidator(object):
  def __init__(self):
    self.ranges = {}
    self.line_number = 0
    self.output_log = {}
    self.SetOutputStream(sys.stderr)

  def Validate(self, feed):
    """Check validity of an IPGeo feed.

    Args:
      feed: iterable with feed lines
    """

    for line in feed:
      self._ValidateLine(line)

  def SetOutputStream(self, logfile):
    """Controls where the output messages go do (STDERR by default).

    Use None to disable logging.

    Args:
      logfile: a file object (e.g., sys.stdout or sys.stderr) or None.
    """
    self.output_stream = logfile

  def CountErrors(self, severity):
    """How many ERRORs or WARNINGs were generated."""
    return len(self.output_log.get(severity, []))

  ############################################################
  def _ValidateLine(self, line):
    line = line.rstrip('\r\n')
    self.line_number += 1
    self.line = line
    self.is_correct_line = True

    if self._ShouldIgnoreLine(line):
      return

    fields = [field for field in csv.reader([line])][0]

    self._ValidateFields(fields)
    self._FlushOutputStream()

  def _ShouldIgnoreLine(self, line):
    line = line.strip()
    return len(line) == 0 or line.startswith('#')

  ############################################################
  def _ValidateFields(self, fields):
    assert(len(fields) > 0)

    is_correct = self._IsIPAddressOrRangeCorrect(fields[0])

    if len(fields) > 1:
      if not self._IsCountryCode2Correct(fields[1]):
        is_correct = False

    if len(fields) > 2 and not self._IsRegionCodeCorrect(fields[2]):
      is_correct = False

    if len(fields) != 5:
      self._ReportWarning('5 fields were expected (got %d).'
                          % len(fields))

  ############################################################
  def _IsIPAddressOrRangeCorrect(self, field):
    if '/' in field:
      return self._IsCIDRCorrect(field)
    return self._IsIPAddressCorrect(field)

  def _IsCIDRCorrect(self, cidr):
    try:
      iprange = ipaddr.IPNetwork(cidr)
      if iprange.network._ip != iprange._ip:
        self._ReportError('Incorrect IP Network.')
        return False
      if iprange.is_private:
        self._ReportError('IP Address must not be private.')
        return False
    except:
      self._ReportError('Incorrect IP Network.')
      return False
    return True

  def _IsIPAddressCorrect(self, ipaddress):
    try:
      ip = ipaddr.IPAddress(ipaddress)
    except:
      self._ReportError('Incorrect IP Address.')
      return False
    if ip.is_private:
      self._ReportError('IP Address must not be private.')
      return False
    return True

  ############################################################
  def _IsCountryCode2Correct(self, country_code_2):
    if len(country_code_2) == 0:
      return True
    if len(country_code_2) != 2 or not country_code_2.isalpha():
      self._ReportError(
          'Country code must be in the ISO 3166-1 alpha 2 format.')
      return False
    return True

  def _IsRegionCodeCorrect(self, region_code):
    if len(region_code) == 0:
      return True
    if '-' not in region_code:
      self._ReportError('Region code must be in the ISO 3166-2 format.')
      return False

    parts = region_code.split('-')
    if not self._IsCountryCode2Correct(parts[0]):
      return False
    return True

  ############################################################
  def _ReportError(self, message):
    self._ReportWithSeverity('ERROR', message)

  def _ReportWarning(self, message):
    self._ReportWithSeverity('WARNING', message)

  def _ReportWithSeverity(self, severity, message):
    self.is_correct_line = False
    output_line = '%s: %s\n' % (severity, message)

    if severity not in self.output_log:
      self.output_log[severity] = []
    self.output_log[severity].append(output_line)

    if self.output_stream is not None:
      self.output_stream.write(output_line)

  def _FlushOutputStream(self):
    if self.is_correct_line: return
    if self.output_stream is None: return

    self.output_stream.write('line %d: %s\n\n'
                             % (self.line_number, self.line))


############################################################
def main():
   feed_validator = IPGeoFeedValidator()
   feed_validator.Validate(sys.stdin)

   if feed_validator.CountErrors('ERROR'):
     sys.exit(1)

if __name__ == '__main__':
  main()
            ]]></artwork>
          </figure>
        </t>
        <t>
A unit test file, "ipgeo_feed_validator_test.py" is provided as well.  It
provides basic test coverage of the code above, though does not test correct
handling of non-ASCII UTF-8 strings.
        </t>
        <t>
          <figure>
            <artwork><![CDATA[
#!/usr/bin/python
#
# Copyright (c) 2012 IETF Trust and the persons identified as authors of
# the code.  All rights reserved.  Redistribution and use in source and
# binary forms, with or without modification, is permitted pursuant to,
# and subject to the license terms contained in, the Simplified BSD
# License set forth in Section 4.c of the IETF Trust's Legal Provisions
# Relating to IETF Documents (http://trustee.ietf.org/license-info).

import sys
from ipgeo_feed_validator import IPGeoFeedValidator

class IPGeoFeedValidatorTest(object):
  def __init__(self):
    self.validator = IPGeoFeedValidator()
    self.validator.SetOutputStream(None)
    self.successes = 0
    self.failures = 0

  def Run(self):
    self.TestFeedLine('# asdf', 0, 0)
    self.TestFeedLine('   ', 0, 0)
    self.TestFeedLine('', 0, 0)

    self.TestFeedLine('asdf', 1, 1)
    self.TestFeedLine('asdf,US,,,', 1, 0)
    self.TestFeedLine('aaaa::,US,,,', 0, 0)
    self.TestFeedLine('zzzz::,US', 1, 1)
    self.TestFeedLine(',US,,,', 1, 0)
    self.TestFeedLine('55.66.77', 1, 1)
    self.TestFeedLine('55.66.77.888', 1, 1)
    self.TestFeedLine('55.66.77.asdf', 1, 1)

    self.TestFeedLine('2001:db8:cafe::/48,PL,PL-MZ,,02-784', 0, 0)
    self.TestFeedLine('2001:db8:cafe::/48', 0, 1)

    self.TestFeedLine('55.66.77.88,PL', 0, 1)
    self.TestFeedLine('55.66.77.88,PL,,,', 0, 0)
    self.TestFeedLine('55.66.77.88,,,,', 0, 0)
    self.TestFeedLine('55.66.77.88,ZZ,,,', 0, 0)
    self.TestFeedLine('55.66.77.88,US,,,', 0, 0)
    self.TestFeedLine('55.66.77.88,USA,,,', 1, 0)
    self.TestFeedLine('55.66.77.88,99,,,', 1, 0)

    self.TestFeedLine('55.66.77.88,US,US-CA,,', 0, 0)
    self.TestFeedLine('55.66.77.88,US,USA-CA,,', 1, 0)
    self.TestFeedLine('55.66.77.88,USA,USA-CA,,', 2, 0)

    self.TestFeedLine('55.66.77.88,US,US-CA,Mountain View,', 0, 0)
    self.TestFeedLine('55.66.77.88,US,US-CA,Mountain View,94043', 0, 0)
    self.TestFeedLine('55.66.77.88,US,US-CA,Mountain View,94043,'
                      '1600 Ampthitheatre Parkway', 0, 1)

    self.TestFeedLine('55.66.77.0/24,US,,,', 0, 0)
    self.TestFeedLine('55.66.77.88/24,US,,,', 1, 0)
    self.TestFeedLine('55.66.77.88/32,US,,,', 0, 0)
    self.TestFeedLine('55.66.77/24,US,,,', 1, 0)
    self.TestFeedLine('55.66.77.0/35,US,,,', 1, 0)

    self.TestFeedLine('172.15.30.1,US,,,', 0, 0)
    self.TestFeedLine('172.28.30.1,US,,,', 1, 0)
    self.TestFeedLine('192.167.100.1,US,,,', 0, 0)
    self.TestFeedLine('192.168.100.1,US,,,', 1, 0)
    self.TestFeedLine('10.0.5.9,US,,,', 1, 0)
    self.TestFeedLine('10.0.5.0/24,US,,,', 1, 0)
    self.TestFeedLine('fc00::/48,PL,,,', 1, 0)
    self.TestFeedLine('fe00::/48,PL,,,', 0, 0)

    print '%d tests passed, %d failed' % (self.successes, self.failures)

  def IsOutputLogCorrectAtSeverity(self, severity, expected_msg_count):
    msg_count = self.validator.CountErrors(severity)

    if msg_count != expected_msg_count:
      print 'TEST FAILED: %s\nexpected %d %s[s], observed %d\n%s\n' % (
          self.validator.line, expected_sg_count, severity, msg_count,
          str(self.validator.output_log[severity]))
      return False
    return True

  def IsOutputLogCorrect(self, new_errors, new_warnings):
    retval = True

    if not self.IsOutputLogCorrectAtSeverity('ERROR', new_errors):
      retval = False
    if not self.IsOutputLogCorrectAtSeverity('WARNING', new_warnings):
      retval = False

    return retval

  def TestFeedLine(self, line, warning_count, error_count):
    self.validator.output_log['WARNING'] = []
    self.validator.output_log['ERROR'] = []
    self.validator._ValidateLine(line)

    if not self.IsOutputLogCorrect(warning_count, error_count):
      self.failures += 1
      return False

    self.successes += 1
    return True


if __name__ == '__main__':
  IPGeoFeedValidatorTest().Run()
            ]]></artwork>
          </figure>
        </t>
      </section>
  </back>
</rfc>
