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/main.go b/bgpwtf/cccampix/irr/main.go
new file mode 100644
index 0000000..1492e6c
--- /dev/null
+++ b/bgpwtf/cccampix/irr/main.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"strconv"
+	"strings"
+
+	"github.com/golang/glog"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	"code.hackerspace.pl/hscloud/bgpwtf/cccampix/irr/provider"
+	pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto"
+	"code.hackerspace.pl/hscloud/go/mirko"
+)
+
+type service struct {
+	iana      *provider.IANA
+	providers map[provider.IRR]provider.Provider
+}
+
+func main() {
+	flag.Parse()
+	mi := mirko.New()
+
+	if err := mi.Listen(); err != nil {
+		glog.Exitf("Listen failed: %v", err)
+	}
+
+	s := &service{
+		iana: provider.NewIANA(),
+		providers: map[provider.IRR]provider.Provider{
+			provider.IRR_RIPE: provider.NewRIPE(),
+			provider.IRR_ARIN: provider.NewARIN(),
+		},
+	}
+	pb.RegisterIRRServer(mi.GRPC(), s)
+
+	if err := mi.Serve(); err != nil {
+		glog.Exitf("Serve failed: %v", err)
+	}
+
+	<-mi.Done()
+}
+
+// Query returns parsed RPSL data for a given aut-num objects in any of the
+// supported IRRs.
+func (s *service) Query(ctx context.Context, req *pb.IRRQueryRequest) (*pb.IRRQueryResponse, error) {
+	if req.As == "" {
+		return nil, status.Error(codes.InvalidArgument, "as must be given")
+	}
+
+	req.As = strings.ToLower(req.As)
+	if strings.HasPrefix(req.As, "as") {
+		req.As = req.As[2:]
+	}
+
+	asn, err := strconv.ParseUint(req.As, 10, 64)
+	if err != nil {
+		return nil, status.Error(codes.InvalidArgument, "as is invalid")
+	}
+
+	irr, err := s.iana.Who(ctx, asn)
+	if err != nil {
+		return nil, status.Errorf(codes.Unavailable, "could not find AS block delegation from IANA: %v", err)
+	}
+
+	prov, ok := s.providers[irr]
+	if !ok {
+		return nil, status.Errorf(codes.NotFound, "AS belongs to unhandled IRR %s", irr.String())
+	}
+
+	res, err := prov.Query(ctx, asn)
+	return res, err
+}