Comparing version
@@ -16,2 +16,12 @@ import { NetworkStack } from 'tcpip'; | ||
type NameServer = { | ||
/** | ||
* Name server IP address. | ||
*/ | ||
ip: string; | ||
/** | ||
* Name server port. | ||
*/ | ||
port: number; | ||
}; | ||
type DnsType = keyof typeof TypeCode; | ||
@@ -39,2 +49,22 @@ type DnsBaseResponse = { | ||
type DnsClientOptions = { | ||
/** | ||
* Name server address to query. | ||
* @default { ip: '127.0.0.1', port: 53 } | ||
*/ | ||
nameServer?: NameServer; | ||
}; | ||
declare class DnsClient { | ||
#private; | ||
constructor(stack: NetworkStack, options?: DnsClientOptions); | ||
/** | ||
* Performs an A record lookup to get the IP address for a hostname. | ||
*/ | ||
lookup(name: string): Promise<string>; | ||
/** | ||
* Performs a reverse DNS (PTR) lookup to get the hostname for an IP address. | ||
*/ | ||
reverse(ip: string): Promise<string>; | ||
} | ||
type RequestFn = (query: { | ||
@@ -46,2 +76,8 @@ name: string; | ||
/** | ||
* Host to listen on. | ||
* | ||
* @default '0.0.0.0' | ||
*/ | ||
host?: string; | ||
/** | ||
* Port to listen on. | ||
@@ -53,6 +89,2 @@ * | ||
/** | ||
* `tcpip` network stack to use. | ||
*/ | ||
stack: NetworkStack; | ||
/** | ||
* Callback function to handle DNS queries. | ||
@@ -62,6 +94,5 @@ */ | ||
}; | ||
declare function createDnsServer(options: DnsServerOptions): Promise<DnsServer>; | ||
declare class DnsServer { | ||
#private; | ||
constructor(options: DnsServerOptions); | ||
constructor(stack: NetworkStack, options: DnsServerOptions); | ||
listen(): Promise<void>; | ||
@@ -80,2 +111,16 @@ } | ||
/** | ||
* Converts an IPv4 or IPv6 address to a reversed PTR name. | ||
* | ||
* @example | ||
* // Convert IPv4 address to PTR name | ||
* ipToPtrName('10.0.0.1'); | ||
* // '1.0.0.10.in-addr.arpa' | ||
* | ||
* @example | ||
* // Convert IPv6 address to PTR name | ||
* ipToPtrName('2001:db8::567:89ab'); | ||
* // 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' | ||
*/ | ||
declare function ipToPtrName(ip: string): string; | ||
/** | ||
* Converts a reversed PTR name to an IPv4 or IPv6 address. | ||
@@ -97,2 +142,24 @@ * | ||
export { type DnsResponse, DnsServer, type DnsServerOptions, type DnsType, type RequestFn, createDnsServer, ptrNameToIP }; | ||
type CreateDnsOptions = { | ||
/** | ||
* DNS client options. | ||
*/ | ||
client?: DnsClientOptions; | ||
}; | ||
/** | ||
* Creates DNS server and client functions on top of a | ||
* `tcpip` network stack. | ||
* | ||
* @example | ||
* const stack = await createStack(); | ||
* const { serve, lookup } = await createDns(stack); | ||
* const server = await serve({ ... }); | ||
* const ip = await lookup('example.com'); | ||
*/ | ||
declare function createDns(stack: NetworkStack, options?: CreateDnsOptions): Promise<{ | ||
serve: (options: DnsServerOptions) => Promise<DnsServer>; | ||
lookup: (name: string) => Promise<string>; | ||
reverse: (ip: string) => Promise<string>; | ||
}>; | ||
export { type CreateDnsOptions, DnsClient, type DnsClientOptions, type DnsResponse, DnsServer, type DnsServerOptions, type DnsType, type NameServer, type RequestFn, createDns, ipToPtrName, ptrNameToIP }; |
@@ -1,2 +0,2 @@ | ||
var c={A:1,NS:2,CNAME:5,SOA:6,PTR:12,MX:15,TXT:16,AAAA:28,SRV:33,ANY:255},u={IN:1},l={QUERY:0},f={NOERROR:0,SERVFAIL:2,NXDOMAIN:3};function D(e,n){let t=[];for(let s=0;s<e.length;s+=n)t.push(e.slice(s,s+n));return t}function A(e){let[n,t,...s]=e.split(".").reverse().filter(r=>!!r);if(n!=="arpa")throw new Error(`invalid PTR name: ${e}`);switch(t){case"in-addr":return{type:"ipv4",ip:s.join(".")};case"ip6":return{type:"ipv6",ip:m(s.join("").replace(/(.{4})/g,"$1:").replace(/:$/,""))};default:throw new Error(`invalid PTR name: ${e}`)}}function m(e){let t=e.toLowerCase().split(":").map(a=>a.replace(/^0+(?=\w)/,"")),s=-1,r=0,o=-1,i=0;for(let a=0;a<t.length;a++)t[a]==="0"||t[a]===""?(o===-1&&(o=a),i++,i>r&&(s=o,r=i)):(o=-1,i=0);return r>=2&&(t.splice(s,r),s===0?t.unshift("",""):s===t.length?t.push("",""):t.splice(s,0,"")),t.join(":")}function v(e,n){let t=[],s=n;for(;;){let r=e[s];if(r===void 0||r===0)break;s++;let o=new TextDecoder().decode(e.slice(s,s+r));t.push(o),s+=r}return[t.join("."),s+1]}function d(e){let n=e.split("."),t=new Uint8Array(e.length+2),s=0;for(let r of n){t[s]=r.length,s++;for(let o=0;o<r.length;o++)t[s+o]=r.charCodeAt(o);s+=r.length}return t[s]=0,t.slice(0,s+1)}function U(e){let[n]=Object.entries(c).find(([,t])=>t===e)??[];return n}function w(e){return c[e]}function E(e){let[n]=Object.entries(u).find(([,t])=>t===e)??[];return n}function g(e){return u[e]}function C(e){let[n]=Object.entries(l).find(([,t])=>t===e)??[];return n}function T(e){return l[e]}function b(e){let[n]=Object.entries(f).find(([,t])=>t===e)??[];return n}function I(e){return f[e]}function N(e){let n=d(e.name),t=new Uint8Array(n.length+4),s=new DataView(t.buffer),r=0;return t.set(n,r),r+=n.length,s.setUint16(r,w(e.type)),s.setUint16(r+2,g(e.class)),t}function P(e){let n=new TextEncoder,t=D(e,255),s=new Uint8Array(t.length*256),r=0;for(let o of t)s[r]=o.length,s.set(n.encode(o),r+1),r+=256;return s}function O(e){switch(e.type){case"A":return S(e.ip);case"AAAA":return M(e.ip);case"TXT":return P(e.value);case"PTR":return d(e.ptr);default:throw new Error("unsupported record type")}}function q(e){let t=d(e.name),s=O(e),r=new Uint8Array(t.length+10+s.length),o=new DataView(r.buffer),i=0;return r.set(t,i),i+=t.length,o.setUint16(i,w(e.type)),o.setUint16(i+2,g(e.class)),o.setUint32(i+4,e.ttl),o.setUint16(i+8,s.length),i+=10,r.set(s,i),r}function y(e){if(e.length<12)throw new Error("DNS message is too short");let n=0,t=new DataView(e.buffer),s={id:t.getUint16(0),isResponse:!!(e[2]&128),opcode:C(e[2]>>3&15),isAuthoritativeAnswer:!!(e[2]&4),isTruncated:!!(e[2]&2),isRecursionDesired:!!(e[2]&1),isRecursionAvailable:!!(e[3]&128),rcode:b(e[3]&15),questionCount:t.getUint16(4),answerCount:t.getUint16(6),authorityCount:t.getUint16(8),additionalCount:t.getUint16(10)};n=12;let r=[];for(let o=0;o<s.questionCount;o++){let[i,a]=v(e,n);n=a;let p=U(t.getUint16(n)),R=E(t.getUint16(n+2));n+=4,r.push({name:i,type:p,class:R})}return{header:s,questions:r,answers:[],authorities:[],additionals:[]}}function x(e){let t=e.questions.map(N),s=e.answers.map(q),r=12;for(let p of t)r+=p.length;for(let p of s)r+=p.length;let o=new Uint8Array(r),i=new DataView(o.buffer),a=0;i.setUint16(0,e.header.id),o[2]=(e.header.isResponse?128:0)|(T(e.header.opcode)&15)<<3|(e.header.isAuthoritativeAnswer?4:0)|(e.header.isTruncated?2:0)|(e.header.isRecursionDesired?1:0),o[3]=(e.header.isRecursionAvailable?128:0)|I(e.header.rcode)&15,i.setUint16(4,e.questions.length),i.setUint16(6,e.answers.length),i.setUint16(8,e.authorities.length),i.setUint16(10,e.additionals.length),a=12;for(let p of t)o.set(p,a),a+=p.length;for(let p of s)o.set(p,a),a+=p.length;return o}function S(e){return new Uint8Array(e.split(".").map(n=>parseInt(n,10)))}function M(e){return new Uint8Array(e.split(":").flatMap(n=>{let t=parseInt(n,16);return[t>>8,t&255]}))}async function $(e){let n=new h(e);return await n.listen(),n}var h=class{#e;constructor(n){this.#e=n}async listen(){let n=await this.#e.stack.openUdp({port:this.#e.port??53});this.#t(n)}async#t(n){let t=n.writable.getWriter();for await(let s of n)this.#n(s,t)}async#n(n,t){try{let{host:s,port:r}=n,o=y(n.data),i=await j(o,this.#e.request),a=x(i);await t.write({host:s,port:r,data:a})}catch(s){console.error("error handling dns query:",s)}}};async function j(e,n){if(e.questions.length>1)throw new Error("only one dns question is supported");let[t]=e.questions;if(!t)throw new Error("no question found in dns message");if(t.class!=="IN")throw new Error("only IN class is supported");let s=await n({name:t.name,type:t.type});return k(e,s)}function k(e,n){if(!n)return{header:{...e.header,isResponse:!0,isRecursionAvailable:!1,rcode:"NXDOMAIN"},questions:e.questions,answers:[],authorities:[],additionals:[]};let t=e.questions[0];if(!t)throw new Error("no question found in dns message");if(t.class!=="IN")throw new Error("only IN class is supported");let r=(Array.isArray(n)?n:[n]).map(o=>({name:t.name,class:"IN",...o}));return{header:{...e.header,isResponse:!0,isRecursionAvailable:!1,rcode:"NOERROR"},questions:e.questions,answers:r,authorities:[],additionals:[]}}export{h as DnsServer,$ as createDnsServer,A as ptrNameToIP}; | ||
import{compressIPv6 as T,expandIPv6 as b,serializeIPv6Address as P}from"@tcpip/wire";function m(e){let t=e.split(".");if(t.length===4)return`${t.reverse().join(".")}.in-addr.arpa`;let n=b(e),s=P(n);return`${Array.from(s).flatMap(o=>[o>>4,o&15].map(i=>i.toString(16))).reverse().join(".")}.ip6.arpa`}function k(e){let[t,n,...s]=e.split(".").reverse().filter(r=>!!r);if(t!=="arpa")throw new Error(`invalid PTR name: ${e}`);switch(n){case"in-addr":return{type:"ipv4",ip:s.join(".")};case"ip6":return{type:"ipv6",ip:T(s.join("").replace(/(.{4})/g,"$1:").replace(/:$/,""))};default:throw new Error(`invalid PTR name: ${e}`)}}import{compressIPv6 as S,expandIPv6 as I,parseIPv4Address as q,parseIPv6Address as M,serializeIPv4Address as z,serializeIPv6Address as $}from"@tcpip/wire";var f={A:1,NS:2,CNAME:5,SOA:6,PTR:12,MX:15,TXT:16,AAAA:28,SRV:33,ANY:255},l={IN:1},d={QUERY:0,IQUERY:1,STATUS:2,NOTIFY:4,UPDATE:5},w={NOERROR:0,FORMERR:1,SERVFAIL:2,NXDOMAIN:3,NOTIMP:4,REFUSED:5};function A(e,t){let n=[],s=t;for(;;){let r=e[s];if(r===void 0||r===0)break;s++;let o=new TextDecoder().decode(e.slice(s,s+r));n.push(o),s+=r}return[n.join("."),s+1]}function v(e){let t=e.split("."),n=new Uint8Array(e.length+2),s=0;if(e!=="")for(let r of t){n[s]=r.length,s++;for(let o=0;o<r.length;o++)n[s+o]=r.charCodeAt(o);s+=r.length}return n[s]=0,n.slice(0,s+1)}function C(e){let[t]=Object.entries(f).find(([,n])=>n===e)??[];if(!t)throw new Error(`unknown dns type: ${e}`);return t}function U(e){if(!(e in f))throw new Error(`unknown dns type: ${e}`);return f[e]}function O(e){let[t]=Object.entries(l).find(([,n])=>n===e)??[];if(!t)throw new Error(`unknown dns class: ${e}`);return t}function E(e){if(!(e in l))throw new Error(`unknown dns class: ${e}`);return l[e]}function j(e){let[t]=Object.entries(d).find(([,n])=>n===e)??[];if(!t)throw new Error(`unknown dns opcode: ${e}`);return t}function H(e){if(!(e in d))throw new Error(`unknown dns opcode: ${e}`);return d[e]}function V(e){let[t]=Object.entries(w).find(([,n])=>n===e)??[];if(!t)throw new Error(`unknown dns rcode: ${e}`);return t}function Q(e){if(!(e in w))throw new Error(`unknown dns rcode: ${e}`);return w[e]}function B(e,t){let[n,s]=A(e,t),r=new DataView(e.buffer),o=C(r.getUint16(s)),i=O(r.getUint16(s+2));return[{name:n,type:o,class:i},s+4]}function F(e){let t=v(e.name),n=new Uint8Array(t.length+4),s=new DataView(n.buffer),r=0;return n.set(t,r),r+=t.length,s.setUint16(r,U(e.type)),s.setUint16(r+2,E(e.class)),n}function L(e,t,n){let s=[],r=t,o=t+n;for(;r<o;){let i=e[r];if(i===void 0)break;r++;let a=new TextDecoder().decode(e.slice(r,r+i));s.push(a),r+=i}return[s.join(""),r]}function X(e){if(e.length===0)return new Uint8Array([0]);let n=new TextEncoder().encode(e),s=[];for(let a=0;a<n.length;a+=255)s.push(n.slice(a,Math.min(a+255,n.length)));let r=s.reduce((a,p)=>a+1+p.length,0),o=new Uint8Array(r),i=0;for(let a of s)o[i]=a.length,o.set(a,i+1),i+=1+a.length;return o}function R(e,t){let[n,s]=A(e,t),r=new DataView(e.buffer),o=C(r.getUint16(s)),i=O(r.getUint16(s+2)),a=r.getUint32(s+4),p=r.getUint16(s+8);switch(t=s+10,o){case"A":{let c=q(e.slice(t,t+p)),u=t+p;return[{name:n,class:i,ttl:a,type:o,ip:c},u]}case"AAAA":{let c=M(e.slice(t,t+p)),u=S(c),N=t+p;return[{name:n,class:i,ttl:a,type:o,ip:u},N]}case"TXT":{let[c,u]=L(e,t,p);return[{name:n,class:i,ttl:a,type:o,value:c},u]}case"PTR":{let[c,u]=A(e,t);return[{name:n,class:i,ttl:a,type:o,ptr:c},u]}default:throw new Error(`unsupported record type: ${o}`)}}function Y(e){switch(e.type){case"A":return z(e.ip);case"AAAA":return $(I(e.ip));case"TXT":return X(e.value);case"PTR":return v(e.ptr);default:throw new Error("unsupported record type")}}function x(e){let n=v(e.name),s=Y(e),r=new Uint8Array(n.length+10+s.length),o=new DataView(r.buffer),i=0;return r.set(n,i),i+=n.length,o.setUint16(i,U(e.type)),o.setUint16(i+2,E(e.class)),o.setUint32(i+4,e.ttl),o.setUint16(i+8,s.length),i+=10,r.set(s,i),r}function W(e){if(e.length<12)throw new Error("DNS header is too short");let t=new DataView(e.buffer);return[{id:t.getUint16(0),isResponse:!!(e[2]&128),opcode:j(e[2]>>3&15),isAuthoritativeAnswer:!!(e[2]&4),isTruncated:!!(e[2]&2),isRecursionDesired:!!(e[2]&1),isRecursionAvailable:!!(e[3]&128),rcode:V(e[3]&15),questionCount:t.getUint16(4),answerCount:t.getUint16(6),authorityCount:t.getUint16(8),additionalCount:t.getUint16(10)},12]}function G(e){let t=new Uint8Array(12),n=new DataView(t.buffer);return n.setUint16(0,e.id),t[2]=(e.isResponse?128:0)|(H(e.opcode)&15)<<3|(e.isAuthoritativeAnswer?4:0)|(e.isTruncated?2:0)|(e.isRecursionDesired?1:0),t[3]=(e.isRecursionAvailable?128:0)|Q(e.rcode)&15,n.setUint16(4,e.questionCount),n.setUint16(6,e.answerCount),n.setUint16(8,e.authorityCount),n.setUint16(10,e.additionalCount),t}function h(e){if(e.length<12)throw new Error("DNS message is too short");let t=0,[n,s]=W(e);t=s;let r=[];for(let p=0;p<n.questionCount;p++){let[c,u]=B(e,t);r.push(c),t=u}let o=[];for(let p=0;p<n.answerCount;p++){let[c,u]=R(e,t);o.push(c),t=u}let i=[];for(let p=0;p<n.authorityCount;p++){let[c,u]=R(e,t);i.push(c),t=u}let a=[];for(let p=0;p<n.additionalCount;p++){let[c,u]=R(e,t);a.push(c),t=u}return{header:n,questions:r,answers:o,authorities:i,additionals:a}}function D(e){e.header.questionCount=e.questions.length,e.header.answerCount=e.answers?.length??0,e.header.authorityCount=e.authorities?.length??0,e.header.additionalCount=e.additionals?.length??0;let t=G(e.header),n=e.questions.map(F),s=e.answers?.map(x)??[],r=e.authorities?.map(x)??[],o=e.additionals?.map(x)??[],i=t.length;for(let c of n)i+=c.length;for(let c of s)i+=c.length;for(let c of r)i+=c.length;for(let c of o)i+=c.length;let a=new Uint8Array(i),p=0;a.set(t,p),p+=t.length;for(let c of n)a.set(c,p),p+=c.length;for(let c of s)a.set(c,p),p+=c.length;for(let c of r)a.set(c,p),p+=c.length;for(let c of o)a.set(c,p),p+=c.length;return a}var y=class{#t;#e;#n=0;constructor(t,n={}){this.#t=t,this.#e=n.nameServer??{ip:"127.0.0.1",port:53}}async#s(t){let n={header:{id:this.#r(),isResponse:!1,opcode:"QUERY",isAuthoritativeAnswer:!1,isTruncated:!1,isRecursionDesired:!0,isRecursionAvailable:!1,rcode:"NOERROR",questionCount:0,answerCount:0,authorityCount:0,additionalCount:0},questions:[{name:t.name,type:t.type,class:"IN"}]},s=await this.#t.openUdp(),r=D(n);await s.writable.getWriter().write({host:this.#e.ip,port:this.#e.port,data:r});for await(let i of s){let a=h(i.data);if(a.header.id!==n.header.id)continue;if(a.header.rcode!=="NOERROR")throw new Error(`dns query failed with rcode: ${a.header.rcode}`);if(a.header.answerCount>1)throw new Error("expected exactly one dns answer");let[p]=a.answers??[];if(!p)throw new Error("no dns answer found");return p}throw new Error("udp socket closed before receiving response")}async lookup(t){let n=await this.#s({name:t,type:"A"});if(!n||n.type!=="A")throw new Error(`no A record found for ${t}`);return n.ip}async reverse(t){let n=m(t),s=await this.#s({name:n,type:"PTR"});if(!s||s.type!=="PTR")throw new Error(`No PTR record found for ${t}`);return s.ptr}#r(){return this.#n=(this.#n+1)%65536,this.#n}};var g=class{#t;#e;constructor(t,n){this.#t=t,this.#e=n}async listen(){let t=await this.#t.openUdp({host:this.#e.host,port:this.#e.port??53});this.#n(t)}async#n(t){let n=t.writable.getWriter();for await(let s of t)this.#s(s,n)}async#s(t,n){try{let{host:s,port:r}=t,o=h(t.data),i=await _(o,this.#e.request),a=D(i);await n.write({host:s,port:r,data:a})}catch(s){console.error("error handling dns query:",s)}}};async function _(e,t){if(e.questions.length>1)throw new Error("only one dns question is supported");let[n]=e.questions;if(!n)throw new Error("no question found in dns message");if(n.class!=="IN")throw new Error("only IN class is supported");let s=await t({name:n.name,type:n.type});return J(e,s)}function J(e,t){if(!t)return{header:{...e.header,isResponse:!0,isRecursionAvailable:!1,rcode:"NXDOMAIN"},questions:e.questions,answers:[],authorities:[],additionals:[]};let n=e.questions[0];if(!n)throw new Error("no question found in dns message");if(n.class!=="IN")throw new Error("only IN class is supported");let r=(Array.isArray(t)?t:[t]).map(o=>({name:n.name,class:"IN",...o}));return{header:{...e.header,isResponse:!0,isRecursionAvailable:!1,rcode:"NOERROR"},questions:e.questions,answers:r,authorities:[],additionals:[]}}async function fe(e,t={}){let n=new y(e,t.client);return{serve:async s=>{let r=new g(e,s);return await r.listen(),r},lookup:async s=>n.lookup(s),reverse:async s=>n.reverse(s)}}export{y as DnsClient,g as DnsServer,fe as createDns,m as ipToPtrName,k as ptrNameToIP}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@tcpip/dns", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "DNS server built on tcpip.js", | ||
@@ -23,6 +23,8 @@ "main": "dist/index.cjs", | ||
}, | ||
"dependencies": {}, | ||
"dependencies": { | ||
"@tcpip/wire": "0.1" | ||
}, | ||
"devDependencies": { | ||
"@total-typescript/tsconfig": "^1.0.4", | ||
"tcpip": "0.2", | ||
"tcpip": "0.3", | ||
"typescript": "^5.0.4", | ||
@@ -29,0 +31,0 @@ "vitest": "^3.0.1" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
69806
45.62%221
74.02%1
Infinity%3
50%2
100%+ Added
+ Added