cluster/identd/kubenat: implement
This is a library to find pod information for a given TCP 4-tuple.
Change-Id: I254983e579e3aaa04c0c5491851f4af94a3f4249
diff --git a/cluster/identd/kubenat/translation_test.go b/cluster/identd/kubenat/translation_test.go
new file mode 100644
index 0000000..353d291
--- /dev/null
+++ b/cluster/identd/kubenat/translation_test.go
@@ -0,0 +1,130 @@
+package kubenat
+
+import (
+ "context"
+ "flag"
+ "io/ioutil"
+ "net"
+ "os"
+ "testing"
+
+ "github.com/go-test/deep"
+)
+
+// testConntrack is the anonymized content of a production host.
+// The first entry is an appservice-irc connection from a pod to an IRC server.
+// The second connection is an UDP connection between two pods.
+// The third to last entry is not a NAT entry, but an incoming external
+// connection.
+// The fourth connection has a mangled/incomplete entry.
+const testConntrack = `
+ipv4 2 tcp 6 86384 ESTABLISHED src=10.10.26.23 dst=192.0.2.180 sport=51336 dport=6697 src=192.0.2.180 dst=185.236.240.36 sport=6697 dport=28706 [ASSURED] mark=0 zone=0 use=2
+ipv4 2 udp 17 35 src=10.10.24.162 dst=10.10.26.108 sport=49347 dport=53 src=10.10.26.108 dst=10.10.24.162 sport=53 dport=49347 [ASSURED] mark=0 zone=0 use=2
+ipv4 2 tcp 6 2 SYN_SENT src=198.51.100.67 dst=185.236.240.56 sport=51053 dport=3359 [UNREPLIED] src=185.236.240.56 dst=198.51.100.67 sport=3359 dport=51053 mark=0 zone=0 use=2
+ipv4 2 tcp 6 2
+`
+
+// TestConntrackParse exercises the conntrack parser for all entries in testConntrack.
+func TestConntrackParse(t *testing.T) {
+ // Last line is truncated and should be ignored.
+ got, err := conntrackParse([]byte(testConntrack))
+ if err != nil {
+ t.Fatalf("conntrackParse: %v", err)
+ }
+ want := []conntrackEntry{
+ {
+ "ipv4", "tcp", 86384, "ESTABLISHED",
+ map[string]string{
+ "src": "10.10.26.23", "dst": "192.0.2.180", "sport": "57640", "dport": "6697",
+ "mark": "0", "zone": "0", "use": "2",
+ },
+ map[string]string{
+ "src": "192.0.2.180", "dst": "185.236.240.36", "sport": "6697", "dport": "28706",
+ },
+ map[string]bool{
+ "ASSURED": true,
+ },
+ },
+ {
+ "ipv4", "udp", 35, "",
+ map[string]string{
+ "src": "10.10.24.162", "dst": "10.10.26.108", "sport": "49347", "dport": "53",
+ "mark": "0", "zone": "0", "use": "2",
+ },
+ map[string]string{
+ "src": "10.10.26.108", "dst": "10.10.24.162", "sport": "53", "dport": "49347",
+ },
+ map[string]bool{
+ "ASSURED": true,
+ },
+ },
+ {
+ "ipv4", "tcp", 2, "SYN_SENT",
+ map[string]string{
+ "src": "198.51.100.67", "dst": "185.236.240.56", "sport": "51053", "dport": "3359",
+ "mark": "0", "zone": "0", "use": "2",
+ },
+ map[string]string{
+ "src": "185.236.240.56", "dst": "198.51.100.67", "sport": "3359", "dport": "51053",
+ },
+ map[string]bool{
+ "UNREPLIED": true,
+ },
+ },
+ }
+ if diff := deep.Equal(want, got); diff != nil {
+ t.Error(diff)
+ }
+
+ ix := buildIndex(got)
+ if want, got := 0, len(ix.getByRequest("src", "1.2.3.4")); want != got {
+ t.Errorf("by request, src, 1.2.3.4 should have returned %d result, wanted %d", want, got)
+ }
+ if want, got := 1, len(ix.getByRequest("src", "10.10.26.23")); want != got {
+ t.Errorf("by request, src, 1.2.3.4 should have returned %d result, wanted %d", want, got)
+ }
+ if want, got := "10.10.26.23", ix.getByRequest("src", "10.10.26.23")[0].request["src"]; want != got {
+ t.Errorf("by request, wanted src %q, got %q", want, got)
+ }
+ if want, got := 3, len(ix.getByRequest("mark", "0")); want != got {
+ t.Errorf("by request, mark, 0 should have returned %d result, wanted %d", want, got)
+ }
+}
+
+// TestTranslationWorker exercises a translation worker with a
+// testConntrack-backed conntrack file.
+func TestTranslationWorker(t *testing.T) {
+ flag.Set("logtostderr", "true")
+ tmpfile, err := ioutil.TempFile("", "conntack")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmpfile.Name())
+ if _, err := tmpfile.Write([]byte(testConntrack)); err != nil {
+ t.Fatal(err)
+ }
+ r := &Resolver{
+ conntrackPath: tmpfile.Name(),
+ translationC: make(chan *translationReq),
+ }
+ ctx, ctxC := context.WithCancel(context.Background())
+ defer ctxC()
+
+ go r.runTranslationWorker(ctx)
+
+ res, err := r.translate(ctx, &Tuple4{
+ RemoteIP: net.ParseIP("192.0.2.180"),
+ RemotePort: 6697,
+ LocalIP: net.ParseIP("185.236.240.36"),
+ LocalPort: 28706,
+ })
+ if err != nil {
+ t.Fatalf("translate: %v", err)
+ }
+ if want, got := net.ParseIP("10.10.26.23"), res.localIP; !want.Equal(got) {
+ t.Errorf("local ip: wanted %v, got %v", want, got)
+ }
+ if want, got := uint16(51336), res.localPort; want != got {
+ t.Errorf("local port: wanted %d, got %d", want, got)
+ }
+}