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/provider/ripe.go b/bgpwtf/cccampix/irr/provider/ripe.go
new file mode 100644
index 0000000..5b09b9f
--- /dev/null
+++ b/bgpwtf/cccampix/irr/provider/ripe.go
@@ -0,0 +1,81 @@
+package provider
+
+// Support for the RIPE IRR.
+// We use the RIPE REST DB API.
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto"
+)
+
+type ripeResponse struct {
+	Objects struct {
+		Object []ripeObject `json:"object"`
+	} `json:"objects"`
+}
+
+type ripeObject struct {
+	Type       string `json:"type"`
+	Attributes struct {
+		Attribute []ripeAttribute `json:"attribute"`
+	} `json:"attributes"`
+}
+
+type ripeAttribute struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+type ripe struct {
+}
+
+func NewRIPE() Provider {
+	return &ripe{}
+}
+
+func (r *ripe) Query(ctx context.Context, as uint64) (*pb.IRRQueryResponse, error) {
+	req, err := http.NewRequest("GET", fmt.Sprintf("http://rest.db.ripe.net/ripe/aut-num/AS%d.json", as), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	client := http.DefaultClient
+
+	res, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("could not run GET to RIPE: %v", err)
+	}
+	defer res.Body.Close()
+	bytes, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return nil, fmt.Errorf("could not read response from RIPE: %v", err)
+	}
+
+	data := ripeResponse{}
+	err = json.Unmarshal(bytes, &data)
+	if err != nil {
+		return nil, fmt.Errorf("could not decode response from RIPE: %v", err)
+	}
+
+	if len(data.Objects.Object) != 1 {
+		return nil, fmt.Errorf("could not retriev aut-num from RIPE")
+	}
+
+	attributes := make([]rpslRawAttribute, len(data.Objects.Object[0].Attributes.Attribute))
+
+	for i, attr := range data.Objects.Object[0].Attributes.Attribute {
+		attributes[i].name = attr.Name
+		attributes[i].value = attr.Value
+	}
+
+	return &pb.IRRQueryResponse{
+		Source:     pb.IRRQueryResponse_SOURCE_RIPE,
+		Attributes: parseAttributes(attributes),
+	}, nil
+}