Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
080b641ce6 |
@@ -36,8 +36,7 @@ tested.
|
||||
- 2026-10-26: the WHOIS fetcher backs off more slowly. A failure makes its wait
|
||||
1.5 times as long, up to one minute instead of five. Its queries to RIPE,
|
||||
APNIC and AFRINIC leave out person and role objects, which RIPE counts against
|
||||
a daily limit per address. APNIC and AFRINIC are asked for the AS's
|
||||
organisation on its own. A refused query is logged with the RIR's error line
|
||||
a daily limit per address. A refused query is logged with the RIR's error line
|
||||
(closes #159)
|
||||
- 2026-10-26: the home page's AS name search lists the ASes whose handle or
|
||||
description holds the text, as links to their pages, from the new
|
||||
|
||||
+23
-44
@@ -49,29 +49,26 @@ const (
|
||||
RIRAFRNIC = "AFRINIC"
|
||||
)
|
||||
|
||||
// Query flags that keep person and role objects out of an answer. RIPE
|
||||
// counts each one it returns against a daily limit per address, and refuses
|
||||
// an address for good that passes the limit too often:
|
||||
// noPersonalFlag keeps person and role objects out of an answer. The
|
||||
// organisation object stays in it. RIPE counts the person and role objects
|
||||
// it returns against a daily limit per IP address. Role objects for abuse
|
||||
// contacts do not count. RIPE refuses an address for good that passes the
|
||||
// limit too often:
|
||||
// https://docs.db.ripe.net/Types-of-Queries/Access-Control-for-Queries/
|
||||
// APNIC and AFRINIC run the same database software.
|
||||
const (
|
||||
// noPersonalFlag leaves out person and role objects only, so the answer
|
||||
// keeps the organisation object:
|
||||
// https://docs.db.ripe.net/Types-of-Queries/Referenced-Objects-in-Query-Response/
|
||||
noPersonalFlag = "--no-personal"
|
||||
|
||||
// noReferencedFlag leaves out every object the answer's objects refer
|
||||
// to, the organisation object too. It is the only such flag APNIC and
|
||||
// AFRINIC document:
|
||||
// https://www.apnic.net/db/search/web-help.html
|
||||
// https://afrinic.net/whois-getting-started
|
||||
noReferencedFlag = "-r"
|
||||
)
|
||||
//
|
||||
// RIPE documents the flag here:
|
||||
// https://docs.db.ripe.net/Types-of-Queries/Referenced-Objects-in-Query-Response/
|
||||
// APNIC and AFRINIC list it in their answers to the query "help". AFRINIC
|
||||
// sends readers to that answer for its full list of flags:
|
||||
// https://afrinic.net/whois-getting-started
|
||||
const noPersonalFlag = "--no-personal"
|
||||
|
||||
// The error lines with which RIPE's database software refuses a query:
|
||||
// access denied to the address, and the address's limit reached. They are
|
||||
// listed under "Access Errors" in Appendix C of
|
||||
// access denied to the client's IP address, and that address's limit
|
||||
// reached. APNIC and AFRINIC run the same software. The lines are listed
|
||||
// under "Access Errors" on this page, whose URL is split over two lines:
|
||||
// https://docs.db.ripe.net/Appendices/
|
||||
// Appendix-C--RIPE-Database-Query-Server-Response-Codes-and-Messages/
|
||||
const (
|
||||
accessDeniedError = "%ERROR:201:"
|
||||
accessLimitError = "%ERROR:202:"
|
||||
@@ -164,22 +161,6 @@ func (c *Client) LookupASN(ctx context.Context, asn int) (*ASNInfo, error) {
|
||||
// The fields come from the RIR's answer alone: IANA's describes the
|
||||
// block the AS is in.
|
||||
info := c.parseResponse(asn, rir, rirResp)
|
||||
|
||||
// The answers of APNIC and AFRINIC leave out the AS's organisation
|
||||
// object, which holds the organisation's name, country and address. It
|
||||
// is asked for on its own and read after the AS's object, whose fields
|
||||
// come first.
|
||||
if (rir == RIRAPNIC || rir == RIRAFRNIC) && info.OrgID != "" {
|
||||
orgResp, orgErr := c.queryRIR(ctx, whoisServer,
|
||||
rirQuery(rir, info.OrgID))
|
||||
if orgErr != nil {
|
||||
return nil, fmt.Errorf("organisation query failed: %w", orgErr)
|
||||
}
|
||||
|
||||
rirResp += "\n" + orgResp
|
||||
info = c.parseResponse(asn, rir, rirResp)
|
||||
}
|
||||
|
||||
if info.isEmpty() {
|
||||
return nil, fmt.Errorf("%s from %s: %w", query, whoisServer, errNoFields)
|
||||
}
|
||||
@@ -199,17 +180,15 @@ func (info *ASNInfo) isEmpty() bool {
|
||||
info.RegDate == nil && info.LastMod == nil
|
||||
}
|
||||
|
||||
// rirQuery returns the query sent to rir's server for key, an AS number
|
||||
// such as "AS3333" or an organisation ID. RIPE, APNIC and AFRINIC get a
|
||||
// flag that keeps person and role objects out of the answer.
|
||||
func rirQuery(rir, key string) string {
|
||||
// rirQuery returns the query sent to rir's server for asQuery, an AS
|
||||
// number such as "AS3333". RIPE, APNIC and AFRINIC are asked to keep person
|
||||
// and role objects out of the answer.
|
||||
func rirQuery(rir, asQuery string) string {
|
||||
switch rir {
|
||||
case RIRRIPE:
|
||||
return noPersonalFlag + " " + key
|
||||
case RIRAPNIC, RIRAFRNIC:
|
||||
return noReferencedFlag + " " + key
|
||||
case RIRRIPE, RIRAPNIC, RIRAFRNIC:
|
||||
return noPersonalFlag + " " + asQuery
|
||||
default:
|
||||
return key
|
||||
return asQuery
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,24 +101,22 @@ const ripeRefusal = `% This is the RIPE Database query service.
|
||||
% denied because of a repeated excessive querying.
|
||||
`
|
||||
|
||||
// The answers of APNIC and AFRINIC to an "-r" query, which leaves out every
|
||||
// object the AS's object refers to: first for testASN, then for the
|
||||
// organisation that AS's object names.
|
||||
const (
|
||||
answerWithoutOrganisation = `% Abuse contact for 'AS3333' is 'abuse@example.net'
|
||||
// apnicAfrinicRecord is the answer of APNIC or AFRINIC for the AS when asked
|
||||
// to leave out person and role objects. Like RIPE's, it holds the abuse
|
||||
// address as a comment, the AS's object and the organisation object.
|
||||
const apnicAfrinicRecord = `% Abuse contact for 'AS3333' is 'abuse@example.net'
|
||||
|
||||
aut-num: AS3333
|
||||
as-name: EXAMPLE-AS
|
||||
org: ORG-EXAMPLE1
|
||||
admin-c: EX1-TEST
|
||||
last-modified: 2026-08-26T00:40:00Z
|
||||
`
|
||||
organisationAnswer = `organisation: ORG-EXAMPLE1
|
||||
|
||||
organisation: ORG-EXAMPLE1
|
||||
org-name: Example Networks
|
||||
country: ZA
|
||||
address: 1 Example Road
|
||||
`
|
||||
)
|
||||
|
||||
// ianaPrivateUse is IANA's answer for a private-use AS: its block and no
|
||||
// referral.
|
||||
@@ -239,11 +237,11 @@ func TestLookupASNReadsTheRIRAnswer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLookupASNAsksForTheOrganisationOnItsOwn looks up an AS at APNIC and
|
||||
// at AFRINIC. Each answers only "-r" queries, whose answer for the AS
|
||||
// leaves out its organisation object, so the client asks for that object
|
||||
// too. The organisation's name and country come from that second answer.
|
||||
func TestLookupASNAsksForTheOrganisationOnItsOwn(t *testing.T) {
|
||||
// TestLookupASNAsksAPNICAndAFRINICToLeaveOutPersonalData looks up an AS at
|
||||
// APNIC and at AFRINIC. Each answers only a query that leaves out person
|
||||
// and role objects, as RIPE does. That one answer still holds the
|
||||
// organisation object, so the organisation's name and country are filled.
|
||||
func TestLookupASNAsksAPNICAndAFRINICToLeaveOutPersonalData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, server := range []string{"whois.apnic.net", "whois.afrinic.net"} {
|
||||
@@ -253,9 +251,8 @@ func TestLookupASNAsksForTheOrganisationOnItsOwn(t *testing.T) {
|
||||
address := server + ":43"
|
||||
|
||||
info, err := lookUp(t, map[string]string{
|
||||
ianaQuery: "refer: " + server + "\n",
|
||||
address + " -r " + asQuery: answerWithoutOrganisation,
|
||||
address + " -r ORG-EXAMPLE1": organisationAnswer,
|
||||
ianaQuery: "refer: " + server + "\n",
|
||||
address + " --no-personal " + asQuery: apnicAfrinicRecord,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("LookupASN: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user