aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2018-10-05 10:02:55 -0700
committerChristine Dodrill <me@christine.website>2018-10-05 14:31:22 -0700
commitf84de97aac5865fc17ee28cb1af7f25005ee321b (patch)
tree92c767bacd170ba8fbff07794abb5f0951af5eb2
parentd0007f122fe353a6d3796b12f8c7dbdb753811ac (diff)
downloadx-f84de97aac5865fc17ee28cb1af7f25005ee321b.tar.xz
x-f84de97aac5865fc17ee28cb1af7f25005ee321b.zip
import libraries
-rw-r--r--localca/doc.go10
-rw-r--r--localca/localca.go127
-rw-r--r--localca/localca_test.go82
-rw-r--r--localca/minica.go234
-rw-r--r--localca/utils.go83
-rw-r--r--namegen/elfs/elfs.go506
-rw-r--r--namegen/elfs/elfs_test.go10
-rw-r--r--namegen/tarot/doc.go3
-rw-r--r--namegen/tarot/namegen.go40
-rw-r--r--namegen/tarot/namegen_test.go10
-rw-r--r--tun2/backend.go78
-rw-r--r--tun2/backend_test.go211
-rw-r--r--tun2/client.go171
-rw-r--r--tun2/client_test.go21
-rw-r--r--tun2/connection.go162
-rw-r--r--tun2/doc.go11
-rw-r--r--tun2/server.go480
-rw-r--r--tun2/server_test.go171
-rw-r--r--tun2/storage_test.go100
-rw-r--r--web/tokiponatokens/toki_pona_test.go2
20 files changed, 2511 insertions, 1 deletions
diff --git a/localca/doc.go b/localca/doc.go
new file mode 100644
index 0000000..fb4e829
--- /dev/null
+++ b/localca/doc.go
@@ -0,0 +1,10 @@
+// Package localca uses an autocert.Cache to store and generate TLS certificates
+// for domains on demand.
+//
+// This is kind of powerful, and as such it is limited to only generate
+// certificates as subdomains of a given domain.
+//
+// The design and implementation of this is kinda stolen from minica[1].
+//
+// [1]: https://github.com/jsha/minica
+package localca
diff --git a/localca/localca.go b/localca/localca.go
new file mode 100644
index 0000000..abc0f35
--- /dev/null
+++ b/localca/localca.go
@@ -0,0 +1,127 @@
+package localca
+
+import (
+ "context"
+ "crypto/tls"
+ "encoding/pem"
+ "errors"
+ "strings"
+ "time"
+
+ "github.com/Xe/ln"
+ "github.com/Xe/ln/opname"
+ "golang.org/x/crypto/acme/autocert"
+)
+
+var (
+ ErrBadData = errors.New("localca: certificate data is bad")
+ ErrDomainDoesntHaveSuffix = errors.New("localca: domain doesn't have the given suffix")
+)
+
+// Manager automatically provisions and caches TLS certificates in a given
+// autocert Cache. If it cannot fetch a certificate on demand, the certificate
+// is dynamically generated with a lifetime of 100 years, which should be good
+// enough.
+type Manager struct {
+ Cache autocert.Cache
+ DomainSuffix string
+
+ *issuer
+}
+
+// New creates a new Manager with the given key filename, certificate filename,
+// allowed domain suffix and autocert cache. All given certificates will be
+// created if they don't already exist.
+func New(keyFile, certFile, suffix string, cache autocert.Cache) (Manager, error) {
+ iss, err := getIssuer(keyFile, certFile, true)
+
+ if err != nil {
+ return Manager{}, err
+ }
+
+ result := Manager{
+ DomainSuffix: suffix,
+ Cache: cache,
+ issuer: iss,
+ }
+
+ return result, nil
+}
+
+func (m Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ name := hello.ServerName
+ if !strings.Contains(strings.Trim(name, "."), ".") {
+ return nil, errors.New("localca: server name component count invalid")
+ }
+
+ if !strings.HasSuffix(name, m.DomainSuffix) {
+ return nil, ErrDomainDoesntHaveSuffix
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+ defer cancel()
+ ctx = opname.With(ctx, "localca.Manager.GetCertificate")
+ ctx = ln.WithF(ctx, ln.F{"server_name": name})
+
+ data, err := m.Cache.Get(ctx, name)
+ if err != nil && err != autocert.ErrCacheMiss {
+ return nil, err
+ }
+
+ if err == autocert.ErrCacheMiss {
+ data, _, err = m.issuer.sign([]string{name}, nil)
+ if err != nil {
+ return nil, err
+ }
+ err = m.Cache.Put(ctx, name, data)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ cert, err := loadCertificate(name, data)
+ if err != nil {
+ return nil, err
+ }
+
+ ln.Log(ctx, ln.Info("returned cert successfully"))
+
+ return cert, nil
+}
+
+func loadCertificate(name string, data []byte) (*tls.Certificate, error) {
+ priv, pub := pem.Decode(data)
+ if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
+ return nil, ErrBadData
+ }
+ privKey, err := parsePrivateKey(priv.Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ // public
+ var pubDER [][]byte
+ for len(pub) > 0 {
+ var b *pem.Block
+ b, pub = pem.Decode(pub)
+ if b == nil {
+ break
+ }
+ pubDER = append(pubDER, b.Bytes)
+ }
+ if len(pub) > 0 {
+ return nil, ErrBadData
+ }
+
+ // verify and create TLS cert
+ leaf, err := validCert(name, pubDER, privKey, time.Now())
+ if err != nil {
+ return nil, err
+ }
+ tlscert := &tls.Certificate{
+ Certificate: pubDER,
+ PrivateKey: privKey,
+ Leaf: leaf,
+ }
+ return tlscert, nil
+}
diff --git a/localca/localca_test.go b/localca/localca_test.go
new file mode 100644
index 0000000..233253d
--- /dev/null
+++ b/localca/localca_test.go
@@ -0,0 +1,82 @@
+package localca
+
+import (
+ "context"
+ "crypto/tls"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/acme/autocert"
+)
+
+func TestLocalCA(t *testing.T) {
+ dir, err := ioutil.TempDir("", "localca-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ cache := autocert.DirCache(dir)
+
+ keyFile := path.Join(dir, "key.pem")
+ certFile := path.Join(dir, "cert.pem")
+ const suffix = "club"
+
+ m, err := New(keyFile, certFile, suffix, cache)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Run("local", func(t *testing.T) {
+ _, err = m.GetCertificate(&tls.ClientHelloInfo{
+ ServerName: "foo.local.cetacean.club",
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ t.Run("network", func(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ tc := &tls.Config{
+ GetCertificate: m.GetCertificate,
+ }
+
+ go func() {
+ lis, err := tls.Listen("tcp", ":9293", tc)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer lis.Close()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ }
+
+ cli, err := lis.Accept()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer cli.Close()
+
+ go io.Copy(cli, cli)
+ }
+ }()
+
+ time.Sleep(130 * time.Millisecond)
+ cli, err := tls.Dial("tcp", "foo.local.cetacean.club:9293", &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer cli.Close()
+
+ cli.Write([]byte("butts"))
+ })
+}
diff --git a/localca/minica.go b/localca/minica.go
new file mode 100644
index 0000000..d47330b
--- /dev/null
+++ b/localca/minica.go
@@ -0,0 +1,234 @@
+package localca
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/hex"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "math/big"
+ "net"
+ "os"
+ "strings"
+ "time"
+)
+
+type issuer struct {
+ key crypto.Signer
+ cert *x509.Certificate
+}
+
+func getIssuer(keyFile, certFile string, autoCreate bool) (*issuer, error) {
+ keyContents, keyErr := ioutil.ReadFile(keyFile)
+ certContents, certErr := ioutil.ReadFile(certFile)
+ if os.IsNotExist(keyErr) && os.IsNotExist(certErr) {
+ err := makeIssuer(keyFile, certFile)
+ if err != nil {
+ return nil, err
+ }
+ return getIssuer(keyFile, certFile, false)
+ } else if keyErr != nil {
+ return nil, fmt.Errorf("%s (but %s exists)", keyErr, certFile)
+ } else if certErr != nil {
+ return nil, fmt.Errorf("%s (but %s exists)", certErr, keyFile)
+ }
+ key, err := readPrivateKey(keyContents)
+ if err != nil {
+ return nil, fmt.Errorf("reading private key from %s: %s", keyFile, err)
+ }
+
+ cert, err := readCert(certContents)
+ if err != nil {
+ return nil, fmt.Errorf("reading CA certificate from %s: %s", certFile, err)
+ }
+
+ equal, err := publicKeysEqual(key.Public(), cert.PublicKey)
+ if err != nil {
+ return nil, fmt.Errorf("comparing public keys: %s", err)
+ } else if !equal {
+ return nil, fmt.Errorf("public key in CA certificate %s doesn't match private key in %s",
+ certFile, keyFile)
+ }
+ return &issuer{key, cert}, nil
+}
+
+func readPrivateKey(keyContents []byte) (crypto.Signer, error) {
+ block, _ := pem.Decode(keyContents)
+ if block == nil {
+ return nil, fmt.Errorf("no PEM found")
+ } else if block.Type != "RSA PRIVATE KEY" && block.Type != "ECDSA PRIVATE KEY" {
+ return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
+ }
+ return x509.ParsePKCS1PrivateKey(block.Bytes)
+}
+
+func readCert(certContents []byte) (*x509.Certificate, error) {
+ block, _ := pem.Decode(certContents)
+ if block == nil {
+ return nil, fmt.Errorf("no PEM found")
+ } else if block.Type != "CERTIFICATE" {
+ return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
+ }
+ return x509.ParseCertificate(block.Bytes)
+}
+
+func makeIssuer(keyFile, certFile string) error {
+ keyData, key, err := makeKey()
+ if err != nil {
+ return err
+ }
+ ioutil.WriteFile(keyFile, keyData, 0600)
+ certData, _, err := makeRootCert(key, certFile)
+ if err != nil {
+ return err
+ }
+ ioutil.WriteFile(certFile, certData, 0600)
+ return nil
+}
+
+func makeKey() ([]byte, *rsa.PrivateKey, error) {
+ key, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, nil, err
+ }
+ der := x509.MarshalPKCS1PrivateKey(key)
+ if err != nil {
+ return nil, nil, err
+ }
+ buf := bytes.NewBuffer([]byte{})
+ err = pem.Encode(buf, &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: der,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ return buf.Bytes(), key, nil
+}
+
+func makeRootCert(key crypto.Signer, filename string) ([]byte, *x509.Certificate, error) {
+ serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
+ if err != nil {
+ return nil, nil, err
+ }
+ template := &x509.Certificate{
+ Subject: pkix.Name{
+ CommonName: "localca root ca " + hex.EncodeToString(serial.Bytes()[:3]),
+ },
+ SerialNumber: serial,
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(100, 0, 0),
+
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+ BasicConstraintsValid: true,
+ IsCA: true,
+ MaxPathLenZero: true,
+ }
+
+ der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
+ if err != nil {
+ return nil, nil, err
+ }
+ buf := bytes.NewBuffer([]byte{})
+ err = pem.Encode(buf, &pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: der,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ result, err := x509.ParseCertificate(der)
+ return buf.Bytes(), result, err
+}
+
+func parseIPs(ipAddresses []string) ([]net.IP, error) {
+ var parsed []net.IP
+ for _, s := range ipAddresses {
+ p := net.ParseIP(s)
+ if p == nil {
+ return nil, fmt.Errorf("invalid IP address %s", s)
+ }
+ parsed = append(parsed, p)
+ }
+ return parsed, nil
+}
+
+func publicKeysEqual(a, b interface{}) (bool, error) {
+ aBytes, err := x509.MarshalPKIXPublicKey(a)
+ if err != nil {
+ return false, err
+ }
+ bBytes, err := x509.MarshalPKIXPublicKey(b)
+ if err != nil {
+ return false, err
+ }
+ return bytes.Compare(aBytes, bBytes) == 0, nil
+}
+
+func (iss *issuer) sign(domains []string, ipAddresses []string) ([]byte, *x509.Certificate, error) {
+ var cn string
+ if len(domains) > 0 {
+ cn = domains[0]
+ } else if len(ipAddresses) > 0 {
+ cn = ipAddresses[0]
+ } else {
+ return nil, nil, fmt.Errorf("must specify at least one domain name or IP address")
+ }
+ keyData, key, err := makeKey()
+ if err != nil {
+ return nil, nil, err
+ }
+ buf := bytes.NewBuffer([]byte{})
+ buf.Write(keyData)
+
+ parsedIPs, err := parseIPs(ipAddresses)
+ if err != nil {
+ return nil, nil, err
+ }
+ serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
+ if err != nil {
+ return nil, nil, err
+ }
+ template := &x509.Certificate{
+ DNSNames: domains,
+ IPAddresses: parsedIPs,
+ Subject: pkix.Name{
+ CommonName: cn,
+ },
+ SerialNumber: serial,
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(90, 0, 0),
+
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+ BasicConstraintsValid: true,
+ IsCA: false,
+ }
+ der, err := x509.CreateCertificate(rand.Reader, template, iss.cert, key.Public(), iss.key)
+ if err != nil {
+ return nil, nil, err
+ }
+ err = pem.Encode(buf, &pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: der,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ result, err := x509.ParseCertificate(der)
+ return buf.Bytes(), result, err
+}
+
+func split(s string) (results []string) {
+ if len(s) > 0 {
+ return strings.Split(s, ",")
+ }
+ return nil
+}
diff --git a/localca/utils.go b/localca/utils.go
new file mode 100644
index 0000000..efc06f3
--- /dev/null
+++ b/localca/utils.go
@@ -0,0 +1,83 @@
+package localca
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
+ "errors"
+ "time"
+)
+
+// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
+// correspond to the private key, the domain and key type match, and expiration dates
+// are valid. It doesn't do any revocation checking.
+//
+// The returned value is the verified leaf cert.
+func validCert(name string, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) {
+ // parse public part(s)
+ var n int
+ for _, b := range der {
+ n += len(b)
+ }
+ pub := make([]byte, n)
+ n = 0
+ for _, b := range der {
+ n += copy(pub[n:], b)
+ }
+ x509Cert, err := x509.ParseCertificates(pub)
+ if err != nil || len(x509Cert) == 0 {
+ return nil, errors.New("localca: no public key found")
+ }
+ // verify the leaf is not expired and matches the domain name
+ leaf = x509Cert[0]
+ if now.Before(leaf.NotBefore) {
+ return nil, errors.New("localca: certificate is not valid yet")
+ }
+ if now.After(leaf.NotAfter) {
+ return nil, errors.New("localca: expired certificate")
+ }
+ if err := leaf.VerifyHostname(name); err != nil {
+ return nil, err
+ }
+ // ensure the leaf corresponds to the private key and matches the certKey type
+ switch pub := leaf.PublicKey.(type) {
+ case *rsa.PublicKey:
+ prv, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ return nil, errors.New("localca: private key type does not match public key type")
+ }
+ if pub.N.Cmp(prv.N) != 0 {
+ return nil, errors.New("localca: private key does not match public key")
+ }
+ default:
+ return nil, errors.New("localca: unknown public key algorithm")
+ }
+ return leaf, nil
+}
+
+// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
+// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
+// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
+//
+// Inspired by parsePrivateKey in crypto/tls/tls.go.
+func parsePrivateKey(der []byte) (crypto.Signer, error) {
+ if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
+ return key, nil
+ }
+ if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
+ switch key := key.(type) {
+ case *rsa.PrivateKey:
+ return key, nil
+ case *ecdsa.PrivateKey:
+ return key, nil
+ default:
+ return nil, errors.New("localca: unknown private key type in PKCS#8 wrapping")
+ }
+ }
+ if key, err := x509.ParseECPrivateKey(der); err == nil {
+ return key, nil
+ }
+
+ return nil, errors.New("localca: failed to parse private key")
+}
diff --git a/namegen/elfs/elfs.go b/namegen/elfs/elfs.go
new file mode 100644
index 0000000..8607e4b
--- /dev/null
+++ b/namegen/elfs/elfs.go
@@ -0,0 +1,506 @@
+/*
+Package elfs is this project's heroku style name generator.
+*/
+package elfs
+
+import (
+ "fmt"
+ "math/rand"
+ "strings"
+)
+
+// Names is the name of every Pokemon from Pokemon Vietnamese Crystal.
+var Names = []string{
+ "SEED",
+ "GRASS",
+ "FLOWE",
+ "SHAD",
+ "CABR",
+ "SNAKE",
+ "GOLD",
+ "COW",
+ "GUIKI",
+ "PEDAL",
+ "DELAN",
+ "B-FLY",
+ "BIDE",
+ "KEYU",
+ "FORK",
+ "LAP",
+ "PIGE",
+ "PIJIA",
+ "CAML",
+ "LAT",
+ "BIRD",
+ "BABOO",
+ "VIV",
+ "ABOKE",
+ "PIKAQ",
+ "RYE",
+ "SAN",
+ "BREAD",
+ "LIDEL",
+ "LIDE",
+ "PIP",
+ "PIKEX",
+ "ROK",
+ "JUGEN",
+ "PUD",
+ "BUDE",
+ "ZHIB",
+ "GELU",
+ "GRAS",
+ "FLOW",
+ "LAFUL",
+ "ATH",
+ "BALA",
+ "CORN",
+ "MOLUF",
+ "DESP",
+ "DAKED",
+ "MIMI",
+ "BOLUX",
+ "KODA",
+ "GELUD",
+ "MONK",
+ "SUMOY",
+ "GEDI",
+ "WENDI",
+ "NILEM",
+ "NILE",
+ "NILEC",
+ "KEZI",
+ "YONGL",
+ "HUDE",
+ "WANLI",
+ "GELI",
+ "GUAIL",
+ "MADAQ",
+ "WUCI",
+ "WUCI",
+ "MUJEF",
+ "JELLY",
+ "SICIB",
+ "GELU",
+ "NELUO",
+ "BOLI",
+ "JIALE",
+ "YED",
+ "YEDE",
+ "CLO",
+ "SCARE",
+ "AOCO",
+ "DEDE",
+ "DEDEI",
+ "BAWU",
+ "JIUG",
+ "BADEB",
+ "BADEB",
+ "HOLE",
+ "BALUX",
+ "GES",
+ "FANT",
+ "QUAR",
+ "YIHE",
+ "SWAB",
+ "SLIPP",
+ "CLU",
+ "DEPOS",
+ "BILIY",
+ "YUANO",
+ "SOME",
+ "NO",
+ "YELA",
+ "EMPT",
+ "ZECUN",
+ "XIAHE",
+ "BOLEL",
+ "DEJI",
+ "MACID",
+ "XIHON",
+ "XITO",
+ "LUCK",
+ "MENJI",
+ "GELU",
+ "DECI",
+ "XIDE",
+ "DASAJ",
+ "DONGN",
+ "RICUL",
+ "MINXI",
+ "BALIY",
+ "ZENDA",
+ "LUZEL",
+ "HELE5",
+ "0FENB",
+ "KAIL",
+ "JIAND",
+ "CARP",
+ "JINDE",
+ "LAPU",
+ "MUDE",
+ "YIFU",
+ "LINLI",
+ "SANDI",
+ "HUSI",
+ "JINC",
+ "OUMU",
+ "OUMUX",
+ "CAP",
+ "KUIZA",
+ "PUD",
+ "TIAO",
+ "FRMAN",
+ "CLAU",
+ "SPARK",
+ "DRAGO",
+ "BOLIU",
+ "GUAIL",
+ "MIYOU",
+ "MIY",
+ "QIAOK",
+ "BEIL",
+ "MUKEI",
+ "RIDED",
+ "MADAM",
+ "BAGEP",
+ "CROC",
+ "ALIGE",
+ "OUDAL",
+ "OUD",
+ "DADA",
+ "HEHE",
+ "YEDEA",
+ "NUXI",
+ "NUXIN",
+ "ROUY",
+ "ALIAD",
+ "STICK",
+ "QIANG",
+ "LAAND",
+ "PIQI",
+ "PI",
+ "PUPI",
+ "DEKE",
+ "DEKEJ",
+ "NADI",
+ "NADIO",
+ "MALI",
+ "PEA",
+ "ELECT",
+ "FLOWE",
+ "MAL",
+ "MALI",
+ "HUSHU",
+ "NILEE",
+ "YUZI",
+ "POPOZ",
+ "DUZI",
+ "HEBA",
+ "XIAN",
+ "SHAN",
+ "YEYEA",
+ "WUY",
+ "LUO",
+ "KEFE",
+ "HULA",
+ "CROW",
+ "YADEH",
+ "MOW",
+ "ANNAN",
+ "SUONI",
+ "KYLI",
+ "HULU",
+ "HUDEL",
+ "YEHE",
+ "GULAE",
+ "YEHE",
+ "BLU",
+ "GELAN",
+ "BOAT",
+ "NIP",
+ "POIT",
+ "HELAK",
+ "XINL",
+ "BEAR",
+ "LINB",
+ "MAGEH",
+ "MAGEJ",
+ "WULI",
+ "YIDE",
+ "RIVE",
+ "FISH",
+ "AOGU",
+ "DELIE",
+ "MANTE",
+ "KONMU",
+ "DELU",
+ "HELU",
+ "HUAN",
+ "HUMA",
+ "DONGF",
+ "JINCA",
+ "HEDE",
+ "DEFU",
+ "LIBY",
+ "JIAPA",
+ "MEJI",
+ "HELE",
+ "BUHU",
+ "MILK",
+ "HABI",
+ "THUN",
+ "GARD",
+ "DON",
+ "YANGQ",
+ "SANAQ",
+ "BANQ",
+ "LUJ",
+ "PHIX",
+ "SIEI",
+ "EGG",
+}
+
+// Moves is every single move from Pokemon Vietnamese Crystal.
+var Moves = []string{
+ "ABLE",
+ "ABNORMA",
+ "AGAIN",
+ "AIREXPL",
+ "ANG",
+ "ANGER",
+ "ASAIL",
+ "ATTACK",
+ "AURORA",
+ "AWL",
+ "BAN",
+ "BAND",
+ "BARE",
+ "BEAT",
+ "BEATED",
+ "BELLY",
+ "BIND",
+ "BITE",
+ "BLOC",
+ "BLOOD",
+ "BODY",
+ "BOOK",
+ "BREATH",
+ "BUMP",
+ "CAST",
+ "CHAM",
+ "CLAMP",
+ "CLAP",
+ "CLAW",
+ "CLEAR",
+ "CLI",
+ "CLIP",
+ "CLOUD",
+ "CONTRO",
+ "CONVY",
+ "COOLHIT",
+ "CRASH",
+ "CRY",
+ "CUT",
+ "DESCRI",
+ "D-FIGHT",
+ "DIG",
+ "DITCH",
+ "DIV",
+ "DOZ",
+ "DRE",
+ "DUL",
+ "DU-PIN",
+ "DYE",
+ "EARTH",
+ "EDU",
+ "EG-BOMB",
+ "EGG",
+ "ELEGY",
+ "ELE-HIT",
+ "EMBODY",
+ "EMPLI",
+ "ENGL",
+ "ERUPT",
+ "EVENS",
+ "EXPLOR",
+ "EYES",
+ "FALL",
+ "FAST",
+ "F-CAR",
+ "F-DANCE",
+ "FEARS",
+ "F-FIGHT",
+ "FIGHT",
+ "FIR",
+ "FIRE",
+ "FIREHIT",
+ "FLAME",
+ "FLAP",
+ "FLASH",
+ "FLEW",
+ "FORCE",
+ "FRA",
+ "FREEZE",
+ "FROG",
+ "G-BIRD",
+ "GENKISS",
+ "GIFT",
+ "G-KISS",
+ "G-MOUSE",
+ "GRADE",
+ "GROW",
+ "HAMMER",
+ "HARD",
+ "HAT",
+ "HATE",
+ "H-BOMB",
+ "HELL-R",
+ "HEMP",
+ "HINT",
+ "HIT",
+ "HU",
+ "HUNT",
+ "HYPNOSI",
+ "INHA",
+ "IRO",
+ "IRONBAR",
+ "IR-WING",
+ "J-GUN",
+ "KEE",
+ "KICK",
+ "KNIF",
+ "KNIFE",
+ "KNOCK",
+ "LEVEL",
+ "LIGH",
+ "LIGHHIT",
+ "LIGHT",
+ "LIVE",
+ "L-WALL",
+ "MAD",
+ "MAJUS",
+ "MEL",
+ "MELO",
+ "MESS",
+ "MILK",
+ "MIMI",
+ "MISS",
+ "MIXING",
+ "MOVE",
+ "MUD",
+ "NI-BED",
+ "NOISY",
+ "NOONLI",
+ "NULL",
+ "N-WAVE",
+ "PAT",
+ "PEACE",
+ "PIN",
+ "PLAN",
+ "PLANE",
+ "POIS",
+ "POL",
+ "POWDE",
+ "POWE",
+ "POWER",
+ "PRIZE",
+ "PROTECT",
+ "PROUD",
+ "RAGE",
+ "RECOR",
+ "REFLAC",
+ "REFREC",
+ "REGR",
+ "RELIV",
+ "RENEW",
+ "R-FIGHT",
+ "RING",
+ "RKICK",
+ "ROCK",
+ "ROUND",
+ "RUS",
+ "RUSH",
+ "SAND",
+ "SAW",
+ "SCISSOR",
+ "SCRA",
+ "SCRIPT",
+ "SEEN",
+ "SERVER",
+ "SHADOW",
+ "SHELL",
+ "SHINE",
+ "SHO",
+ "SIGHT",
+ "SIN",
+ "SMALL",
+ "SMELT",
+ "SMOK",
+ "SNAKE",
+ "SNO",
+ "SNOW",
+ "SOU",
+ "SO-WAVE",
+ "SPAR",
+ "SPEC",
+ "SPID",
+ "S-PIN",
+ "SPRA",
+ "STAM",
+ "STARE",
+ "STEA",
+ "STONE",
+ "STORM",
+ "STRU",
+ "STRUG",
+ "STUDEN",
+ "SUBS",
+ "SUCID",
+ "SUN-LIG",
+ "SUNRIS",
+ "SUPLY",
+ "S-WAVE",
+ "TAILS",
+ "TANGL",
+ "TASTE",
+ "TELLI",
+ "THANK",
+ "TONKICK",
+ "TOOTH",
+ "TORL",
+ "TRAIN",
+ "TRIKICK",
+ "TUNGE",
+ "VOLT",
+ "WA-GUN",
+ "WATCH",
+ "WAVE",
+ "W-BOMB",
+ "WFALL",
+ "WFING",
+ "WHIP",
+ "WHIRL",
+ "WIND",
+ "WOLF",
+ "WOOD",
+ "WOR",
+ "YUJA",
+}
+
+func randomMove() string {
+ return Moves[rand.Intn(len(Moves))]
+}
+
+func randomName() string {
+ return Names[rand.Intn(len(Names))]
+}
+
+// Next generates a new domain name based on the moves and Pokemon names
+// from Pokemon Vietnamese Crystal.
+func Next() string {
+ move1 := randomMove()
+ move2 := randomMove()
+ poke := randomName()
+ return strings.ToLower(fmt.Sprintf("%s-%s-%s", move1, move2, poke))
+}
diff --git a/namegen/elfs/elfs_test.go b/namegen/elfs/elfs_test.go
new file mode 100644
index 0000000..dd8a2d4
--- /dev/null
+++ b/namegen/elfs/elfs_test.go
@@ -0,0 +1,10 @@
+package elfs
+
+import "testing"
+
+func TestNext(t *testing.T) {
+ n := Next()
+ if len(n) == 0 {
+ t.Fatalf("MakeName had a zero output")
+ }
+}
diff --git a/namegen/tarot/doc.go b/namegen/tarot/doc.go
new file mode 100644
index 0000000..9fb79cc
--- /dev/null
+++ b/namegen/tarot/doc.go
@@ -0,0 +1,3 @@
+// Package tarot is an automatic name generator. It generates names that could
+// pass for tarot cards if you squint hard enough.
+package tarot
diff --git a/namegen/tarot/namegen.go b/namegen/tarot/namegen.go
new file mode 100644
index 0000000..ec18d11
--- /dev/null
+++ b/namegen/tarot/namegen.go
@@ -0,0 +1,40 @@
+package tarot
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+func init() {
+ rand.Seed(time.Now().Unix())
+}
+
+// The ranks and suits of this name generator.
+var (
+ Ranks = []string{
+ "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten",
+ "ace", "king", "page", "princess", "queen", "jack",
+ "king", "magus", "prince", "knight", "challenger",
+ "daughter", "son", "priestess", "shaman",
+ }
+
+ Suits = []string{
+ "clubs", "hearts", "spades", "diamonds", // common playing cards
+ "swords", "cups", "pentacles", "wands", // tarot
+ "disks", // thoth tarot
+ "coins", // karma
+ "earth", "wind", "water", "air", // classical elements
+ "aether", "spirits", "nirvana", // new age sounding things
+ "chakras", "dilutions", "goings",
+ }
+)
+
+// Next creates a name.
+func Next() string {
+ rank := Ranks[rand.Int()%len(Ranks)]
+ suit := Suits[rand.Int()%len(Suits)]
+
+ return fmt.Sprintf("%s-of-%s-%d", rank, suit, rand.Int63()%100000)
+}
diff --git a/namegen/tarot/namegen_test.go b/namegen/tarot/namegen_test.go
new file mode 100644
index 0000000..e4f2640
--- /dev/null
+++ b/namegen/tarot/namegen_test.go
@@ -0,0 +1,10 @@
+package tarot
+
+import (
+ "log"
+ "testing"
+)
+
+func TestNext(t *testing.T) {
+ log.Println(Next())
+}
diff --git a/tun2/backend.go b/tun2/backend.go
new file mode 100644
index 0000000..d94a1a8
--- /dev/null
+++ b/tun2/backend.go
@@ -0,0 +1,78 @@
+package tun2
+
+import "time"
+
+// Backend is the public state of an individual Connection.
+type Backend struct {
+ ID string
+ Proto string
+ User string
+ Domain string
+ Phi float32
+ Host string
+ Usable bool
+}
+
+type backendMatcher func(*Connection) bool
+
+func (s *Server) getBackendsForMatcher(bm backendMatcher) []Backend {
+ s.connlock.Lock()
+ defer s.connlock.Unlock()
+
+ var result []Backend
+
+ for _, c := range s.conns {
+ if !bm(c) {
+ continue
+ }
+
+ result = append(result, Backend{
+ ID: c.id,
+ Proto: c.conn.LocalAddr().Network(),
+ User: c.user,
+ Domain: c.domain,
+ Phi: float32(c.detector.Phi(time.Now())),
+ Host: c.conn.RemoteAddr().String(),
+ Usable: c.usable,
+ })
+ }
+
+ return result
+}
+
+// KillBackend forcibly disconnects a given backend but doesn't offer a way to
+// "ban" it from reconnecting.
+func (s *Server) KillBackend(id string) error {
+ s.connlock.Lock()
+ defer s.connlock.Unlock()
+
+ for _, c := range s.conns {
+ if c.id == id {
+ c.cancel()
+ return nil
+ }
+ }
+
+ return ErrNoSuchBackend
+}
+
+// GetBackendsForDomain fetches all backends connected to this server associated
+// to a single public domain name.
+func (s *Server) GetBackendsForDomain(domain string) []Backend {
+ return s.getBackendsForMatcher(func(c *Connection) bool {
+ return c.domain == domain
+ })
+}
+
+// GetBackendsForUser fetches all backends connected to this server owned by a
+// given user by username.
+func (s *Server) GetBackendsForUser(uname string) []Backend {
+ return s.getBackendsForMatcher(func(c *Connection) bool {
+ return c.user == uname
+ })