# Split Horizon

The Split Horizon app provides two distinct features which can be used independently:

1. Create `APP` records in primary and forwarder zones that can return different sets of `A`, `AAAA`, or `CNAME` records for clients querying over public, private, or other specified networks.

1. Translate IP addresses in a DNS response for `A` and `AAAA` type requests based on the client's network address and the configured 1:1 translation.

The following sections describe each feature in more detail.

## A / AAAA / CNAME

To respond with different `A`, `AAAA`, or `CNAME` records to different clients, create an `APP` record with the respective name in a primary or forwarder zone. Select the `Split Horizon` app and the `SplitHorizon.SimpleAddress` class for `A` and `AAAA` records or `SplitHorizon.SimpleCNAME` for `CNAME` records.

Each `APP` record is configured with a JSON document which looks like the following:

```
{
  "public": [
    "1.1.1.1",
    "2.2.2.2"
  ],
  "private": [
    "192.168.1.1",
    "::1"
  ],
  "custom-networks": [
    "172.16.1.1"
  ],
  "10.0.0.0/8": [
    "10.1.1.1"
  ]
}
```

An example for `CNAME` replacements:

```
{
  "public": "api.example.com",
  "private": "api.example.corp",
  "custom-networks": "custom.example.corp",
  "10.0.0.0/8": "api.intranet.example.corp"
}
```

Keys can be one of the following:

- a network specification (like `10.0.0.0/8`)
- a named network defined in the global app configuration (see [Address Translation])
- `private`: private IP ranges defined in RFC 1918
- `public`: all IPs outside the private IP ranges defined in RFC 1918

Values are either lists of IPv4 and IPv6 addresses which get mapped to `A` and `AAAA` records in the response or a single string to be used as the resulting `CNAME` response.

The lists don't have to be exhaustive: requests from clients which don't match any of the networks are processed as if the `APP` record didn't exist. For example, this could mean that a request is forwarded via a `FWD` record.

## Address Translation

Translates IP addresses in a DNS response for `A` and `AAAA` type request based on the client's network address and the configured 1:1 translation. Also supports reverse (`PTR`) queries for translated addresses.

### Configuration

This feature is both a _post processor_ as well as a _request handler_. That means, it modifies a response generated by the DNS server before it is sent to the client and also serves authoritative responses for some requests. It is configured globally in the app settings. Its configuration file is a JSON document which looks like the following:

```
{
    "networks": {
        "custom-networks": [
            "172.16.1.0/24",
            "172.16.10.0/24",
            "172.16.2.1"
        ]
    },
    "enableAddressTranslation": false,
    "networkGroupMap": {
        "10.0.0.0/8": "local1",
        "172.16.0.0/12": "local2",
        "192.168.0.0/16": "local3"
    },
    "groups": [
        {
            "name": "local1",
            "enabled": true,
            "translateReverseLookups": true,
            "externalToInternalTranslation": {
               "1.2.3.0/24": "10.0.0.0/24",
               "5.6.7.8": "10.0.0.5"
            }
        },
        {
            "name": "local2",
            "enabled": true,
            "translateReverseLookups": true,
            "externalToInternalTranslation": {
               "1.2.3.4": "172.16.0.4",
               "5.6.7.8": "172.16.0.5"
            }
        },
        {
            "name": "local3",
            "enabled": true,
            "translateReverseLookups": true,
            "externalToInternalTranslation": {
               "1.2.3.4": "192.168.0.4",
               "5.6.7.8": "192.168.0.5"
            }
        }
    ]
}
```

The individual settings are:

- `networks`: a map of network names to lists of network addresses. This can used to name networks for use in `APP` records for this app. By using named networks, it becomes easy to change a network definition which is reused across multiple `APP` records.
- `enableAddressTranslation`: when set to `false`, address translation is disabled and the original response is passed through unmodified.
- `networkGroupMap`: maps given networks to one of the following named groups. The IP of the requesting client is usses the group with the most specific network mask. Example: if you have mappings `"192.168.1.0/24" = "local1"` and `"192.168.0.0/16" = "local2"`, the client with the IP `192.168.1.10` is considered a member of group `local1`.
- `groups`: a list of groups. A group has the following properties:
  - `name`: the name of the group.
  - `enabled`: flag whether to perform address translation for this group.
  - `translateReverseLookups`: flag whether to respond to `PTR` queries for internal IPs (see below)
  - `externalToInternalTranslation`: a mapping from external to internal network addresses (see below). The networks must be of the same size (have the same prefix length).

### Processing

Forward lookups (`A` and `AAAA`) which fulfill all of the following requirements are processed by this app:

- the requesting client is a member of a group defined in `networkGroupMap`
- the response code is `NoError`
- the response has at least one answer

Note that `NXDOMAIN`, `SERVFAIL`, and `NODATA` answers are passed through unmodified.

For every `A` and `AAAA` record in the response, the app replaces any IP according to the rules defined in `externalToInternalTranslation` of the client's network group. The translation is a 1:1 mapping which just replaces the network part of an IP. For example, given `externalToInternalTranslation": { "1.2.3.0/24": "10.0.0.0/24 }`, the response `1.2.3.4` is replaced with `10.0.0.4`.

If `translateReverseLookups` is enabled for a given group, the app also modifies `PTR` queries for domains representing the internal IPs of the group by responding with a `CNAME` record for a domain representing the corresponding IP in the external range. For example, given `externalToInternalTranslation": { "1.2.3.0/24": "10.0.0.0/24 }`, a `PTR` query for `4.0.0.10.in-addr.arpa` will receive the response `CNAME 4.3.2.1.in-addr.arpa`.
