bgpwtf/cccampix: add IRR daemon

We add a small IRR service for getting a parsed RPSL from IRRs. For now,
we only support RIPE and ARIN, and only the following attributes:
 - remarks
 - import
 - export

Since RPSL/RFC2622 is fucking insane, there is no guarantee that the
parser, especially the import/export parser, is correct. But it should
be good enough for our use. We even throw in some tests for good
measure.

    $ grpcurl -format text -plaintext -d 'as: "26625"' 127.0.0.1:4200 ix.IRR.Query
    source: SOURCE_ARIN
    attributes: <
      import: <
        expressions: <
          peering: "AS6083"
          actions: "pref=10"
        >
        filter: "ANY"
      >
    >
    attributes: <
      import: <
        expressions: <
          peering: "AS12491"
          actions: "pref=10"
        >
        filter: "ANY"
      >
    >

Change-Id: I8b240ffe2cd3553a25ce33dbd3917c0aef64e804
diff --git a/bgpwtf/cccampix/irr/whois/whois.go b/bgpwtf/cccampix/irr/whois/whois.go
new file mode 100644
index 0000000..0e313b1
--- /dev/null
+++ b/bgpwtf/cccampix/irr/whois/whois.go
@@ -0,0 +1,32 @@
+package whois
+
+// Support for the WHOIS protocol.
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"strings"
+)
+
+// Query returns a semi-raw response from a WHOIS server for a given query.
+// We only convert \r\n to \n, and then do no other transformation to the data.
+func Query(ctx context.Context, server string, query string) (string, error) {
+	var d net.Dialer
+	conn, err := d.DialContext(ctx, "tcp", server)
+	if err != nil {
+		return "", fmt.Errorf("while dialing %q: %v", server, err)
+	}
+
+	defer conn.Close()
+
+	fmt.Fprintf(conn, "%s\r\n", query)
+
+	data, err := ioutil.ReadAll(conn)
+	if err != nil {
+		return "", fmt.Errorf("while receiving data from %q: %v", server, err)
+	}
+
+	return strings.ReplaceAll(string(data), "\r", ""), nil
+}