| package provider |
| |
| // Support for the ARIN IRR. |
| // ARIN is special. We have to query them via whois. It's also not the same |
| // whois as you usually see. And also not many autnums (even big players like |
| // AS15169 and AS112) have IRR entries at all. |
| |
| import ( |
| "context" |
| "fmt" |
| "strings" |
| |
| "code.hackerspace.pl/hscloud/bgpwtf/cccampix/irr/whois" |
| pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| ) |
| |
| const ARINWhois = "rr.arin.net:43" |
| |
| type arin struct { |
| sem chan struct{} |
| } |
| |
| func NewARIN(limit int) Provider { |
| return &arin{ |
| sem: make(chan struct{}, limit), |
| } |
| } |
| |
| func (a *arin) Query(ctx context.Context, asn uint64) (*pb.IRRQueryResponse, error) { |
| a.sem <- struct{}{} |
| defer func() { |
| <-a.sem |
| }() |
| |
| data, err := whois.Query(ctx, ARINWhois, fmt.Sprintf("AS%d", asn)) |
| if err != nil { |
| return nil, status.Errorf(codes.Unavailable, "could not contact ARIN IRR: %v", err) |
| } |
| |
| lines := strings.Split(data, "\n") |
| |
| // Convert possibly 'continued' RPSL entries into single-line entries. |
| // eg. |
| // import: from AS6083 |
| // action pref=10; |
| // accept ANY |
| // into |
| // 'import', 'from AS6083, action pref=10; accept ANY' |
| |
| attrs := []rpslRawAttribute{} |
| for _, line := range lines { |
| if strings.HasPrefix(strings.TrimSpace(line), "%") { |
| // Comment |
| continue |
| } |
| if strings.TrimSpace(line) == "" { |
| // Empty line |
| continue |
| } |
| |
| if strings.HasPrefix(line, " ") { |
| // Continuation |
| if len(attrs) < 1 { |
| return nil, status.Errorf(codes.Unavailable, "unparseable IRR, continuation with no previous atribute name: %q", line) |
| } |
| |
| attrs[len(attrs)-1].value += " " + strings.TrimSpace(line) |
| } else { |
| parts := strings.SplitN(line, ":", 2) |
| if len(parts) != 2 { |
| return nil, status.Errorf(codes.Unavailable, "unparseable IRR, line with no attribute key: %q", line) |
| } |
| name := strings.TrimSpace(parts[0]) |
| value := strings.TrimSpace(parts[1]) |
| attrs = append(attrs, rpslRawAttribute{ |
| name: name, |
| value: value, |
| }) |
| } |
| } |
| |
| if len(attrs) == 0 { |
| return nil, status.Errorf(codes.NotFound, "no such ASN") |
| } |
| |
| return &pb.IRRQueryResponse{ |
| Source: pb.IRRQueryResponse_SOURCE_ARIN, |
| Attributes: parseAttributes(attrs), |
| }, nil |
| } |