Markdown for XML2RFC v3

Or... how to write Markdown to generate XML that is valid XML2RFC v3. These are some assorted notes and ideas

Goal:

  1. Self contained file with all information to generate a complete I-D;
  2. CommonMarkdown, with some extension.

I'm working on implementing these ideas in blackfriday. The following has already been implemented:

  • Abstract;
  • Citation;
  • Document divisions;
  • Index;
  • File inclusion;
  • Extra attributes (kramdown's IAL.) (work-in-progress).

Document meta data

In TOML - much easier than YAML for instance, lines are starting with %. We don't need to specify the citations here because:

Citations

Use the reference syntax: [p. 23][#RFC2535], which can be extended to include a filename where the reference is:

[p. 23][#RFC2335,bib/reference.RFC.2525.xml]

They can optionally have 'n' or 'i' modifier to normative or informative reference. The reference section is automatically generated. Here is a normative and informative reference:

[][n#RFC2335,bib/reference.RFC.2525.xml]
[][i#RFC2335,bib/reference.RFC.2525.xml]

The default is informative. This only have to be done once, after that you can just use [][#RFC2535].

Abstract, notes and asides

Abstract is done with prefixing a paragraph with AB>, notes: N> and aside with A>.

Extra Attributes

CommonMark (seems?) to have adapted the kramdown IAL syntax but dropped the colon, so we would use {.class key=value} to add attributes to block elements.

Document Divisions

The front-, middle- and back-sections are signalled with:

{frontmatter}, {middlematter} and {backmatter}, where a document automatically starts with a frontmatter.

Index

Using triple parentheses, like so: (((Cats, Tiger))).

Including Markdown files

Use the syntax {{file}} will get file included.

All of the above use the extension syntax, i.e. starting with a { .

Quotes attribution?

> this is an quote
-- Miek Gieben

BCP14 keywords?

Uppercase in asterisks *MUST*.

Tagged , , ,

xmodmap is dead

I'm using an Apple keyboard at work since a day or two, but for some reason this keyboard has a special key for eject, because in 1898 that was useful when you wanted to eject a cdrom.

Apple Keyboard

Anyway making this key do something useful proved incredible painful, especially since xmodmap is deprecated and we should all use xkb (setxkbmap andxkbcomp). In our new world of hotplugging everything this kinda makes sense (xmodmap settings are lost when your USB keyboard goes to sleep).

I use i3, but with gnome-settings-daemon running. So first I removed the eject functionality in there (not tested if this still is needed, with the later fix). In dconf-editor, go to org.gnome.settings-daemon.plugins.media-keys and remove the Value from the key named eject.

Searching more I found this site, which gave me to solution I settled on. With xev I found out the keycode sent is 169.

Dump the current config with xkbcomp $DISPLAY, this creates a file that will be called server-0.xkb

Find the line where keycode 169 is mapped:

key <I167> {         [     XF86Forward ] };
key <I169> {         [       XF86Eject ] };
key <I170> {         [       XF86Eject,       XF86Eject ] };

And change the key <I169> on to use BackSpace (for instance). Compile it to an .xkm file and load the keymap:

xkbcomp server_0.xkb
xkbcomp server_0.xkm $DISPLAY

In xmodmap this would have something like (untested) keycode 169 = BackSpace, which is of course so simple it needed a better solution.

Tagged , ,

Vim live preview (sort of)

When editing Markdown files or internet drafts in Pandoc's Markdown, I wanted to see some live preview window. I looked around a bit, but the solutions presented on the Internet, seemed to be insufficient, either to clumsy or don't work at all.

My usual routine is: edit -> write -> make -> reload "rendered" file.

Turns out you can automate most of this. Vim has a feature: --servername <id> which allows you to send commands to another vim instance using that <id>. So we need two pieces to make this work.

  1. start a vim instance with --servername markdown and;
  2. some way to send commands to it when building the file, turns out makeprg is an excellent candidate for this, as I was already using Make.

So to start with (2), add an autocmd:

autocmd Filetype pandoc set makeprg=makepandoc\ markdown

Which calls this little shell script:

1
2
3
#!/bin/zsh
make && \
vim --servername "${1}" --remote-send '<C-\><C-N>:e<CR>'

And then (1) in a terminal, next to the one you're typing in:

vim -R --servername markdown draft.txt

Now every time you call :make (after writing it), the other vim window reloads it's file. Some things that obviously don't work: scrollbind, so you need to manual scroll to the interesting area of your file.

The fancy stuff right now would be to show a screencast, but ... no.

Tagged , ,

IDN and Private RR in Go DNS

Thanks to the excellent work from Alex Sergeyev, Go DNS has gotten some new features. I want to highlight two: IDN (https://www.ietf.org/rfc/rfc3492.txt) and Private RR support (http://tools.ietf.org/html/rfc6895).

IDN

This adds support for converting from and to Punycode. There is no explicit support, you will need to call idn.ToPunycode and idn.FromPunyCode yourself if you are dealing with IDNs.

The examples give in the code:

name := "インターネット.テスト"
fmt.Printf("%s -> %s", name, idn.ToPunycode(name))

Which outputs:

インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah

Private RR

Another thing that was added is the ability to add private RR types: that is new RR type with a code in the 65,280 - 65,534 range. This makes it possible to specify your own (vanity) types, which then work just as the normal ones in Go DNS.

Say for instance I want to add a new RR called "MIEK" (with type code 65281), this RR has two rdata elements: a domain name and it ends with a hexadecimal string. Basically it looks like:

www.example.com.    IN MIEK     example.com. AABB1234

Note that you can also use TYPExxxx and RFC3597 (http://www.ietf.org/rfc/rfc3597.txt) for this, but then the above RR would look something like this:

www.example.com.    IN TYPE65281 \# 8 AABBCCDDAABB1234

Which works, but of course looks bad in the vanity department.

To do this you will need (see example_test.go for example code in the library) create a new record type, implement the PrivateRdata interface and register the new type with the library.

const typeMiek = 65281
type MIEK struct {
    Target string
    Extra  string
}

Implement PrivateRdata:

  1. String - returns the string presentation of the rdata;
  2. Len - returns the length of the rdata;
  3. Pack and Unpack to convert to and from wire format;
  4. Parse to parse a text presentation of the rdata;
  5. Copy to allow the private RR to be copied inside Go DNS (sometimes needed for DNSSEC).

So lets start with the implementation beginning with String() and Len()

func (m *MIEK) String() string { return m.Target + " " + m.Extra }
func (m *MIEK) Len() int  { return len(m.Target) + len(m.Extra)/2 }

And the functions to convert from and to wire format:

func (m *MIEK) Pack(buf []byte) (int, error) {
        off, err := dns.PackDomainName(m.Target, buf, 0, nil, false)
        if err != nil {
                return off, err
        }
        h, err := hex.DecodeString(m.Extra)
        if err != nil {
                return off, err
        }
        if off+hex.DecodedLen(len(m.Extra)) > len(buf) {
                return len(buf), errors.New("overflow packing hex")
        }
        copy(buf[off:off+hex.DecodedLen(len(m.Extra))], h)
        off += hex.DecodedLen(len(m.Extra))
        return off, nil
}

func (m *MIEK) Unpack(buf []byte) (int, error) {
        s, off, err := dns.UnpackDomainName(buf, 0)
        if err != nil {
                return len(buf), err
        }
        m.Target = s
        s = hex.EncodeToString(buf[off:])
        m.Extra = s
        return len(buf), nil
}

And our Parse function that parses the string slice sx into MIEK's rdata.

func (m *MIEK) Parse(sx []string) error {
        if len(sx) < 2 {
                return errors.New("need at least 2 pieces of rdata")
        }
        m.Target = sx[0]
        if  _, ok := dns.IsDomainName(m.Target); !ok {
                return errors.New("bad MIEK Target")
        }
        // Hex data can contain spaces.
        for _, s := range sx[1:] {
                m.Extra += s
        }
        return nil
}

And Copy which is needed to copy the rdata:

func (m *MIEK) Copy(dest dns.PrivateRdata) error {
        m1, ok := dest.(*MIEK)
        if !ok {
                return dns.ErrRdata
        }
        m1.Target = m.Target
        m1.Extra = m.Extra
        return nil
}

And finally we can register the new type:

dns.PrivateHandle("MIEK", typeMiek, func() dns.PrivateRdata { return new(MIEK) })
defer dns.PrivateHandleRemove(typeMiek) // when removing again

We can now use all this code to do the following:

m1, _ := dns.NewRR("example.com. IN MIEK target.example.com. DEADBEEF")
log.Printf("%s\n", m1.String())

That prints:

 example.com.   3600    IN  MIEK    target.example.com. DEADBEEF

Mission accomplished :) This is all pretty new code, but I'm pretty happy how this is shaping up, but there might be some changes here in the future.

If you need to export this new type, your best bet is to convert it to an unknown RR:

u := new(dns.RFC3597)                                                                                                                                                                                                  
u.ToRFC3597(m1)                                                                                                                                                                                                        
log.Printf("%s\n", u.String())

example.com.    3600    IN  MIEK    \# 24 06746172676574076578616d706c6503636f6d00deadbeef
Tagged ,

Go DNS package

Go DNS is a package that implements a DNS interface in Go. This library takes a new, innovative and enterprise ready approach sends and receives queries to and from the DNS. It is licensed under the same license as the official Go code, as this is a fork of that code.

The aim is to be powerful, simple and fast.

Supported:

  • All RR types;
  • Synchronous and asynchronous queries and replies;
  • DNSSEC: validation, signing, key generation, reading .private key files
  • (Fast) sending/receiving/printing packets, RRs;
  • Full control over what is being send;
  • Zone transfers, EDNS0, TSIG, NSID;
  • Server side programming (a full blown nameserver).
  • (Fast) reading zones/RRs from files/strings.

Code

The git repository is hosted on github.

Examples using this library can be found in exdns repository over at github.

Tutorials and more info.

Printing MX records

A small peek in to how to print MX records with Go DNS.

We want to create a little program that prints out the MX records of domains, like so:

% mx miek.nl
miek.nl.        86400   IN      MX      10 elektron.atoom.net.

Or

% mx microsoft.com 
microsoft.com.  3600    IN      MX      10 mail.messaging.microsoft.com.

First the normal header of a Go program, with a bunch of imports. We need the dns package:

package main

import (
    "github.com/miekg/dns"
    "os"
    "net"
    "fmt"
    "log"
)

Next we need to get the local nameserver to use:

config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")

Then we create a dns.Client to perform the queries for us. In Go:

c := new(dns.Client)

We skip some error handling and assume a zone name is given. So we prepare our question. For that to work, we need:

  1. a new packet (dns.Msg);
  2. setting some header bits;
  3. define a question section;
  4. fill out the question section: os.Args[1] contains the zone name.

Which translates into:

m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(os.Args[1]), dns.TypeMX)
m.RecursionDesired = true

Then we need to finally 'ask' the question. We do this by calling the Exchange() function. The unused return value is the rtt (round trip time).

r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))

Check if we got something sane. The following code snippet prints the answer section of the received packet:

Bail out on an error:

if r == nil {
    log.Fatalf("*** error: %s\n", err.Error())
}

if r.Rcode != dns.RcodeSuccess {
        log.Fatalf(" *** invalid answer name %s after MX query for %s\n", os.Args[1], os.Args[1])
}

// Stuff must be in the answer section
for _, a := range r.Answer {
        fmt.Printf("%v\n", a)
}

And we are done.

Full Source

package main

import (
    "github.com/miekg/dns"
    "net"
    "os"
    "log"
    "fmt"
)

func main() {
    config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
    c := new(dns.Client)

    m := new(dns.Msg)
    m.SetQuestion(dns.Fqdn(os.Args[1]), dns.TypeMX)
    m.RecursionDesired = true

    r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
    if r == nil {
        log.Fatalf("*** error: %s\n", err.Error())
    }

    if r.Rcode != dns.RcodeSuccess {
            log.Fatalf(" *** invalid answer name %s after MX query for %s\n", os.Args[1], os.Args[1])
    }
    // Stuff must be in the answer section
    for _, a := range r.Answer {
            fmt.Printf("%v\n", a)
    }
}
Tagged ,

SkyDNS running live

SkyDNS is able to do DNSSEC. It generates signatures and NSEC3 records on the fly. For authenticated denial of existence SkyDNS uses NSEC3 white lies, of course implementing (and testing!) this isn't completely trivial.

To aid in debugging I've setup a live version of SkyDNS on voordeur.atoom.net, under the name the zone http://dnssex.nl:

% dig +mul +noall +answer @voordeur.atoom.net soa skydns.dnssex.nl
skydns.dnssex.nl.    3600 IN SOA ns1.dns.skydns.dnssex.nl. hostmaster.skydns.local. (
                            1403942400 ; serial
                            28800      ; refresh (8 hours)
                            7200       ; retry (2 hours)
                            604800     ; expire (1 week)
                            60         ; minimum (1 minute)
                            )

To help getting DNSSEC support 100% working this zone has been delegated and has an DS record in the parent zone. With unbound-host you can see the validation status of this zone:

% unbound-host -C /etc/unbound/unbound.conf -vt SOA skydns.dnssex.nl 
skydns.dnssex.nl has SOA record ns1.dns.skydns.dnssex.nl. hostmaster.skydns.local. 
    1403942400 28800 7200 604800 60 (secure)

Where (secure) indicates DNSSEC is in order.

Bugs

However getting NXDOMAIN and NODATA response it gets a bit more flaky, but some stuff is working:

% unbound-host -C /etc/unbound/unbound.conf -vt TXT dns.skydns.dnssex.nl
dns.skydns.dnssex.nl has no TXT record (secure)

And some is not:

% unbound-host -C /etc/unbound/unbound.conf -vt SRV server2.miek.skydns.dnssex.nl
Host server2.miek.skydns.dnssex.nl not found: 3(NXDOMAIN). (BOGUS (security failure))
validation failure <server2.miek.skydns.dnssex.nl. SRV IN>: 
    nameerror proof failed from 89.98.74.120

sadface I believe this is due to defaulting to skydns.dnssex.nl as the closest encloser and *.skydns.dnssex.nl as the source of synthesis, but I haven't had the time to dig deeper into this.

Testing

In the near future I hope to update the current test, to include NSEC3 white lies tests.

Tagged , , , , , ,

SkyDNS version 2

SkyDNS version 1 was announced some time ago, since then it has seen some developments, which resulted in SkyDNS version 2. This new version uses Etcd as its backend. This blog post will walk you through the installation and shows how to use it.

What?!

SkyDNS(2) is a service discovery tool that utilizes the DNS to find hosts in a distributed environment. But using DNS means "legacy" clients can be used. Want to know if you MariaDB cluster is still up? ping mariadb.skydns.local can be used for that. By default SkyDNS will use skydns.local. as the domain to anchor all names.

Installation

If not already installed, install Go for your system, either via the package manager or from source. After that you will need Etcd and SkyDNS:

  • go get github.com/coreos/etcd
  • go get github.com/skynetservices/skydns

After the installation, start Etcd: ./etcd. This will run a lonely, non clusterized Etcd on port 4001 on your local machine. SkyDNS has the ability to use configuration stored in Etcd, but for now we use the command line flags to start SkyDNS:

% ./skydns -addr=127.0.0.1:1054 -machines=http://127.0.0.1:4001 \
    -nameservers=8.8.4.4:53
[skydns] Jun  8 08:30:19.761 INFO      | ready for queries

Let's see if it works, by using dig:

% dig @127.0.0.1 -p 1054 +noall +answer +add SOA skydns.local
skydns.local. 3600 IN SOA ns1.dns.skydns.local. hostmaster.skydns.local. (
                1402210800 ; serial
                28800      ; refresh (8 hours)
                7200       ; retry (2 hours)
                604800     ; expire (1 week)
                60         ; minimum (1 minute)
                )

Somebody is answering! Note in the other examples, I will use the same command line for, but remove all the flags and options. If a query aimed at SkyDNS does not fall under skydns.local. it will forward it to 8.8.4.4 and returns the answer from that:

% dig a miek.nl
miek.nl.        19827 IN A 176.58.119.54

With this you can configure SkyDNS as your nameserver in /etc/resolv.conf.

Services

The original SkyDNS used a fix naming scheme, environment.service.version.region.skydns.local., SkyDNS2 does away with this, but still it makes sense to defines some scheme to be used in your environment. In this blog post I will use a very simple scheme that only uses a region, like "east", "west", etc.

Let register a service in Etcd, we want the register the name 'web01.east.skydns.local', which listens on port 80 and has an IP4 address of 10.0.1.3. All names used by SkyDNS in Etcd are stored under /skydns/ and we need to reverse the domain name. So to register the name we need to use the key: /v2/keys/skydns/local/skydns/east/web01, the payload of it must be JSON like so:

% curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/web01 \
    -d value='{"Port":80,"Host": "10.0.1.3"}'

And retrieving it via DNS:

% dig A web01.east.skydns.local
web01.east.skydns.local. 3600 IN A 10.0.1.3

Now we also add another webservice in the east region, web02.east.skydns.local, with IP4 address of 10.0.1.4.

Now suppose you want to have a list of all webservers in the east region? Simple just query for east.skydns.local:

% dig A east.skydns.local
east.skydns.local.  3600 IN A 10.0.1.4
east.skydns.local.  3600 IN A 10.0.1.3

Of course IP6 is also supported. Using A and AAAA records allows for "legacy" support, however the port number must be know by the client connection, because that information is not in the returned records. To fix this you can also query for SRV records.

SRV Records

SRV records return much more information than A/AAAA records, it includes a port number, a priority a weight and a name (not an address record). As the service information for web01 only includes an address, SkyDNS will synthesise the SRV record and includes the actual IP address in the additional section:

% dig SRV web01.east.skydns.local
web01.east.skydns.local. 3600 IN SRV 10 100 80 web01.east.skydns.local.
web01.east.skydns.local. 3600 IN A 10.0.1.3

The numbers "10", "100" and "80" in the SRV records are respectively:

  • 10: priority.
  • 20: weight (when multiple SRV records have the same priority, look at the weight). In SkyDNS weight is a percentage.
  • 80: the port number for the service, if the port is not given in the service, it defaults to 0.

Of course this all works when you query for east.skydns.local as well.

Wildcards

The DNS standards supports wildcards, but SkyDNS extends this usage to allow wildcards within a domainname. To show how this we add another service, this time web01.west.skydns.local. Suppose we want to target all web01 servers? With plain DNS you will need to do two queries (and know about west and east!), with SkyDNS only one is needed:

% dig web01.*.skydns.local
web01.*.skydns.local.   3600    IN  SRV 10 50 80 web01.east.skydns.local.
web01.*.skydns.local.   3600    IN  SRV 10 50 80 web01.west.skydns.local.
web01.east.skydns.local. 3600   IN  A   10.0.1.3
web01.west.skydns.local. 3600   IN  A   10.0.2.1

DNSSEC

Signed responses are also supported, although authenticated denial of existence based on NSEC3 is a work in progress. A quick primer on how to enable it, as there are a few steps.

  1. Generate a DNSSEC keypair for SkyDNS:

    % dnssec-keygen skydns.local
    Generating key pair........................................++++++ .....++++++ 
    Kskydns.local.+005+04821
    
  2. Use the basename of the generated key pair as an argument to SkyDNS:

    % ./skydns -addr=127.0.0.1:1054 -machines=http://127.0.0.1:4001 \
        -nameservers=8.8.4.4:53 -dnssec Kskydns.local.+005+04821
    [skydns] Jun  8 12:28:37.981 INFO      | ready for queries, signing with Kskydns.local.+005+04821
    

When you know query with the DO bit on (+dnssec in dig) you will get signed responses:

% dig +dnssec web01.*.skydns.local
web01.*.skydns.local.   3600 IN SRV 10 50 80 web01.east.skydns.local.
web01.*.skydns.local.   3600 IN SRV 10 50 80 web01.west.skydns.local.
web01.*.skydns.local.   3600 IN RRSIG SRV 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     DWNzcR9DqNHrITZPN/sfGUH42Ur+GQXSQu95O4eouY0a
                     8uupN8pa75IA/inJ6tnN7cTW5XGbe6AKLB90GrxEIiHt
                     dxogqzCNaCt/Z4bj8BY8ZMLC5IXX2+L6hnLYlCv6Q/jJ
                     4ixafFhbJSD+Rc4eK764Rberhik/zUtuXDe8kXM= )
web01.east.skydns.local. 3600 IN A 10.0.1.3
web01.west.skydns.local. 3600 IN A 10.0.2.1
web01.east.skydns.local. 3600 IN RRSIG A 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     X9uu3yw2aPUJaJu0q0I0cEyJHAcvpqd1bDhAFEM0uHkr
                     yWQzm+i0VK6HEawK+hgtenfkJb1DtqIUdsEQ7+Kz1elV
                     EC+w/W7p+bXL29BTuJOu722H8tXo21I9lr+SoIoIDsKG
                     BfPkVwACwBAWaPJWrxy90v43NXdSunl55eUVoP4= )
web01.west.skydns.local. 3600 IN RRSIG A 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     RNacRPSW6hxhtze6sV6LafcOIuqMex1TBVo6FbI1pkXV
                     ZVz3Bm+xWuo7QUP74I8r7j5y8cPUpRNRMOO6StAr73Wy
                     GGgb7HExUSHTKU03xHAwnAvKbcTlXkJcSHmxBrY+/yE+
                     HkbwFHe4Y9qNTF4ygvU0BtObbJ3+e0hW8wr6YIU= )

The signatures are cached, so this does not turn into an easy DDoS at once.

Other responses you expect from a DNS server are supported, like SOA, NS, TXT, etc.

Tagged , , , ,

Learning Go

"Learning Go" is a book that gives an introduction into the Go language of Google. It is licensed under a copy-left license. The book currently consists out +/- 120 (A4 sized) pages and the following chapters:

  1. Introduction
    Show how to install Go and details the lineage of the language Go.
  2. Basics
    Types, variables and control structures.
  3. Functions
    How to make and use functions.
  4. Packages
    Functions and data is grouped together in packages. Here you will see how to make your own package. How to unit test your package is also described.
  5. Beyond the basics
    Learn how to create your own data types and define function on them (called methods in Go).
  6. Interfaces
    Go does not support Object Orientation in the traditional sense. In Go the central concept is interfaces.
  7. Concurrency
    With the go keyword function can be started in separate routines (called goroutines). Communication with those goroutines is done via channels.
  8. Communication
    How to create/read/write from and to files. And how to do networking.

Each chapter concludes with a number of exercises (and answers) to may help you to get some hands on experience. Currently it has more than 30 exercises.

There is also a Chinese translation by Mike Spook.

What readers say:

I am really glad that I found your Go book. It's been a couple of weeks since I started learning Go, but didn't make much progress till I found your book.

I also read with great interest the (successive versions of the) free E-book by Miek Gieben & Co. Which I find definitely very well crafted and very useful. Definitely an extremely laudable initiative.

Prebuild PDFs can be found at /downloads/Go. The source code of the book can be found on github.

It is written in LaTeX with the Memoir class (and a bunch a extra classes).

Questions, patches, text, bug reports and general discussions can be directed to miek@miek.nl (in English or Dutch). If you like this work you may choose support it by sending me money :)

Tagged , , ,

DNS Router

Say you have a zone that does not fit in the memory of one machine. Who hasn't these zones nowadays? How would you solve such a problem? With a DNS router of course!

Dns router is a small Go program I whipped together that acts as a DNS router. Clients register an <ip:port, regexp> combination and will then only receive queries that match that regular expression. The registration happens in Etcd. Of course "Dns router" (I need a better name), has some features, it will:

  • health checks the server every 5 seconds using TCP using a id.server. TXT CH query;
  • set an Ectd watch to get updates when a new server is added or removed.

So it's pretty dynamic, but the health checking could be better, as servers will never be re-added once removed.

Ldns actually has an utility to split a zonefile into chunks (with a new SOA, called zsplit, see http://git.nlnetlabs.nl/ldns/tree/examples/ldns-zsplit.1. In this case I just manually split a zone into 2 chunks, one with names starting with [ab] and another with [cd]. Of course the apex of the zone needs to go somewhere, so this has to be specified somewhere. See the examples later in this article.

For the purpose of this article I've used 2 docker images with BIND9 and the 2 (split) zones I have prepared.

The whole "how-do-I-prepare-an-Docker-image" will be left out, there is plenty of documentation on the Net on this. In all I've created two docker images, two running BIND9 and pieces of miek.nl. After fiddling with docker I found the following command line would start my VMs OK:

docker run -p 5300:53/udp -p 5300:53 -d miek/bind:bind9a

And run the other docker container on a different port:

docker run -p 5301:53/udp -p 5301:53 -d miek/bind:bind9c

So, all a-b names are reachable on port 5300 and all c-d names can be found via port 5301.

Assuming we have an etcd running on our host we register our two docker VMs with it and then start dnsrouter.

curl -L http://127.0.0.1:4001/v2/keys/dnsrouter/a1 -XPUT -d value="127.0.0.1:5300,^[ab]\.miek"
curl -L http://127.0.0.1:4001/v2/keys/dnsrouter/c1 -XPUT -d value="127.0.0.1:5301,^[cd]\.miek"

And two routes for the apex of the zone, dnsrouter will round robin between the two servers.

% DNS_ADDR=127.0.0.1:5299 ./dnsrouter
2014/05/17 10:54:38 enabling health checking
2014/05/17 10:54:38 setting watch
2014/05/17 10:54:38 getting initial list
2014/05/17 10:54:38 unable to parse node /dnsrouter with value # small bug I need to fix
2014/05/17 10:54:38 adding route ^[ab]\.miek for 127.0.0.1:5300
2014/05/17 10:54:38 adding route ^[cd]\.miek for 127.0.0.1:5301
2014/05/17 10:54:38 ready for queries

So dnsrouter is running on port 5299, lets try some queries and check the logs of dnsrouter.

% dig @localhost +noall +ans -p 5299 TXT a.miek.nl
a.miek.nl.      43200   IN  TXT "aa"
% dig @localhost +noall +ans -p 5299 TXT c.miek.nl
c.miek.nl.      43200   IN  TXT "cc"

And the logs from dnsrouters:

2014/05/17 11:04:07 routing a.miek.nl. to 127.0.0.1:5300
2014/05/17 11:04:12 routing c.miek.nl. to 127.0.0.1:5301

A request for the apex of the zone fails because we don't have setup a route for it, so let's add two:

2014/05/17 11:06:51 adding route ^miek for 127.0.0.1:5301
2014/05/17 11:06:58 adding route ^miek for 127.0.0.1:5300

And dig again:

% dig @localhost +noall +ans -p 5299 SOA miek.nl
miek.nl. 43200   IN  SOA linode.atoom.net. miek.miek.nl. 1282630056 14400 3600 604800 86400

And we even see some round robin at work:

2014/05/17 11:07:23 routing miek.nl. to 127.0.0.1:5300
2014/05/17 11:07:23 routing miek.nl. to 127.0.0.1:5301

Let's kill one of the docker VMs. Dnsrouter should detect this and disable that server. It does not autmatically re-add it, for that you need to write again to etcd, which will then automatically be picked up by Dnsrouter.

% docker stop 29fde54f64f8
...
2014/05/17 11:22:00 healthcheck failed for 127.0.0.1:5300
2014/05/17 11:22:00 removing 127.0.0.1:5300

And starting it again:

% docker start 29fde54f64f8
% curl -L http://127.0.0.1:4001/v2/keys/dnsrouter/a1 -XPUT -d value="127.0.0.1:5300,^[ab]\.miek"
...
2014/05/17 11:23:41 adding route ^[ab]\.miek for 127.0.0.1:5300

In an upcoming article I will describe how I got this running on CoreOS.

Tagged , ,

grub_term_highlight_color

During two Ubuntu 14.04 upgrades, both on a Mac (so needing an EFI boot), grub was borked after the install resulting in a grub rescue prompt when booting.

The actual error was error: symbol 'grub_term_highlight_color' not found.

Needless to say I couldn't get the system to boot from this prompt.

I had a Fedora boot USB stick laying around, but using that did not really fix the problem, in any case I could use it to copy off /home if I could not rescue the system.

HOWEVER, the following procedure worked great!

  • Download super grub disk;
    • 1.4 MB download!
  • Copy it to an USB stick: dd if=super_grub_disk_hybrid-1.98s1.iso of=/dev/sdb;
  • Boot from this USB stick;
  • In the boot menu, choose detect OSs;
  • Boot you newly minted, unbootable, Ubuntu 14.04;
  • If all goes well it should boot Ubuntu and you can log in;
  • Install and use boot repair;
  • If you use EFI, check this: https://help.ubuntu.com/community/UEFI.
Tagged , ,