Initial commit

This commit is contained in:
Sam Stephenson 2014-10-28 13:28:24 -05:00
commit 2d7c9e412a
4 changed files with 234 additions and 0 deletions

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2014 Sam Stephenson, Basecamp
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

9
README.md Normal file
View File

@ -0,0 +1,9 @@
#### xip-pdns
This is the source of the [PowerDNS](http://powerdns.com/) pipe backend adapter powering [xip.io](http://xip.io/).
Install this on your system, adjust [etc/xip-pdns.conf](etc/xip-pdns.conf.example) to your liking, and configure PowerDNS as follows:
launch=pipe
pipe-command=/path/to/xip-pdns/bin/xip-pdns /path/to/xip-pdns/etc/xip-pdns.conf

187
bin/xip-pdns Executable file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env bash
set -e
shopt -s nocasematch
#
# Configuration
#
XIP_DOMAIN="xip.test"
XIP_ROOT_ADDRESSES=( "127.0.0.1" )
XIP_NS_ADDRESSES=( "127.0.0.1" )
XIP_TIMESTAMP="0"
XIP_TTL=300
if [ -a "$1" ]; then
source "$1"
fi
#
# Protocol helpers
#
read_cmd() {
local IFS=$'\t'
local i=0
local arg
read -ra CMD
for arg; do
eval "$arg=\"\${CMD[$i]}\""
let i=i+1
done
}
send_cmd() {
local IFS=$'\t'
printf "%s\n" "$*"
}
fail() {
send_cmd "FAIL"
log "Exiting"
exit 1
}
read_helo() {
read_cmd HELO VERSION
[ "$HELO" = "HELO" ] && [ "$VERSION" = "1" ]
}
read_query() {
read_cmd TYPE QNAME QCLASS QTYPE ID IP
}
send_answer() {
local type="$1"
shift
send_cmd "DATA" "$QNAME" "$QCLASS" "$type" "$XIP_TTL" "$ID" "$@"
}
log() {
printf "[xip-pdns:$$] %s\n" "$@" >&2
}
#
# xip.io domain helpers
#
XIP_DOMAIN_PATTERN="(^|\.)${XIP_DOMAIN//./\.}\$"
NS_SUBDOMAIN_PATTERN="^ns-([0-9]+)\$"
IP_SUBDOMAIN_PATTERN="(^|\.)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\$"
BASE36_SUBDOMAIN_PATTERN="(^|\.)([a-z0-9]{1,7})\$"
qtype_is() {
[ "$QTYPE" = "$1" ] || [ "$QTYPE" = "ANY" ]
}
qname_matches_domain() {
[[ "$QNAME" =~ $XIP_DOMAIN_PATTERN ]]
}
qname_is_root_domain() {
[ "$QNAME" = "$XIP_DOMAIN" ]
}
extract_subdomain_from_qname() {
SUBDOMAIN="${QNAME:0:${#QNAME}-${#XIP_DOMAIN}}"
SUBDOMAIN="${SUBDOMAIN%.}"
}
subdomain_is_ns() {
[[ "$SUBDOMAIN" =~ $NS_SUBDOMAIN_PATTERN ]]
}
subdomain_is_ip() {
[[ "$SUBDOMAIN" =~ $IP_SUBDOMAIN_PATTERN ]]
}
subdomain_is_base36() {
[[ "$SUBDOMAIN" =~ $BASE36_SUBDOMAIN_PATTERN ]]
}
resolve_ns_subdomain() {
local index="${SUBDOMAIN:3}"
echo "${XIP_NS_ADDRESSES[$index-1]}"
}
resolve_ip_subdomain() {
[[ "$SUBDOMAIN" =~ $IP_SUBDOMAIN_PATTERN ]] || true
echo "${BASH_REMATCH[2]}"
}
resolve_base36_subdomain() {
[[ "$SUBDOMAIN" =~ $BASE36_SUBDOMAIN_PATTERN ]] || true
local ip=$(( 36#${BASH_REMATCH[2]} ))
printf "%d.%d.%d.%d" $(( ip&0xFF )) $(( (ip>>8)&0xFF )) $(( (ip>>16)&0xFF )) $(( (ip>>24)&0xFF ))
}
answer_soa_query() {
send_answer "SOA" "admin.$XIP_DOMAIN ns-1.$XIP_DOMAIN $XIP_TIMESTAMP $XIP_TTL $XIP_TTL $XIP_TTL $XIP_TTL"
}
answer_ns_query() {
local i=1
local ns_address
for ns_address in "${XIP_NS_ADDRESSES[@]}"; do
send_answer "NS" "ns-$i.$XIP_DOMAIN"
let i+=1
done
}
answer_root_a_query() {
local address
for address in "${XIP_ROOT_ADDRESSES[@]}"; do
send_answer "A" "$address"
done
}
answer_subdomain_a_query_for() {
local type="$1"
local address="$(resolve_${type}_subdomain)"
if [ -n "$address" ]; then
send_answer "A" "$address"
fi
}
#
# PowerDNS pipe backend implementation
#
trap fail err
read_helo
send_cmd "OK" "xip.io PowerDNS pipe backend (protocol version 1)"
while read_query; do
log "Query: type=$TYPE qname=$QNAME qclass=$QCLASS qtype=$QTYPE id=$ID ip=$IP"
if qname_matches_domain; then
if qname_is_root_domain; then
if qtype_is "SOA"; then
answer_soa_query
fi
if qtype_is "NS"; then
answer_ns_query
fi
if qtype_is "A"; then
answer_root_a_query
fi
elif qtype_is "A"; then
extract_subdomain_from_qname
if subdomain_is_ns; then
answer_subdomain_a_query_for ns
elif subdomain_is_ip; then
answer_subdomain_a_query_for ip
elif subdomain_is_base36; then
answer_subdomain_a_query_for base36
fi
fi
fi
send_cmd "END"
done

18
etc/xip-pdns.conf.example Normal file
View File

@ -0,0 +1,18 @@
# Increment this timestamp when the contents of the file change.
XIP_TIMESTAMP="2014102800"
# The top-level domain for which the name server is authoritative.
XIP_DOMAIN="xip.test"
# The public IP addresses (e.g. for the web site) of the top-level domain.
# `A` queries for the top-level domain will return this list of addresses.
XIP_ROOT_ADDRESSES=( "1.2.3.1" )
# The public IP addresses on which this xip-pdns server will run.
# `NS` queries for the top-level domain will return this list of addresses.
# Each entry maps to a 1-based subdomain of the format `ns-1`, `ns-2`, etc.
# `A` queries for these subdomains map to the corresponding addresses here.
XIP_NS_ADDRESSES=( "1.2.3.4" "1.2.3.5" )
# How long responses should be cached, in seconds.
XIP_TTL=300