logo Netzschleuder network catalog, repository, and centrifuge

History of peeringdb

The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.

[entries] Add the date when each entry was added, and remove icon_hash by Tiago de Paula Peixoto beff7c9
The dates are those of the commits that first added the entries' files
(2020-08-11 for the entries of the initial commit); renamed entries keep
the date of their first name.

Changed: none
diff --git a/entries/peeringdb/__init__.py b/entries/peeringdb/__init__.py
index d7abeef..7841317 100644
--- a/entries/peeringdb/__init__.py
+++ b/entries/peeringdb/__init__.py
@@ -117,7 +117,7 @@ citation = [('A. Lodhi, N. Larson, A. Dhamdhere, C. Dovrolis and k. claffy, "Usi
              'https://doi.org/10.1145/2602204.2602208'),
             ('CAIDA, "PeeringDB archive." (2010-2026)',
              'https://catalog.caida.org/dataset/peeringdb_archive')]
-icon_hash = None
+date_added = '2026-10-31'
 upstream_license = ("CAIDA Acceptable Use Agreement for publicly accessible datasets",
                     "https://www.caida.org/about/legal/aua/public_aua/")
 
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto bc3dbbd
The lists of properties at the end of the descriptions marked edge
properties with "Edge" but left node properties unmarked, or grouped
them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property
was often unclear. Every item now starts with (Node), (Edge) or (Graph),
followed by the property names, a colon and the explanation, one item
per property or group of properties; qualifications (kinds of nodes,
networks) follow the names in parentheses. Properties that were only
named got a short explanation, and a few items that named the wrong
kind of property were corrected.
diff --git a/entries/peeringdb/__init__.py b/entries/peeringdb/__init__.py
index 067cfec..d7abeef 100644
--- a/entries/peeringdb/__init__.py
+++ b/entries/peeringdb/__init__.py
@@ -89,26 +89,26 @@ Nodes are networks (as registered, each with its AS number), IXPs, facilities an
 
 The networks have the following properties:
 
-- `kind`: `network`, `ix`, `facility` or `organization`.
-- `name`: the name of the record.
-- `peeringdb_id`: the identifier of the record in PeeringDB (of its kind; the identifiers of the older format differ from those of the newer one).
-- `asn` (networks): the AS number (-1 for other nodes, or if not given).
-- `aka` (networks): other names of the network.
-- `info_type` (networks): the type of network (e.g. `NSP`, `Content`, `Cable/DSL/ISP`, `Enterprise`, `Educational/Research`, `Non-Profit`, `Route Server`).
-- `info_scope` (networks): the geographic scope (e.g. `Global`, `Regional`, `Europe`).
-- `info_traffic`, `info_ratio` (networks): the traffic level and the traffic ratio (inbound/outbound) declared by the network.
-- `policy_general` (networks): the general peering policy (`Open`, `Selective`, `Restrictive`, `No`).
-- `irr_as_set` (networks): the AS-SET of the network in the Internet Routing Registry.
-- `info_prefixes4`, `info_prefixes6` (networks): the numbers of IPv4 and IPv6 prefixes the network announces (the older format has one number, given as `info_prefixes4`; -1 if not given).
-- `city`, `country`: the city and country (ISO code) of IXPs, facilities and organizations.
-- `region_continent` (IXPs): the continent of the IXP.
-- `latitude`, `longitude` (facilities): the coordinates of the facility (only in the snapshots from 2018 on; NaN if not given).
-- `created`: the date of creation of the record (YYYY-MM-DD, empty if not given).
-- Edge `type`: `network-ix`, `network-facility`, `ix-facility` or `organization`.
-- Edge `speed` (`network-ix`): the speed of the port, in Mbit/s (-1 for other edges).
-- Edge `ipaddr4`, `ipaddr6` (`network-ix`): the IPv4 and IPv6 addresses of the port in the peering LAN of the IXP (in the older format, each address has an edge of its own).
-- Edge `is_rs_peer` (`network-ix`): whether the network peers with the route servers of the IXP (only in the newer format).
-- Graph property `snapshot`: the date of the snapshot (YYYY-MM-DD)."""
+- (Node) `kind`: `network`, `ix`, `facility` or `organization`.
+- (Node) `name`: the name of the record.
+- (Node) `peeringdb_id`: the identifier of the record in PeeringDB (of its kind; the identifiers of the older format differ from those of the newer one).
+- (Node) `asn` (networks): the AS number (-1 for other nodes, or if not given).
+- (Node) `aka` (networks): other names of the network.
+- (Node) `info_type` (networks): the type of network (e.g. `NSP`, `Content`, `Cable/DSL/ISP`, `Enterprise`, `Educational/Research`, `Non-Profit`, `Route Server`).
+- (Node) `info_scope` (networks): the geographic scope (e.g. `Global`, `Regional`, `Europe`).
+- (Node) `info_traffic`, `info_ratio` (networks): the traffic level and the traffic ratio (inbound/outbound) declared by the network.
+- (Node) `policy_general` (networks): the general peering policy (`Open`, `Selective`, `Restrictive`, `No`).
+- (Node) `irr_as_set` (networks): the AS-SET of the network in the Internet Routing Registry.
+- (Node) `info_prefixes4`, `info_prefixes6` (networks): the numbers of IPv4 and IPv6 prefixes the network announces (the older format has one number, given as `info_prefixes4`; -1 if not given).
+- (Node) `city`, `country`: the city and country (ISO code) of IXPs, facilities and organizations.
+- (Node) `region_continent` (IXPs): the continent of the IXP.
+- (Node) `latitude`, `longitude` (facilities): the coordinates of the facility (only in the snapshots from 2018 on; NaN if not given).
+- (Node) `created`: the date of creation of the record (YYYY-MM-DD, empty if not given).
+- (Edge) `type`: `network-ix`, `network-facility`, `ix-facility` or `organization`.
+- (Edge) `speed` (`network-ix`): the speed of the port, in Mbit/s (-1 for other edges).
+- (Edge) `ipaddr4`, `ipaddr6` (`network-ix`): the IPv4 and IPv6 addresses of the port in the peering LAN of the IXP (in the older format, each address has an edge of its own).
+- (Edge) `is_rs_peer` (`network-ix`): whether the network peers with the route servers of the IXP (only in the newer format).
+- (Graph) `snapshot`: the date of the snapshot (YYYY-MM-DD)."""
 tags = ['Technological', 'Unweighted', 'Multigraph', 'Metadata']
 url = ['https://catalog.caida.org/dataset/peeringdb_archive',
        'https://publicdata.caida.org/datasets/peeringdb/',
[entries] New entry: peeringdb by Tiago de Paula Peixoto f480d48
diff --git a/entries/peeringdb/__init__.py b/entries/peeringdb/__init__.py
new file mode 100644
index 0000000..067cfec
--- /dev/null
+++ b/entries/peeringdb/__init__.py
@@ -0,0 +1,330 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import sqlite3
+import tempfile
+
+from .. import *
+
+# The snapshots: the first one of each month of the archive (July 2010 to
+# October 2026), and the latest one
+SNAPSHOTS = ["2010_07_29", "2010_08_01", "2010_09_01", "2010_10_01",
+             "2010_11_01", "2010_12_01", "2011_01_01", "2011_02_01",
+             "2011_03_01", "2011_04_01", "2011_05_01", "2011_06_01",
+             "2011_07_01", "2011_08_01", "2011_09_01", "2011_10_01",
+             "2011_11_01", "2011_12_01", "2012_01_01", "2012_02_01",
+             "2012_03_01", "2012_04_01", "2012_05_01", "2012_06_01",
+             "2012_07_01", "2012_08_01", "2012_09_01", "2012_10_01",
+             "2012_11_01", "2012_12_01", "2013_01_01", "2013_02_01",
+             "2013_03_01", "2013_04_01", "2013_05_01", "2013_06_01",
+             "2013_07_01", "2013_08_01", "2013_09_01", "2013_10_01",
+             "2013_11_01", "2013_12_01", "2014_01_01", "2014_02_01",
+             "2014_03_01", "2014_04_01", "2014_05_01", "2014_06_01",
+             "2014_07_01", "2014_08_01", "2014_09_01", "2014_10_01",
+             "2014_11_01", "2014_12_01", "2015_01_01", "2015_02_01",
+             "2015_03_01", "2015_04_01", "2015_05_01", "2015_06_01",
+             "2015_07_01", "2015_08_01", "2015_09_01", "2015_10_01",
+             "2015_11_01", "2015_12_01", "2016_01_01", "2016_02_01",
+             "2016_03_01", "2016_05_27", "2016_06_01", "2016_07_01",
+             "2016_08_01", "2016_09_01", "2016_10_01", "2016_11_01",
+             "2016_12_01", "2017_01_01", "2017_02_01", "2017_03_01",
+             "2017_04_01", "2017_05_01", "2017_06_01", "2017_07_01",
+             "2017_08_01", "2017_09_01", "2017_10_01", "2017_11_01",
+             "2017_12_01", "2018_01_01", "2018_02_01", "2018_03_01",
+             "2018_04_01", "2018_05_01", "2018_06_01", "2018_07_01",
+             "2018_08_01", "2018_09_03", "2018_10_01", "2018_11_01",
+             "2018_12_01", "2019_01_01", "2019_02_01", "2019_03_01",
+             "2019_04_01", "2019_05_01", "2019_06_01", "2019_07_01",
+             "2019_08_01", "2019_09_07", "2019_10_01", "2019_11_01",
+             "2019_12_01", "2020_01_01", "2020_02_10", "2020_03_01",
+             "2020_04_01", "2020_05_01", "2020_06_01", "2020_07_01",
+             "2020_08_01", "2020_09_01", "2020_10_01", "2020_11_01",
+             "2020_12_01", "2021_01_01", "2021_02_01", "2021_03_01",
+             "2021_04_01", "2021_05_01", "2021_06_01", "2021_07_01",
+             "2021_08_01", "2021_09_01", "2021_10_01", "2021_11_01",
+             "2021_12_01", "2022_01_01", "2022_02_01", "2022_03_01",
+             "2022_04_01", "2022_05_01", "2022_06_01", "2022_07_01",
+             "2022_08_01", "2022_09_01", "2022_10_01", "2022_11_01",
+             "2022_12_01", "2023_01_01", "2023_02_01", "2023_03_01",
+             "2023_04_01", "2023_05_01", "2023_06_01", "2023_07_01",
+             "2023_08_01", "2023_09_01", "2023_10_01", "2023_11_01",
+             "2023_12_01", "2024_01_01", "2024_02_01", "2024_03_01",
+             "2024_04_01", "2024_05_01", "2024_06_01", "2024_07_01",
+             "2024_08_01", "2024_09_01", "2024_10_01", "2024_11_01",
+             "2024_12_01", "2025_01_01", "2025_02_01", "2025_03_01",
+             "2025_04_01", "2025_05_01", "2025_06_06", "2025_07_01",
+             "2025_08_01", "2025_09_01", "2025_10_01", "2025_11_01",
+             "2025_12_01", "2026_01_01", "2026_02_01", "2026_03_01",
+             "2026_04_01", "2026_05_01", "2026_06_01", "2026_07_01",
+             "2026_08_01", "2026_09_01", "2026_10_01", "2026_10_30"]
+
+def _fname(s):
+    y, m, d = s.split("_")
+    if s < "2016_04":
+        return f"{y}/{m}/peeringdb_dump_{s}.sqlite"
+    if s < "2018_03_11":
+        return f"{y}/{m}/peeringdb_2_dump_{s}.sqlite"
+    return f"{y}/{m}/peeringdb_2_dump_{s}.json"
+
+title = "PeeringDB interconnection network (2010-2026)"
+description = r"""Networks of the interconnection facilities of the Internet from PeeringDB, the database maintained by network operators in which they register their networks (autonomous systems), the Internet exchange points (IXPs) where they exchange traffic, and the colocation facilities (data centers) where they are present, from the daily snapshots archived by CAIDA since July 2010. There is one network per month, from the first snapshot of the month in the archive (from 29 July 2010 to 1 October 2026, 195 snapshots), and the latest snapshot (30 October 2026). The networks are named after the date of their snapshot (e.g. `2020_01_01`). As the archive's README explains, the snapshots from 22 January to 9 August 2017 are not fully up to date, and those from October 2017 to March 2018 repeat the data of 9 October 2017 (they are kept, as they are in the archive); there are no snapshots in April 2016.
+
+Nodes are networks (as registered, each with its AS number), IXPs, facilities and, from 2017 on, organizations (the companies that operate them; the older format of the database, used until March 2016, has no organizations). Undirected edges are of four types: a network's connection to an IXP (one edge per port, with its speed and IP addresses, so that pairs can have several edges), a network's presence in a facility, an IXP's presence in a facility, and the ownership of a network, IXP or facility by an organization. Only the records that are active (status `ok`, or approved in the older format) are included; edges with an end that is not an active record are left out. The contact data (points of contact, e-mail addresses and phone numbers), the notes, the IP prefixes of the IXPs and the other fields of the records are not included.
+
+The networks have the following properties:
+
+- `kind`: `network`, `ix`, `facility` or `organization`.
+- `name`: the name of the record.
+- `peeringdb_id`: the identifier of the record in PeeringDB (of its kind; the identifiers of the older format differ from those of the newer one).
+- `asn` (networks): the AS number (-1 for other nodes, or if not given).
+- `aka` (networks): other names of the network.
+- `info_type` (networks): the type of network (e.g. `NSP`, `Content`, `Cable/DSL/ISP`, `Enterprise`, `Educational/Research`, `Non-Profit`, `Route Server`).
+- `info_scope` (networks): the geographic scope (e.g. `Global`, `Regional`, `Europe`).
+- `info_traffic`, `info_ratio` (networks): the traffic level and the traffic ratio (inbound/outbound) declared by the network.
+- `policy_general` (networks): the general peering policy (`Open`, `Selective`, `Restrictive`, `No`).
+- `irr_as_set` (networks): the AS-SET of the network in the Internet Routing Registry.
+- `info_prefixes4`, `info_prefixes6` (networks): the numbers of IPv4 and IPv6 prefixes the network announces (the older format has one number, given as `info_prefixes4`; -1 if not given).
+- `city`, `country`: the city and country (ISO code) of IXPs, facilities and organizations.
+- `region_continent` (IXPs): the continent of the IXP.
+- `latitude`, `longitude` (facilities): the coordinates of the facility (only in the snapshots from 2018 on; NaN if not given).
+- `created`: the date of creation of the record (YYYY-MM-DD, empty if not given).
+- Edge `type`: `network-ix`, `network-facility`, `ix-facility` or `organization`.
+- Edge `speed` (`network-ix`): the speed of the port, in Mbit/s (-1 for other edges).
+- Edge `ipaddr4`, `ipaddr6` (`network-ix`): the IPv4 and IPv6 addresses of the port in the peering LAN of the IXP (in the older format, each address has an edge of its own).
+- Edge `is_rs_peer` (`network-ix`): whether the network peers with the route servers of the IXP (only in the newer format).
+- Graph property `snapshot`: the date of the snapshot (YYYY-MM-DD)."""
+tags = ['Technological', 'Unweighted', 'Multigraph', 'Metadata']
+url = ['https://catalog.caida.org/dataset/peeringdb_archive',
+       'https://publicdata.caida.org/datasets/peeringdb/',
+       'https://www.peeringdb.com/']
+citation = [('A. Lodhi, N. Larson, A. Dhamdhere, C. Dovrolis and k. claffy, "Using peeringDB to understand the peering ecosystem." ACM SIGCOMM Computer Communication Review 44(2), 20-27 (2014)',
+             'https://doi.org/10.1145/2602204.2602208'),
+            ('CAIDA, "PeeringDB archive." (2010-2026)',
+             'https://catalog.caida.org/dataset/peeringdb_archive')]
+icon_hash = None
+upstream_license = ("CAIDA Acceptable Use Agreement for publicly accessible datasets",
+                    "https://www.caida.org/about/legal/aua/public_aua/")
+
+upstream_prefix = 'https://publicdata.caida.org/datasets/peeringdb'
+files = [(_fname(s), s, None) for s in SNAPSHOTS]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+NODE_PROPS = [("kind", "string"), ("name", "string"), ("peeringdb_id", "int"),
+              ("asn", "int64_t"), ("aka", "string"), ("info_type", "string"),
+              ("info_scope", "string"), ("info_traffic", "string"),
+              ("info_ratio", "string"), ("policy_general", "string"),
+              ("irr_as_set", "string"), ("info_prefixes4", "int64_t"),
+              ("info_prefixes6", "int64_t"), ("city", "string"),
+              ("country", "string"), ("region_continent", "string"),
+              ("latitude", "double"), ("longitude", "double"),
+              ("created", "string")]
+NODE_DEFAULTS = {"asn": -1, "info_prefixes4": -1, "info_prefixes6": -1,
+                 "peeringdb_id": -1, "latitude": numpy.nan,
+                 "longitude": numpy.nan}
+EDGE_PROPS = [("type", "string"), ("speed", "int64_t"), ("ipaddr4", "string"),
+              ("ipaddr6", "string"), ("is_rs_peer", "bool")]
+
+NET_FIELDS = ["aka", "info_type", "info_scope", "info_traffic", "info_ratio",
+              "policy_general", "irr_as_set"]
+
+def _int(x, default=-1):
+    try:
+        return int(x)
+    except (TypeError, ValueError):
+        return default
+
+def _float(x):
+    try:
+        return float(x)
+    except (TypeError, ValueError):
+        return numpy.nan
+
+def _str(x):
+    return "" if x is None else str(x).strip()
+
+def _records_v1(db):
+    "Nodes and edges of a snapshot in the format of PeeringDB 1."
+    nodes = {}
+    edges = []
+    q = lambda sql: db.execute(sql).fetchall()
+    for r in q("select id, asn, name, " + ", ".join(
+            f for f in ["aka", "info_type", "info_scope", "info_traffic",
+                        "info_ratio", "policy_general", "irr_as_set",
+                        "info_prefixes", "date_created"]) +
+               " from peerParticipants"):
+        p = {"kind": "network", "peeringdb_id": r[0], "asn": _int(r[1]),
+             "name": _str(r[2]), "info_prefixes4": _int(r[10]),
+             "created": _str(r[11])[:10]}
+        p.update({k: _str(v) for k, v in zip(NET_FIELDS, r[3:10])})
+        nodes[("network", r[0])] = p
+    for r in q("select id, name, city, country, region_continent from mgmtPublics"
+               " where approved = 'Y'"):
+        nodes[("ix", r[0])] = {"kind": "ix", "peeringdb_id": r[0],
+                               "name": _str(r[1]), "city": _str(r[2]),
+                               "country": _str(r[3]),
+                               "region_continent": _str(r[4])}
+    for r in q("select id, name, city, country from mgmtFacilities"
+               " where approved = 'Y'"):
+        nodes[("facility", r[0])] = {"kind": "facility", "peeringdb_id": r[0],
+                                     "name": _str(r[1]), "city": _str(r[2]),
+                                     "country": _str(r[3])}
+    for r in q("select participant_id, public_id, local_ipaddr, speed, protocol"
+               " from peerParticipantsPublics where pending = '0'"
+               " order by id"):
+        v6 = _str(r[4]).lower() == "ipv6"
+        edges.append((("network", r[0]), ("ix", r[1]),
+                      {"type": "network-ix", "speed": _int(r[3]),
+                       "ipaddr4": "" if v6 else _str(r[2]),
+                       "ipaddr6": _str(r[2]) if v6 else ""}))
+    for r in q("select participant_id, facility_id from peerParticipantsPrivates"
+               " order by id"):
+        edges.append((("network", r[0]), ("facility", r[1]),
+                      {"type": "network-facility"}))
+    for r in q("select public_id, facility_id from mgmtPublicsFacilities"
+               " order by id"):
+        edges.append((("ix", r[0]), ("facility", r[1]), {"type": "ix-facility"}))
+    return nodes, edges
+
+def _records_v2(tables):
+    """Nodes and edges of a snapshot in the format of PeeringDB 2, from its
+    tables (lists of dicts) `net`, `ix`, `fac`, `org`, `ixlan`, `netixlan`,
+    `netfac`, `ixfac`."""
+    nodes = {}
+    edges = []
+    ok = lambda t: [r for r in tables[t] if r.get("status", "ok") == "ok"]
+    for r in ok("org"):
+        nodes[("organization", r["id"])] = {
+            "kind": "organization", "peeringdb_id": r["id"],
+            "name": _str(r.get("name")), "city": _str(r.get("city")),
+            "country": _str(r.get("country")),
+            "created": _str(r.get("created"))[:10]}
+    for r in ok("net"):
+        p = {"kind": "network", "peeringdb_id": r["id"],
+             "asn": _int(r.get("asn")), "name": _str(r.get("name")),
+             "info_prefixes4": _int(r.get("info_prefixes4")),
+             "info_prefixes6": _int(r.get("info_prefixes6")),
+             "created": _str(r.get("created"))[:10]}
+        p.update({k: _str(r.get(k)) for k in NET_FIELDS})
+        nodes[("network", r["id"])] = p
+        edges.append((("organization", r.get("org_id")), ("network", r["id"]),
+                      {"type": "organization"}))
+    for r in ok("ix"):
+        nodes[("ix", r["id"])] = {
+            "kind": "ix", "peeringdb_id": r["id"], "name": _str(r.get("name")),
+            "city": _str(r.get("city")), "country": _str(r.get("country")),
+            "region_continent": _str(r.get("region_continent")),
+            "created": _str(r.get("created"))[:10]}
+        edges.append((("organization", r.get("org_id")), ("ix", r["id"]),
+                      {"type": "organization"}))
+    for r in ok("fac"):
+        nodes[("facility", r["id"])] = {
+            "kind": "facility", "peeringdb_id": r["id"],
+            "name": _str(r.get("name")), "city": _str(r.get("city")),
+            "country": _str(r.get("country")),
+            "latitude": _float(r.get("latitude")),
+            "longitude": _float(r.get("longitude")),
+            "created": _str(r.get("created"))[:10]}
+        edges.append((("organization", r.get("org_id")), ("facility", r["id"]),
+                      {"type": "organization"}))
+    ixlan_ix = {r["id"]: r["ix_id"] for r in ok("ixlan")}
+    for r in sorted(ok("netixlan"), key=lambda r: r["id"]):
+        ix = r.get("ix_id", ixlan_ix.get(r.get("ixlan_id")))
+        edges.append((("network", r["net_id"]), ("ix", ix),
+                      {"type": "network-ix", "speed": _int(r.get("speed")),
+                       "ipaddr4": _str(r.get("ipaddr4")),
+                       "ipaddr6": _str(r.get("ipaddr6")),
+                       "is_rs_peer": bool(r.get("is_rs_peer"))}))
+    for r in sorted(ok("netfac"), key=lambda r: r["id"]):
+        edges.append((("network", r["net_id"]), ("facility", r["fac_id"]),
+                      {"type": "network-facility"}))
+    for r in sorted(ok("ixfac"), key=lambda r: r["id"]):
+        edges.append((("ix", r["ix_id"]), ("facility", r["fac_id"]),
+                      {"type": "ix-facility"}))
+    return nodes, edges
+
+# For checks only: if set, only this many edges of each snapshot are kept
+_check_rows = None
+
+V2_TABLES = {"net": "peeringdb_network", "ix": "peeringdb_ix",
+             "fac": "peeringdb_facility", "org": "peeringdb_organization",
+             "ixlan": "peeringdb_ixlan", "netixlan": "peeringdb_network_ixlan",
+             "netfac": "peeringdb_network_facility",
+             "ixfac": "peeringdb_ix_facility"}
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    for fname, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        if fname.endswith(".json"):
+            with open_upstream_file(name, fname, "rb") as f:
+                d = json.load(f)
+            nodes, edges = _records_v2({k: d[k]["data"] for k in V2_TABLES})
+            del d
+        else:
+            # (sqlite needs a file: the stored copy is decompressed to a
+            # temporary file)
+            os.makedirs("cache/temp", exist_ok=True)
+            with tempfile.NamedTemporaryFile(dir="cache/temp") as tmp:
+                with open_upstream_file(name, fname, "rb") as f:
+                    shutil.copyfileobj(f, tmp, 1 << 24)
+                tmp.flush()
+                db = sqlite3.connect(f"file:{tmp.name}?mode=ro", uri=True)
+                db.row_factory = None
+                if "v2" in fname or "_2_dump" in fname:
+                    def table(t):
+                        cur = db.execute(f"select * from {t}")
+                        cols = [c[0] for c in cur.description]
+                        return [dict(zip(cols, r)) for r in cur]
+                    nodes, edges = _records_v2({k: table(t) for k, t
+                                                in V2_TABLES.items()})
+                else:
+                    nodes, edges = _records_v1(db)
+                db.close()
+
+        if _check_rows is not None:
+            edges = edges[:_check_rows]
+        g = Graph(directed=False)
+        keys = sorted(nodes, key=lambda k: (["network", "ix", "facility",
+                                             "organization"].index(k[0]), k[1]))
+        vmap = {k: i for i, k in enumerate(keys)}
+        g.add_vertex(len(keys))
+        for k, t in NODE_PROPS:
+            p = g.vp[k] = g.new_vp(t)
+            default = NODE_DEFAULTS.get(k, "")
+            if "string" in t:
+                for i, key in enumerate(keys):
+                    p[i] = nodes[key].get(k, default)
+            else:
+                p.a = [nodes[key].get(k, default) for key in keys]
+        for k, t in EDGE_PROPS:
+            g.ep[k] = g.new_ep(t)
+        el = [(vmap[s], vmap[t], p["type"], p.get("speed", -1),
+               p.get("ipaddr4", ""), p.get("ipaddr6", ""),
+               p.get("is_rs_peer", False))
+              for s, t, p in edges if s in vmap and t in vmap]
+        g.add_edge_list(el, eprops=[g.ep[k] for k, t in EDGE_PROPS])
+        g.gp.snapshot = g.new_gp("string", alt.replace("_", "-"))
+        yield alt, g