diff --git a/Dockerfile b/Dockerfile.gortr similarity index 100% rename from Dockerfile rename to Dockerfile.gortr diff --git a/Dockerfile.prod b/Dockerfile.gortr.prod similarity index 100% rename from Dockerfile.prod rename to Dockerfile.gortr.prod diff --git a/Dockerfile.rtrdump b/Dockerfile.rtrdump new file mode 100644 index 0000000..6f52883 --- /dev/null +++ b/Dockerfile.rtrdump @@ -0,0 +1,24 @@ +ARG src_dir="/go/src/github.com/cloudflare/gortr" + +FROM golang:alpine as builder +ARG src_dir + +RUN apk --update --no-cache add git && \ + mkdir -p ${src_dir} + +WORKDIR ${src_dir} +COPY . . + +RUN go get -u github.com/golang/dep/cmd/dep && \ + dep ensure && \ + go build cmd/rtrdump/rtrdump.go + +FROM alpine:latest +ARG src_dir + +RUN apk --update --no-cache add ca-certificates && \ + adduser -S -D -H -h / rtr +USER rtr + +COPY --from=builder ${src_dir}/rtrdump / +ENTRYPOINT ["./rtrdump"] diff --git a/Dockerfile.rtrdump.prod b/Dockerfile.rtrdump.prod new file mode 100644 index 0000000..b08d1ee --- /dev/null +++ b/Dockerfile.rtrdump.prod @@ -0,0 +1,17 @@ +ARG src_uri=github.com/cloudflare/gortr/cmd/rtrdump + +FROM golang:alpine as builder +ARG src_uri + +RUN apk --update --no-cache add git && \ + go get -u $src_uri + +FROM alpine:latest +ARG src_uri + +RUN apk --update --no-cache add ca-certificates && \ + adduser -S -D -H -h / rtr +USER rtr + +COPY --from=builder /go/bin/rtrdump / +ENTRYPOINT ["./rtrdump"] diff --git a/cmd/rtrdump/rtrdump.go b/cmd/rtrdump/rtrdump.go new file mode 100644 index 0000000..943bf0f --- /dev/null +++ b/cmd/rtrdump/rtrdump.go @@ -0,0 +1,124 @@ +package main + +import ( + "flag" + "fmt" + rtr "github.com/cloudflare/gortr/lib" + "github.com/cloudflare/gortr/prefixfile" + log "github.com/sirupsen/logrus" + "os" + "runtime" + "encoding/json" + "time" + "io" + "crypto/tls" +) + +const AppVersion = "RTRdump 0.9.4" + +var ( + Connect = flag.String("connect", "127.0.0.1:8282", "Connection address") + OutFile = flag.String("file", "output.json", "Output file") + + UseTLS = flag.Bool("tls.enable", false, "Use TLS") + ValidateCert = flag.Bool("tls.validate", true, "Validate TLS") + + RefreshInterval = flag.Int("refresh", 600, "Refresh interval in seconds") + + LogLevel = flag.String("loglevel", "info", "Log level") + Version = flag.Bool("version", false, "Print version") +) + +type Client struct { + Data prefixfile.ROAList +} + +func (c *Client) HandlePDU(cs *rtr.ClientSession, pdu rtr.PDU) { + log.Debugf("Received: %v", pdu) + switch pdu := pdu.(type) { + case *rtr.PDUIPv4Prefix: + rj := prefixfile.ROAJson{ + Prefix: pdu.Prefix.String(), + ASN: fmt.Sprintf("AS%v", pdu.ASN), + Length: pdu.MaxLen, + } + c.Data.Data = append(c.Data.Data, rj) + c.Data.Metadata.Counts++ + case *rtr.PDUIPv6Prefix: + rj := prefixfile.ROAJson{ + Prefix: pdu.Prefix.String(), + ASN: fmt.Sprintf("AS%v", pdu.ASN), + Length: pdu.MaxLen, + } + c.Data.Data = append(c.Data.Data, rj) + c.Data.Metadata.Counts++ + case *rtr.PDUEndOfData: + t := time.Now().UTC().UnixNano()/1000000000 + c.Data.Metadata.Generated = int(t) + c.Data.Metadata.Valid = int(pdu.SerialNumber) + cs.Disconnect() + } +} + +func (c *Client) Connected(cs *rtr.ClientSession) { + cs.SendResetQuery() +} + +func (c *Client) Disconnected(cs *rtr.ClientSession) { + +} + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + flag.Parse() + if *Version { + fmt.Println(AppVersion) + os.Exit(0) + } + + lvl, _ := log.ParseLevel(*LogLevel) + log.SetLevel(lvl) + + + cc := rtr.ClientConfiguration{ + ProtocolVersion: rtr.PROTOCOL_VERSION_0, + Log: log.StandardLogger(), + } + + client := &Client{ + Data: prefixfile.ROAList{ + Metadata: prefixfile.MetaData{ + }, + Data: make([]prefixfile.ROAJson, 0), + }, + } + + clientSession := rtr.NewClientSession(cc, client) + + config := &tls.Config{ + InsecureSkipVerify: !*ValidateCert, + } + err := clientSession.Start(*Connect, *UseTLS, config) + if err != nil { + log.Fatal(err) + } + + var f io.Writer + if *OutFile != "" { + ff, err := os.Create(*OutFile) + defer ff.Close() + if err != nil { + log.Fatal(err) + } + f = ff + } else { + f = os.Stdout + } + + enc := json.NewEncoder(f) + err = enc.Encode(client.Data) + if err != nil { + log.Fatal(err) + } +} diff --git a/lib/client.go b/lib/client.go new file mode 100644 index 0000000..1eb6079 --- /dev/null +++ b/lib/client.go @@ -0,0 +1,158 @@ +package rtrlib + +import ( + "net" + "time" + "crypto/tls" +) + +type RTRClientSessionEventHandler interface { + //RequestCache(*ClientSession) + HandlePDU(*ClientSession, PDU) +} + +type Logger interface{ + Debugf(string, ...interface{}) + Printf(string, ...interface{}) + Errorf(string, ...interface{}) + Infof(string, ...interface{}) +} + +type ClientSession struct { + version uint8 + + connected bool + + curserial uint32 + transmits chan PDU + quit chan bool + + tcpconn net.Conn + + handler RTRClientSessionEventHandler + + log Logger +} + +type ClientConfiguration struct { + ProtocolVersion uint8 + EnforceVersion bool + + RefreshInterval uint32 + RetryInterval uint32 + ExpireInterval uint32 + + Log Logger +} + +func NewClientSession(configuration ClientConfiguration, handler RTRClientSessionEventHandler) *ClientSession { + return &ClientSession{ + transmits: make(chan PDU, 256), + quit: make(chan bool), + log: configuration.Log, + handler: handler, + } +} + +func (c *ClientSession) SendResetQuery() { + pdu := &PDUResetQuery{} + c.SendPDU(pdu) +} + +func (c *ClientSession) SendSerialQuery(serial uint32) { + pdu := &PDUSerialQuery{ + // to fill + } + c.SendPDU(pdu) +} + +func (c *ClientSession) SendPDU(pdu PDU) { + pdu.SetVersion(c.version) + c.SendRawPDU(pdu) +} + +func (c *ClientSession) SendRawPDU(pdu PDU) { + c.transmits <- pdu +} + +func (c *ClientSession) sendLoop() { + for c.connected { + select { + case pdu := <-c.transmits: + c.tcpconn.Write(pdu.Bytes()) + case <-c.quit: + break + } + } +} + +func (c *ClientSession) refreshLoop() { + for c.connected { + select { + case <-time.After(20*time.Second): + // send refresh + } + } +} + +func (c *ClientSession) Disconnect() { + c.connected = false + //log.Debugf("Disconnecting client %v", c.String()) + //if c.handler != nil { + // c.handler.ClientDisconnected(c) + //} + select { + case c.quit <- true: + default: + + } + + c.tcpconn.Close() +} + +func (c *ClientSession) StartWithConn(tcpconn net.Conn) error { + c.tcpconn = tcpconn + c.connected = true + //if c.handler != nil { + // c.handler.ClientConnected(c) + //} + + go c.sendLoop() + c.SendResetQuery() + for c.connected { + dec, err := Decode(c.tcpconn) + if err != nil || dec == nil { + if c.log != nil { + c.log.Errorf("Error %v", err) + } + c.Disconnect() + return err + } + if c.handler != nil { + c.handler.HandlePDU(c, dec) + } + } + + return nil +} + +func (c *ClientSession) Start(addr string, useTls bool, config *tls.Config) error { + addrTCP, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return err + } + if useTls { + tcpconn, err := tls.Dial("tcp", addr, config) + if err != nil { + return err + } + return c.StartWithConn(tcpconn) + } else { + tcpconn, err := net.DialTCP("tcp", nil, addrTCP) + if err != nil { + return err + } + return c.StartWithConn(tcpconn) + } + return nil +} \ No newline at end of file diff --git a/lib/server.go b/lib/server.go index 3205efe..a1daa94 100644 --- a/lib/server.go +++ b/lib/server.go @@ -631,6 +631,7 @@ func (c *Client) Start() { buf := make([]byte, 8000) for c.connected { + // Remove this? length, err := c.tcpconn.Read(buf) if err != nil || length == 0 { log.Debugf("Error %v", err) diff --git a/lib/structs.go b/lib/structs.go index 8f4195f..9a96709 100644 --- a/lib/structs.go +++ b/lib/structs.go @@ -598,14 +598,30 @@ func Decode(rdr io.Reader) (PDU, error) { Prefix: ipnet, }, nil case PDU_ID_END_OF_DATA: - if len(toread) != 4 { - return nil, errors.New(fmt.Sprintf("Wrong length for End of Data PDU: %v != 4", len(toread))) + if len(toread) != 4 && len(toread) != 16 { + return nil, errors.New(fmt.Sprintf("Wrong length for End of Data PDU: %v != 4 or != 16", len(toread))) } - serial := binary.BigEndian.Uint32(toread) + + var serial uint32 + var refreshInterval uint32 + var retryInterval uint32 + var expireInterval uint32 + if len(toread) == 4 { + serial = binary.BigEndian.Uint32(toread) + } else if len(toread) == 16 { + serial = binary.BigEndian.Uint32(toread[0:4]) + refreshInterval = binary.BigEndian.Uint32(toread[4:8]) + retryInterval = binary.BigEndian.Uint32(toread[8:12]) + expireInterval = binary.BigEndian.Uint32(toread[12:16]) + } + return &PDUEndOfData{ Version: pver, SessionId: sessionId, SerialNumber: serial, + RefreshInterval: refreshInterval, + RetryInterval: retryInterval, + ExpireInterval: expireInterval, }, nil case PDU_ID_CACHE_RESET: if len(toread) != 0 { diff --git a/prefixfile/prefixfile.go b/prefixfile/prefixfile.go index 54cdfee..4125b7d 100644 --- a/prefixfile/prefixfile.go +++ b/prefixfile/prefixfile.go @@ -80,19 +80,19 @@ type ROAJson struct { Prefix string `json:"prefix"` Length uint8 `json:"maxLength"` ASN string `json:"asn"` - TA string `json:"ta"` + TA string `json:"ta,omitempty"` } type MetaData struct { Counts int `json:"counts"` Generated int `json:"generated"` - Valid int `json:"valid"` + Valid int `json:"valid,omitempty"` Signature string `json:"signature,omitempty"` SignatureDate string `json:"signatureDate,omitempty"` } type ROAList struct { - Metadata MetaData `json:"metadata"` + Metadata MetaData `json:"metadata,omitempty"` Data []ROAJson `json:"roas"` }