Skip to content

Commit

Permalink
updated: whois for IP & ASN
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiIgna committed Feb 20, 2025
1 parent ca00a7f commit 20cc99c
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 127 deletions.
25 changes: 9 additions & 16 deletions src/parsers.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import type { WhoisDataGroup } from './types.ts'
import type { WhoisData, WhoisDataGroup } from './types.ts'
import { splitStringBy, isDomain } from './utils.ts'

interface WhoisData {
[key: string]: string | string[] | WhoisDataGroup | {[key: string]: WhoisDataGroup} | undefined
contacts?: {[key: string]: WhoisDataGroup}
__comments: string[]
__raw: string
}

export function parseSimpleWhois(whois: string): WhoisData {
const data: WhoisData = {
__comments: [],
__raw: whois,
}

const renameLabels: {[key: string]: string} = {
NetRange: 'range',
inetnum: 'range',
Expand All @@ -37,10 +25,15 @@ export function parseSimpleWhois(whois: string): WhoisData {
}

if (whois.includes('returned 0 objects') || whois.includes('No match found')) {
return data
throw new Error('No WHOIS data found')
}

const { groups } = whoisDataToGroups(whois)
const { comments, groups } = whoisDataToGroups(whois)

const data: WhoisData = {
__comments: comments,
__raw: whois,
}

groups
.forEach((group) => {
Expand All @@ -59,7 +52,7 @@ export function parseSimpleWhois(whois: string): WhoisData {
* @see https://www.apnic.net/manage-ip/using-whois/guide/role/
*/
if (!isGroup && groupLabels.includes('role')) {
isGroup = 'Contact ' + group.role.split(' ')[1]
isGroup = 'Contact ' + group['role'].split(' ')[1]
} else if (!isGroup && groupLabels.includes('person')) {
isGroup = 'Contact ' + group['nic-hdl']
}
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ export interface WhoisDataGroup {
[key: string]: string
}

export interface WhoisData {
[key: string]: string | string[] | WhoisDataGroup | {[key: string]: WhoisDataGroup} | undefined
contacts?: {[key: string]: WhoisDataGroup}
__comments: string[]
__raw: string
}

/**
* TLD Whois response, from iana.org
*/
Expand Down
102 changes: 87 additions & 15 deletions src/whoiser.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,119 @@
import { strict as assert } from 'node:assert'
import { suite, test } from 'node:test'

import { whoisTld } from './whoiser.ts'
import { whoisAsn, whoisIp, whoisTld } from './whoiser.ts'

suite('whoisTld', () => {
test('whoisTld() - invalid tlds', function() {
suite('whoisAsn()', () => {
test('reserved ASN', async () => {
assert.rejects(whoisAsn(1))
assert.rejects(whoisAsn(4294967295))
})

test('AS15169', async () => {
const whois = await whoisAsn(15169)

assert.equal(whois['ASHandle'], 'AS15169', 'AS Number doesn\'t match')
assert.equal(whois['ASName'], 'GOOGLE', 'AS Name doesn\'t match')
})

test('AS13335', async () => {
const whois = await whoisAsn(13335)

assert.equal(whois['ASNumber'], '13335', 'AS Number doesn\'t match')
assert.equal(whois['ASHandle'], 'AS13335', 'AS Number doesn\'t match')
assert.equal(whois['ASName'], 'CLOUDFLARENET', 'AS Name doesn\'t match')
})
})

suite('whoisIp()', () => {
test('invalid IPs', () => {
assert.rejects(whoisIp(''))
assert.rejects(whoisIp('.'))
assert.rejects(whoisIp(':'))
assert.rejects(whoisIp('1'))
assert.rejects(whoisIp('1.1.1.1.1'))
})

test('1.1.1.1', async () => {
const whois = await whoisIp('1.1.1.1')

assert.equal(whois['asn'], 'AS13335')
assert.equal(whois['country'], 'AU')
assert.equal(whois['range'], '1.1.1.0 - 1.1.1.255', 'IP Range doesn\'t match')
assert.equal(whois['route'], '1.1.1.0/24', 'IP Route doesn\'t match')
})

test('8.8.8.8', async () => {
const whois = await whoisIp('8.8.8.8')

assert.equal(whois['NetName'], 'GOGL')
assert.equal(whois['organisation']['Country'], 'US')
assert.equal(whois['range'], '8.8.8.0 - 8.8.8.255', 'IP Range doesn\'t match')
assert.equal(whois['route'], '8.8.8.0/24', 'IP Route doesn\'t match')
});

test('2606:4700:4700::1111', async () => {
const whois = await whoisIp('2606:4700:4700::1111')

assert.equal(whois['asn'], 'AS13335')
assert.equal(whois['NetName'], 'CLOUDFLARENET')
assert.equal(whois['organisation']['Country'], 'US')
assert.equal(whois['range'], '2606:4700:: - 2606:4700:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'IP Range doesn\'t match')
assert.equal(whois['route'], '2606:4700::/32', 'IP Route doesn\'t match')
});

test('2001:4860:4860::8888', async () => {
const whois = await whoisIp('2001:4860:4860::8888')

assert.equal(whois['asn'], 'AS15169')
assert.equal(whois['NetName'], 'GOOGLE-IPV6')
assert.equal(whois['organisation']['Country'], 'US')
assert.equal(whois['range'], '2001:4860:: - 2001:4860:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'IP Range doesn\'t match')
assert.equal(whois['route'], '2001:4860::/32', 'IP Route doesn\'t match')
});
})

suite('whoisTld()', () => {
test('invalid TLDs', () => {
assert.rejects(whoisTld('-abc'))
assert.rejects(whoisTld('thistldshouldntexist'))
});
})

test(`whoisTld('com')`, async function() {
test('com', async () => {
const whois = await whoisTld('com')

assert.equal(whois.tld, 'COM', 'TLD doesn\'t match')
assert.equal(whois.whois, 'whois.verisign-grs.com', 'WHOIS server doesn\'t match')
});
assert.equal(whois.created, '1985-01-01')
})

test(`whoisTld('.google')`, async function() {
test('.google', async () => {
const whois = await whoisTld('.google')

assert.equal(whois.tld, 'GOOGLE', 'TLD doesn\'t match')
assert.equal(whois.whois, 'whois.nic.google', 'WHOIS server doesn\'t match')
});
})

test(`whoisTld('.香港') - IDN`, async function() {
test('.香港 - IDN', async () => {
const whois = await whoisTld('.香港')
assert.equal(whois.tld, '香港', 'TLD doesn\'t match')
assert.equal(whois.whois, 'whois.hkirc.hk', 'WHOIS server doesn\'t match')
});
})

test(`whoisTld('com.au') - SLD`, async function() {
test('com.au - SLD', async () => {
const whois = await whoisTld('com.au')
assert.equal(whois.tld, 'AU', 'TLD doesn\'t match')
assert.equal(whois.whois, 'whois.auda.org.au', 'WHOIS server doesn\'t match')
assert.equal(whois.created, '1986-03-05')
});
})

test(`whoisTld('uk') - TLD/SLD match`, async function() {
test('uk - TLD/SLD match', async () => {
const whois1 = await whoisTld('uk')
const whois2 = await whoisTld('co.uk')
const whois3 = await whoisTld('google.co.uk')

assert.equal(whois1.whois, 'whois.nic.uk', 'WHOIS server doesn\'t match')
assert.equal(whois2.whois, 'whois.nic.uk', 'WHOIS server doesn\'t match')
assert.equal(whois3.whois, 'whois.nic.uk', 'WHOIS server doesn\'t match')
});
})
})

77 changes: 47 additions & 30 deletions src/whoiser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import net from 'node:net'
import punycode from 'punycode'

import type { TldWhoisResponse } from './types.ts'
import type { TldWhoisResponse, WhoisData } from './types.ts'
import { parseSimpleWhois, parseDomainWhois, whoisDataToGroups } from './parsers.ts'
import { splitStringBy, validatedTld } from './utils.ts'

Expand Down Expand Up @@ -160,7 +160,7 @@ export const whoisDomain = async (domain, { host = null, timeout = 15000, follow

// find WHOIS server for TLD
if (!host) {
const tld = await whoisTld(domain, { timeout, domainName, domainTld })
const tld = await whoisTld(domain, timeout)

if (!tld.whois) {
throw new Error(`TLD for "${domain}" not supported`)
Expand All @@ -184,7 +184,7 @@ export const whoisDomain = async (domain, { host = null, timeout = 15000, follow
}

try {
resultRaw = await whoisQuery({ host, query, timeout })
resultRaw = await whoisQuery(host, query, { timeout })
result = parseDomainWhois(domain, resultRaw, ignorePrivacy)
} catch (err) {
result = { error: err.message }
Expand Down Expand Up @@ -233,48 +233,65 @@ export const whoisDomain = async (domain, { host = null, timeout = 15000, follow
return results
}

export const whoisIpOrAsn = async (query, { host = null, timeout = 15000, follow = 2, raw = false } = {}) => {
const type = net.isIP(query) ? 'ip' : 'asn'
query = String(query)
export async function whoisIp(ip: string, { host = null, timeout = 15000 } = {}): Promise<WhoisData> {
if (!net.isIP(ip)) {
throw new Error(`Invalid IP address "${ip}"`)
}

// find WHOIS server for IP
if (!host) {
let whoisResult = await whoisQuery({ host: 'whois.iana.org', query, timeout })
whoisResult = parseSimpleWhois(whoisResult)

if (whoisResult.whois) {
host = whoisResult.whois
}
host = await findWhoisServerInIana(ip)
}

if (!host) {
throw new Error(`No WHOIS server for "${query}"`)
throw new Error(`No WHOIS server for "${ip}"`)
}

let data
let modifiedQuery = ip

while (host && follow) {
let modifiedQuery = query
// hardcoded custom queries..
if (host === 'whois.arin.net') {
modifiedQuery = `+ n ${ip}`
}

// hardcoded custom queries..
if (host === 'whois.arin.net' && type === 'ip') {
modifiedQuery = `+ n ${query}`
} else if (host === 'whois.arin.net' && type === 'asn') {
modifiedQuery = `+ a ${query}`
}
const ipWhoisResult = await whoisQuery(host, modifiedQuery, { timeout })

const rawResult = await whoisQuery({ host, query: modifiedQuery, timeout })
data = parseSimpleWhois(rawResult)
return parseSimpleWhois(ipWhoisResult)
}

if (raw) {
data.__raw = rawResult
}
async function findWhoisServerInIana(query: string) {
let whoisResult = await whoisQuery('whois.iana.org', query)
const { groups } = whoisDataToGroups(whoisResult)

follow--
host = data?.ReferralServer?.split('//')?.[1]
const groupWithWhois = groups.find((group) => Object.keys(group).includes('whois'))

return groupWithWhois['whois']
}

export async function whoisAsn(asn: number, { host = null, timeout = 15000 } = {}) {
if (asn < 0 || asn > 4294967295) {
throw new Error(`Invalid ASN number "${asn}"`)
}

// find WHOIS server for ASN
if (!host) {
host = await findWhoisServerInIana(String(asn))
}

return data
if (!host) {
throw new Error(`No WHOIS server for "${asn}"`)
}

let modifiedQuery = String(asn)

// hardcoded custom queries..
if (host === 'whois.arin.net') {
modifiedQuery = `+ a ${asn}`
}

const asnWhoisResult = await whoisQuery(host, modifiedQuery, { timeout })

return parseSimpleWhois(asnWhoisResult)
}

export const firstResult = (whoisResults) => {
Expand Down
66 changes: 0 additions & 66 deletions test/test.js

This file was deleted.

0 comments on commit 20cc99c

Please sign in to comment.