cluster/identd/ident: add basic ident protocol server
This adds an ident protocol server and tests for it.
Change-Id: I830f85faa7dce4220bd7001635b20e88b4a8b417
diff --git a/cluster/identd/ident/server_test.go b/cluster/identd/ident/server_test.go
new file mode 100644
index 0000000..7cf53ae
--- /dev/null
+++ b/cluster/identd/ident/server_test.go
@@ -0,0 +1,158 @@
+package ident
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+ "testing"
+)
+
+// loopback sets up a net.Listener on any available TCP port and returns it and
+// a dialer function that returns open connections to that listener.
+func loopback(t *testing.T) (net.Listener, func() net.Conn) {
+ t.Helper()
+
+ lis, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("Listen: %v", err)
+ }
+
+ return lis, func() net.Conn {
+ t.Helper()
+ conn, err := net.Dial("tcp", lis.Addr().String())
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ return conn
+ }
+}
+
+// dumbHandler is a handler that returns USERID:UNIX:q3k for every request.
+func dumbHandler(ctx context.Context, w ResponseWriter, r *Request) {
+ w.SendIdent(&IdentResponse{
+ UserID: "q3k",
+ })
+}
+
+// reqRessps send an ident query to the conn and expects a response with
+// USERID:UNIX:q3k on the scanner.
+func reqResp(t *testing.T, conn net.Conn, scanner *bufio.Scanner, client, server uint16) {
+ t.Helper()
+ if _, err := fmt.Fprintf(conn, "%d,%d\r\n", client, server); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ if !scanner.Scan() {
+ t.Fatalf("Scan: %v", scanner.Err())
+ }
+ if want, got := fmt.Sprintf("%d,%d:USERID:UNIX:q3k", client, server), scanner.Text(); want != got {
+ t.Fatalf("Wanted %q, got %q", want, got)
+ }
+}
+
+// TestServeSimple exercises the basic Server functionality: responding to
+// ident requests.
+func TestServeSimple(t *testing.T) {
+ lis, dial := loopback(t)
+ defer lis.Close()
+
+ isrv := NewServer()
+ isrv.HandleFunc(dumbHandler)
+ go isrv.Serve(lis)
+
+ conn := dial()
+ defer conn.Close()
+ scanner := bufio.NewScanner(conn)
+
+ // Send a request, expect response.
+ reqResp(t, conn, scanner, 123, 234)
+ // Send another request on the same conn, expect response.
+ reqResp(t, conn, scanner, 234, 345)
+
+ // Send another request in parallel, expect response.
+ conn2 := dial()
+ defer conn2.Close()
+ scanner2 := bufio.NewScanner(conn2)
+ reqResp(t, conn2, scanner2, 345, 456)
+}
+
+// TestServeError exercises situations where the server has to deal with
+// nasty/broken clients.
+func TestServeErrors(t *testing.T) {
+ lis, dial := loopback(t)
+ defer lis.Close()
+
+ isrv := NewServer()
+ isrv.HandleFunc(dumbHandler)
+ go isrv.Serve(lis)
+
+ conn := dial()
+ defer conn.Close()
+
+ // Send something that's not ident.
+ fmt.Fprintf(conn, "GET / HTTP/1.1\r\n\r\n")
+ // Expect EOF on read.
+ data := make([]byte, 100)
+ _, err := conn.Read(data)
+ if want, got := io.EOF, err; want != got {
+ t.Fatalf("Expected %v, got %v", want, got)
+ }
+
+ conn = dial()
+ defer conn.Close()
+
+ // Send a very long request line, expect to not be served.
+ fmt.Fprintf(conn, "123,%s123\r\n", strings.Repeat(" ", 4096))
+ data = make([]byte, 100)
+ _, err = conn.Read(data)
+ // In a large write, the connection will be closed by the server before
+ // we're finished writing. That will cause the connection to be reset, not
+ // just EOF'd as above.
+ if err == nil {
+ t.Fatalf("Read did not fail")
+ }
+}
+
+// TestServerRestart ensures that the server's serve/stop logic works as expected.
+func TestServerRestart(t *testing.T) {
+ lis, dial := loopback(t)
+ defer lis.Close()
+
+ isrv := NewServer()
+ isrv.HandleFunc(dumbHandler)
+
+ // Stop the server before it's even started.
+ isrv.Stop()
+
+ // The server should now exit immediately.
+ if err := isrv.Serve(lis); err != nil {
+ t.Fatalf("Serve: %v", err)
+ }
+
+ // On a subsequent run it should, however, start and serve.
+ go isrv.Serve(lis)
+
+ conn := dial()
+ defer conn.Close()
+ scanner := bufio.NewScanner(conn)
+
+ // Send a request, expect response.
+ reqResp(t, conn, scanner, 123, 234)
+
+ // Attempting another simultaneous Serve() shoud fail.
+ if err := isrv.Serve(lis); err == nil {
+ t.Fatal("Serve() returned nil, wanted error")
+ }
+
+ // Send a request, expect response.
+ reqResp(t, conn, scanner, 234, 345)
+
+ // Stop server, restart server.
+ isrv.Stop()
+ go isrv.Serve(lis)
+
+ // Send a request, expect response.
+ reqResp(t, conn, scanner, 345, 456)
+}