The GNU Name System (GNS)

The GNU Name System (GNS) is a decentralized database that enables users to securely resolve names to values. Names can be used to identify other users (for example, in social networking), or network services (for example, VPN services running at a peer in GNUnet, or purely IP-based services on the Internet). Users interact with GNS by typing in a hostname that ends in ".gnu" or ".zkey".

Videos giving an overview of most of the GNS and the motivations behind it is available here and here. The remainder of this chapter targets developers that are familiar with high level concepts of GNS as presented in these talks.

GNS-aware applications should use the GNS resolver to obtain the respective records that are stored under that name in GNS. Each record consists of a type, value, expiration time and flags.

The type specifies the format of the value. Types below 65536 correspond to DNS record types, larger values are used for GNS-specific records. Applications can define new GNS record types by reserving a number and implementing a plugin (which mostly needs to convert the binary value representation to a human-readable text format and vice-versa). The expiration time specifies how long the record is to be valid. The GNS API ensures that applications are only given non-expired values. The flags are typically irrelevant for applications, as GNS uses them internally to control visibility and validity of records.

Records are stored along with a signature. The signature is generated using the private key of the authoritative zone. This allows any GNS resolver to verify the correctness of a name-value mapping.

Internally, GNS uses the NAMECACHE to cache information obtained from other users, the NAMESTORE to store information specific to the local users, and the DHT to exchange data between users. A plugin API is used to enable applications to define new GNS record types.

libgnunetgns

The GNS API itself is extremely simple. Clients first connec to the GNS service using GNUNET_GNS_connect. They can then perform lookups using GNUNET_GNS_lookup or cancel pending lookups using GNUNET_GNS_lookup_cancel. Once finished, clients disconnect using GNUNET_GNS_disconnect.

Looking up records

GNUNET_GNS_lookup takes a number of arguments:

handle
This is simply the GNS connection handle from GNUNET_GNS_connect.
name
The client needs to specify the name to be resolved. This can be any valid DNS or GNS hostname.
zone
The client needs to specify the public key of the GNS zone against which the resolution should be done (the ".gnu" zone). Note that a key must be provided, even if the name ends in ".zkey". This should typically be the public key of the master-zone of the user.
type
This is the desired GNS or DNS record type to look for. While all records for the given name will be returned, this can be important if the client wants to resolve record types that themselves delegate resolution, such as CNAME, PKEY or GNS2DNS. Resolving a record of any of these types will only work if the respective record type is specified in the request, as the GNS resolver will otherwise follow the delegation and return the records from the respective destination, instead of the delegating record.
only_cached
This argument should typically be set to GNUNET_NO. Setting it to GNUNET_YES disables resolution via the overlay network.
shorten_zone_key
If GNS encounters new names during resolution, their respective zones can automatically be learned and added to the "shorten zone". If this is desired, clients must pass the private key of the shorten zone. If NULL is passed, shortening is disabled.
proc
This argument identifies the function to call with the result. It is given proc_cls, the number of records found (possilby zero) and the array of the records as arguments. proc will only be called once. After proc,> has been called, the lookup must no longer be cancelled.
proc_cls
The closure for proc.

Accessing the records

The libgnunetgnsrecord library provides an API to manipulate the GNS record array that is given to proc. In particular, it offers functions such as converting record values to human-readable strings (and back). However, most libgnunetgnsrecord functions are not interesting to GNS client applications.

For DNS records, the libgnunetdnsparser library provides functions for parsing (and serializing) common types of DNS records.

Creating records

Creating GNS records is typically done by building the respective record information (possibly with the help of libgnunetgnsrecord and libgnunetdnsparser) and then using the libgnunetnamestore to publish the information. The GNS API is not involved in this operation.

Future work

In the future, we want to expand libgnunetgns to allow applications to observe shortening operations performed during GNS resolution, for example so that users can receive visual feedback when this happens.

libgnunetgnsrecord

The libgnunetgnsrecord library is used to manipulate GNS records (in plaintext or in their encrypted format). Applications mostly interact with libgnunetgnsrecord by using the functions to convert GNS record values to strings or vice-versa, or to lookup a GNS record type number by name (or vice-versa). The library also provides various other functions that are mostly used internally within GNS, such as converting keys to names, checking for expiration, encrypting GNS records to GNS blocks, verifying GNS block signatures and decrypting GNS records from GNS blocks.

We will now discuss the four commonly used functions of the API.
libgnunetgnsrecord does not perform these operations itself, but instead uses plugins to perform the operation. GNUnet includes plugins to support common DNS record types as well as standard GNS record types.

Value handling

GNUNET_GNSRECORD_value_to_string can be used to convert the (binary) representation of a GNS record value to a human readable, 0-terminated UTF-8 string. NULL is returned if the specified record type is not supported by any available plugin.

GNUNET_GNSRECORD_string_to_value can be used to try to convert a human readable string to the respective (binary) representation of a GNS record value.

Type handling

GNUNET_GNSRECORD_typename_to_number can be used to obtain the numeric value associated with a given typename. For example, given the typename "A" (for DNS A reocrds), the function will return the number 1. A list of common DNS record types is here. Note that not all DNS record types are supported by GNUnet GNSRECORD plugins at this time.

GNUNET_GNSRECORD_number_to_typename can be used to obtain the typename associated with a given numeric value. For example, given the type number 1, the function will return the typename "A".

GNS plugins

Adding a new GNS record type typically involves writing (or extending) a GNSRECORD plugin. The plugin needs to implement the gnunet_gnsrecord_plugin.h API which provides basic functions that are needed by GNSRECORD to convert typenames and values of the respective record type to strings (and back). These gnsrecord plugins are typically implemented within their respective subsystems. Examples for such plugins can be found in the GNSRECORD, GNS and CONVERSATION subsystems.

The libgnunetgnsrecord library is then used to locate, load and query the appropriate gnsrecord plugin. Which plugin is appropriate is determined by the record type (which is just a 32-bit integer). The libgnunetgnsrecord library loads all block plugins that are installed at the local peer and forwards the application request to the plugins. If the record type is not supported by the plugin, it should simply return an error code.

The central functions of the block APIs (plugin and main library) are the same four functions for converting between values and strings, and typenames and numbers documented in the previous section.

The GNS Client-Service Protocol

The GNS client-service protocol consists of two simple messages, the LOOKUP message and the LOOKUP_RESULT. Each LOOKUP message contains a unique 32-bit identifier, which will be included in the corresponding response. Thus, clients can send many lookup requests in parallel and receive responses out-of-order. A LOOKUP request also includes the public key of the GNS zone, the desired record type and fields specifying whether shortening is enabled or networking is disabled. Finally, the LOOKUP message includes the name to be resolved.

The response includes the number of records and the records themselves in the format created by GNUNET_GNSRECORD_records_serialize. They can thus be deserialized using GNUNET_GNSRECORD_records_deserialize.

Hijacking the DNS-Traffic using gnunet-service-dns

This section documents how the gnunet-service-dns (and the gnunet-helper-dns) intercepts DNS queries from the local system.
This is merely one method for how we can obtain GNS queries. It is also possible to change resolv.conf to point to a machine running gnunet-dns2gns or to modify libc's name system switch (NSS) configuration to include a GNS resolution plugin. The method described in this chaper is more of a last-ditch catch-all approach.

gnunet-service-dns enables intercepting DNS traffic using policy based routing. We MARK every outgoing DNS-packet if it was not sent by our application. Using a second routing table in the Linux kernel these marked packets are then routed through our virtual network interface and can thus be captured unchanged.

Our application then reads the query and decides how to handle it: A query to an address ending in ".gnu" or ".zkey" is hijacked by gnunet-service-gns and resolved internally using GNS. In the future, a reverse query for an address of the configured virtual network could be answered with records kept about previous forward queries. Queries that are not hijacked by some application using the DNS service will be sent to the original recipient. The answer to the query will always be sent back through the virtual interface with the original nameserver as source address.

Network Setup Details

The DNS interceptor adds the following rules to the Linux kernel:

iptables -t mangle -I OUTPUT 1 -p udp --sport $LOCALPORT --dport 53 -j ACCEPT
iptables -t mangle -I OUTPUT 2 -p udp --dport 53 -j MARK --set-mark 3
ip rule add fwmark 3 table2
ip route add default via $VIRTUALDNS table2

Line 1 makes sure that all packets coming from a port our application opened beforehand ($LOCALPORT) will be routed normally. Line 2 marks every other packet to a DNS-Server with mark 3 (chosen arbitrarily). The third line adds a routing policy based on this mark 3 via the routing table.

Serving DNS lookups via GNS on W32

This section documents how the libw32nsp (and gnunet-gns-helper-service-w32) do DNS resolutions of DNS queries on the local system. This only applies to GNUnet running on W32.

W32 has a concept of "Namespaces" and "Namespace providers". These are used to present various name systems to applications in a generic way. Namespaces include DNS, mDNS, NLA and others. For each namespace any number of providers could be registered, and they are queried in an order of priority (which is adjustable).

Applications can resolve names by using WSALookupService*() family of functions.

However, these are WSA-only facilities. Common BSD socket functions for namespace resolutions are gethostbyname and getaddrinfo (among others). These functions are implemented internally (by default - by mswsock, which also implements the default DNS provider) as wrappers around WSALookupService*() functions (see "Sample Code for a Service Provider" on MSDN).

On W32 GNUnet builds a libw32nsp - a namespace provider, which can then be installed into the system by using w32nsp-install (and uninstalled by w32nsp-uninstall), as described in "Installation Handbook".

libw32nsp is very simple and has almost no dependencies. As a response to NSPLookupServiceBegin(), it only checks that the provider GUID passed to it by the caller matches GNUnet DNS Provider GUID, checks that name being resolved ends in ".gnu" or ".zkey", then connects to gnunet-gns-helper-service-w32 at 127.0.0.1:5353 (hardcoded) and sends the name resolution request there, returning the connected socket to the caller.

When the caller invokes NSPLookupServiceNext(), libw32nsp reads a completely formed reply from that socket, unmarshalls it, then gives it back to the caller.

At the moment gnunet-gns-helper-service-w32 is implemented to ever give only one reply, and subsequent calls to NSPLookupServiceNext() will fail with WSA_NODATA (first call to NSPLookupServiceNext() might also fail if GNS failed to find the name, or there was an error connecting to it).

gnunet-gns-helper-service-w32 does most of the processing:

  • Maintains a connection to GNS.
  • Reads GNS config and loads appropriate keys.
  • Checks service GUID and decides on the type of record to look up, refusing to make a lookup outright when unsupported service GUID is passed.
  • Launches the lookup

When lookup result arrives, gnunet-gns-helper-service-w32 forms a complete reply (including filling a WSAQUERYSETW structure and, possibly, a binary blob with a hostent structure for gethostbyname() client), marshalls it, and sends it back to libw32nsp. If no records were found, it sends an empty header.

This works for most normal applications that use gethostbyname() or getaddrinfo() to resolve names, but fails to do anything with applications that use alternative means of resolving names (such as sending queries to a DNS server directly by themselves). This includes some of well known utilities, like "ping" and "nslookup".