Skip to content

Commit ed2519e

Browse files
committed
feat(new tool): PTR/ARPA generator/decoder
Fix #246
1 parent 1cafdfa commit ed2519e

File tree

6 files changed

+105
-6
lines changed

6 files changed

+105
-6
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import { fromARPA } from '@/utils/ip';
3+
4+
const arpaInput = ref('');
5+
6+
const decodedIP = computed(() => {
7+
try {
8+
return fromARPA(arpaInput.value.trim());
9+
}
10+
catch (e: any) {
11+
return `# ${e.toString()}`;
12+
}
13+
});
14+
</script>
15+
16+
<template>
17+
<div>
18+
<NFormItem label="ARPA Address:" label-placement="left" mb-2>
19+
<NInput v-model:value="arpaInput" placeholder="e.g. 10.1.168.192.in-addr.arpa. or xxxx.ip6.arpa." />
20+
</NFormItem>
21+
22+
<c-card v-if="decodedIP">
23+
<textarea-copyable :value="decodedIP" />
24+
</c-card>
25+
</div>
26+
</template>

src/tools/arpa-decoder/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { World } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'ARPA Decoder',
6+
path: '/arpa-decoder',
7+
description: 'Decode IPv4/6 ARPA addresses or PTR record',
8+
keywords: ['arpa', 'ptr', 'ip', 'dns', 'decoder'],
9+
component: () => import('./arpa-decoder.vue'),
10+
icon: World,
11+
createdAt: new Date('2026-01-01'),
12+
category: 'Network',
13+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { World } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'PTR DNS Generator',
6+
path: '/ptr-dns-generator',
7+
description: 'Generate DNS PTR records',
8+
keywords: ['ptr', 'arpa', 'ip', 'dns', 'generator'],
9+
component: () => import('./ptr-dns-generator.vue'),
10+
icon: World,
11+
createdAt: new Date('2026-01-01'),
12+
category: 'Network',
13+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import { toARPA } from '@/utils/ip';
3+
4+
const ipInput = ref('');
5+
const ptrRecord = computed(() => {
6+
try {
7+
return toARPA(ipInput.value.trim());
8+
}
9+
catch (e: any) {
10+
return `# ${e.toString()}`;
11+
}
12+
});
13+
</script>
14+
15+
<template>
16+
<div>
17+
<NFormItem label="IP Address (IPv4 or IPv6):" label-placement="left" mb-2>
18+
<NInput v-model:value="ipInput" placeholder="e.g. 192.168.1.10 or 2001:db8::1" />
19+
</NFormItem>
20+
21+
<c-card v-if="ptrRecord">
22+
<textarea-copyable :value="ptrRecord" />
23+
</c-card>
24+
</div>
25+
</template>

src/utils/ip.test.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import { getIPNetworkType, getNetworksCount, getSubnets, parseAsCIDR, to6to4Prefix, toARPA, toIPv4MappedAddress, toIPv4MappedAddressDecimal } from './ip';
2+
import { fromARPA, getIPNetworkType, getNetworksCount, getSubnets, parseAsCIDR, to6to4Prefix, toARPA, toIPv4MappedAddress, toIPv4MappedAddressDecimal } from './ip';
33

44
describe('ipv4/6 util', () => {
55
describe('parseAsCIDR', () => {
@@ -195,10 +195,10 @@ describe('ipv4/6 util', () => {
195195

196196
describe('toARPA', () => {
197197
it('returns ARPA address', () => {
198-
expect(toARPA('1.1.1.1')).to.eql('1.1.1.1.in-addr.arpa'); // NOSONAR
199-
expect(toARPA('10.10.1.1')).to.eql('1.1.10.10.in-addr.arpa'); // NOSONAR
200-
expect(toARPA('192.168.1.1')).to.eql('1.1.168.192.in-addr.arpa'); // NOSONAR
201-
expect(toARPA('255.255.255.0')).to.eql('0.255.255.255.in-addr.arpa'); // NOSONAR
198+
expect(toARPA('1.1.1.1')).to.eql('1.1.1.1.in-addr.arpa.'); // NOSONAR
199+
expect(toARPA('10.10.1.1')).to.eql('1.1.10.10.in-addr.arpa.'); // NOSONAR
200+
expect(toARPA('192.168.1.1')).to.eql('1.1.168.192.in-addr.arpa.'); // NOSONAR
201+
expect(toARPA('255.255.255.0')).to.eql('0.255.255.255.in-addr.arpa.'); // NOSONAR
202202
expect(toARPA('FF02::2')).to.eql('2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.f.f.ip6.arpa.'); // NOSONAR
203203
expect(toARPA('2345:0425:2CA1:0000:0000:0567:5673:23b5')).to.eql('5.b.3.2.3.7.6.5.7.6.5.0.0.0.0.0.0.0.0.0.1.a.c.2.5.2.4.0.5.4.3.2.ip6.arpa.'); // NOSONAR
204204
expect(toARPA('fdf8:f53b:82e4::53')).to.eql('3.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.4.e.2.8.b.3.5.f.8.f.d.f.ip6.arpa.'); // NOSONAR
@@ -235,4 +235,12 @@ describe('ipv4/6 util', () => {
235235
expect(to6to4Prefix('2001:db8:0:85a3::ac1f:8001')).to.eql(''); // NOSONAR
236236
}); // NOSONAR
237237
}); // NOSONAR
238+
describe('fromARPA', () => {
239+
it('returns IP', () => {
240+
expect(fromARPA('42.2.0.192.in-addr.arpa.')).to.eql('192.0.2.42'); // NOSONAR
241+
expect(fromARPA('42.2.0.192.in-addr.arpa')).to.eql('192.0.2.42'); // NOSONAR
242+
expect(fromARPA('e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.')).to.eql('2001:0:ce49:7601:e866:efff:62c3:fffe'); // NOSONAR
243+
expect(fromARPA('e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa')).to.eql('2001:0:ce49:7601:e866:efff:62c3:fffe'); // NOSONAR
244+
}); // NOSONAR
245+
}); // NOSONAR
238246
}); // NOSONAR

src/utils/ip.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,26 @@ export function toARPA(address: string) {
122122
(bigInt >> BigInt(24) & BigInt(255)),
123123
].join('.')
124124
);
125-
return `${reverseIP}.in-addr.arpa`;
125+
return `${reverseIP}.in-addr.arpa.`;
126126
}
127127

128128
return (new Address6(address)).reverseForm();
129129
}
130130

131+
export function fromARPA(arpa: string) {
132+
if (!arpa) {
133+
return null;
134+
}
135+
if (!arpa.endsWith('.')) {
136+
arpa = `${arpa}.`;
137+
}
138+
if (arpa.includes('.in-addr.arpa')) {
139+
return Address4.fromArpa(arpa).correctForm();
140+
}
141+
142+
return Address6.fromArpa(arpa).correctForm();
143+
}
144+
131145
export function toIPv4MappedAddress(address: string) {
132146
if (!isIPv4(address)) {
133147
return '';

0 commit comments

Comments
 (0)