@celilo/e2e 0.7.14 → 0.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/vantage.ts ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Vantage-point network assertions (ISS-0117, §D3/§D4).
3
+ *
4
+ * Every network check runs FROM a named vantage — the box a real consumer occupies —
5
+ * and classifies the result against the zone topology (see ./zone-classifier). A green
6
+ * from the wrong vantage is worse than no test: ISS-0101 (www -> DMZ container IP),
7
+ * ISS-0111 (git-ssh -> container IP) and the ISS-0095 risk all passed e2e because the
8
+ * assertion ran from the management box, which has routes into every zone.
9
+ *
10
+ * This module is the typed VERB layer. It performs no I/O itself: it orchestrates a
11
+ * `ProbeTransport` (the injected boundary, Rule 2.3 / Rule 10.4) and applies zone
12
+ * classification + assertion policy on the typed results. The real transports — a
13
+ * dockerExec escape hatch now, a probe-agent later (§D2) — implement `ProbeTransport`
14
+ * elsewhere, so the verbs stay pure and unit-testable without Docker.
15
+ */
16
+
17
+ import { type Vantage, type Zone } from './types';
18
+ import { classifyIp } from './zone-classifier';
19
+
20
+ // `Vantage` is topology vocabulary — defined in ./types alongside Zone — and re-exported
21
+ // here so verb-layer consumers can import it from the same module as the verbs.
22
+ export type { Vantage };
23
+
24
+ /** How a name is resolved (§D3) — lets us test the resolution layers independently. */
25
+ export type ResolveMethod =
26
+ | 'system' // the vantage's real stack: resolv.conf AND /etc/hosts
27
+ | 'dns' // query a resolver directly (optionally a specific `server`)
28
+ | 'hostfiles'; // the /etc/hosts layer only (the host-pin path — ISS-0095)
29
+
30
+ /** A zone the answer may legitimately land in, plus `public` for a routable internet address. */
31
+ export type ExpectedZone = Zone | 'public';
32
+
33
+ export interface ResolveResult {
34
+ name: string;
35
+ from: Vantage;
36
+ method: ResolveMethod;
37
+ ips: string[]; // resolved A records; empty == NXDOMAIN / no answer
38
+ }
39
+
40
+ export interface CertInfo {
41
+ cn: string;
42
+ issuer: string;
43
+ subjectAltNames: string[];
44
+ selfSigned: boolean;
45
+ }
46
+
47
+ export interface HttpsResult {
48
+ from: Vantage;
49
+ url: string;
50
+ status: number;
51
+ body: string;
52
+ cert: CertInfo;
53
+ }
54
+
55
+ export interface ResolutionInspection {
56
+ from: Vantage;
57
+ name: string;
58
+ resolvConf: string;
59
+ hostsLines: string[];
60
+ }
61
+
62
+ /**
63
+ * The injected I/O boundary. Implementations (dockerExec-based now, probe-agent later)
64
+ * are the ONLY place raw shell / network calls live; the verbs below never touch I/O.
65
+ */
66
+ export interface ProbeTransport {
67
+ resolve(opts: { from: Vantage; name: string; method: ResolveMethod; server?: string }): Promise<{ ips: string[] }>;
68
+ tcpConnect(opts: { from: Vantage; host: string; port: number }): Promise<{ connected: boolean; detail?: string }>;
69
+ ping(opts: { from: Vantage; target: string }): Promise<{ alive: boolean }>;
70
+ httpsRequest(opts: { from: Vantage; url: string }): Promise<{ status: number; body: string; cert: CertInfo }>;
71
+ inspectResolution(opts: { from: Vantage; name: string }): Promise<{ resolvConf: string; hostsLines: string[] }>;
72
+ }
73
+
74
+ /** Thrown when a vantage assertion fails. Messages name the bug class they guard. */
75
+ export class VantageAssertionError extends Error {
76
+ constructor(message: string) {
77
+ super(message);
78
+ this.name = 'VantageAssertionError';
79
+ }
80
+ }
81
+
82
+ interface ResolveOpts {
83
+ from: Vantage;
84
+ name: string;
85
+ method?: ResolveMethod;
86
+ server?: string;
87
+ }
88
+
89
+ interface ReachOpts {
90
+ from: Vantage;
91
+ host: string;
92
+ port: number;
93
+ }
94
+
95
+ interface HttpsAssertOpts {
96
+ from: Vantage;
97
+ url: string;
98
+ expectStatus?: number;
99
+ expectCN?: string;
100
+ expectCA?: string; // expected issuer substring (Let's Encrypt / Pebble in e2e); never self-signed
101
+ }
102
+
103
+ function describeZone(ip: string): string {
104
+ const c = classifyIp(ip);
105
+ switch (c.kind) {
106
+ case 'zone':
107
+ return `zone ${c.zone}`;
108
+ case 'reserved':
109
+ return `reserved (${c.label})`;
110
+ case 'public':
111
+ return 'public';
112
+ case 'invalid':
113
+ return 'invalid IP';
114
+ }
115
+ }
116
+
117
+ /**
118
+ * The typed probe surface. Construct with a transport; every verb takes a single
119
+ * options object with a required `from` (§D1 — no default vantage).
120
+ */
121
+ export class VantageProbe {
122
+ constructor(private readonly transport: ProbeTransport) {}
123
+
124
+ async resolveFrom(opts: ResolveOpts): Promise<ResolveResult> {
125
+ const method = opts.method ?? 'system';
126
+ const { ips } = await this.transport.resolve({
127
+ from: opts.from,
128
+ name: opts.name,
129
+ method,
130
+ server: opts.server,
131
+ });
132
+ return { name: opts.name, from: opts.from, method, ips };
133
+ }
134
+
135
+ async assertResolves(opts: ResolveOpts): Promise<ResolveResult> {
136
+ const result = await this.resolveFrom(opts);
137
+ if (result.ips.length === 0) {
138
+ throw new VantageAssertionError(
139
+ `${opts.name} did not resolve from ${opts.from} (NXDOMAIN / no A record), expected an answer.`,
140
+ );
141
+ }
142
+ return result;
143
+ }
144
+
145
+ async assertNxdomain(opts: ResolveOpts): Promise<void> {
146
+ const result = await this.resolveFrom(opts);
147
+ if (result.ips.length > 0) {
148
+ throw new VantageAssertionError(
149
+ `${opts.name} resolved from ${opts.from} to ${result.ips.join(', ')}, expected NXDOMAIN ` +
150
+ `(this name must not be visible from ${opts.from}).`,
151
+ );
152
+ }
153
+ }
154
+
155
+ /**
156
+ * The killer verb: resolve, classify every answer, assert the expected zone.
157
+ * `www.<domain>` from `internalDevice` MUST be `internal`/`public` (natIp/public),
158
+ * never a managed `dmz`/`app`/`secure` container IP — the ISS-0101 / ISS-0111 class.
159
+ */
160
+ async assertResolvesInZone(opts: ResolveOpts & { zone: ExpectedZone }): Promise<ResolveResult> {
161
+ const result = await this.assertResolves(opts);
162
+ const offenders = result.ips.filter((ip) => !ipMatchesExpectedZone(ip, opts.zone));
163
+ if (offenders.length > 0) {
164
+ const detail = offenders.map((ip) => `${ip} (${describeZone(ip)})`).join(', ');
165
+ throw new VantageAssertionError(
166
+ `${opts.name} from ${opts.from} resolved to ${detail}, expected zone "${opts.zone}". ` +
167
+ 'A managed-zone container IP is unroutable from outside that zone — this is the ' +
168
+ 'e2e-green/prod-broke class (ISS-0101, ISS-0111). The right answer from a consumer ' +
169
+ 'vantage is the firewall natIp (internal) or a public address.',
170
+ );
171
+ }
172
+ return result;
173
+ }
174
+
175
+ /**
176
+ * Resolve and assert the answer includes the EXACT expected IP(s) — the DNS-correctness
177
+ * verb. Pair with `method: 'dns', server` to assert what a SPECIFIC resolver returns (e.g.
178
+ * the internal split-horizon resolver vs the public resolver). Used by the DNS suites to
179
+ * check a DDNS-registered IP, an nsupdate'd A record, or a split-horizon answer.
180
+ */
181
+ async assertResolvesTo(opts: ResolveOpts & { expected: string | string[] }): Promise<ResolveResult> {
182
+ const result = await this.assertResolves(opts);
183
+ const expected = Array.isArray(opts.expected) ? opts.expected : [opts.expected];
184
+ const missing = expected.filter((ip) => !result.ips.includes(ip));
185
+ if (missing.length > 0) {
186
+ const via = opts.server ? ` via ${opts.server}` : '';
187
+ throw new VantageAssertionError(
188
+ `${opts.name} from ${opts.from}${via} resolved to [${result.ips.join(', ')}], ` +
189
+ `expected to include [${missing.join(', ')}].`,
190
+ );
191
+ }
192
+ return result;
193
+ }
194
+
195
+ async ping(opts: { from: Vantage; target: string }): Promise<boolean> {
196
+ const { alive } = await this.transport.ping(opts);
197
+ return alive;
198
+ }
199
+
200
+ async assertReachable(opts: ReachOpts): Promise<void> {
201
+ const { connected, detail } = await this.transport.tcpConnect(opts);
202
+ if (!connected) {
203
+ throw new VantageAssertionError(
204
+ `${opts.host}:${opts.port} was NOT reachable from ${opts.from}` +
205
+ (detail ? ` (${detail})` : '') +
206
+ ', expected a successful TCP connection.',
207
+ );
208
+ }
209
+ }
210
+
211
+ /** Isolation assertion (§D6): prove what shouldn't work, e.g. appSystem cannot reach a secure host. */
212
+ async assertUnreachable(opts: ReachOpts): Promise<void> {
213
+ const { connected } = await this.transport.tcpConnect(opts);
214
+ if (connected) {
215
+ throw new VantageAssertionError(
216
+ `${opts.host}:${opts.port} WAS reachable from ${opts.from}, expected it to be blocked ` +
217
+ '(zone isolation / firewall boundary violated).',
218
+ );
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Connect, validate the REAL cert chain (right CN, expected issuer, never self-signed),
224
+ * then status. Cert provenance catches the skip-cert / 0.0.0.0-sentinel cheats.
225
+ */
226
+ async assertHttps(opts: HttpsAssertOpts): Promise<HttpsResult> {
227
+ const res = await this.transport.httpsRequest({ from: opts.from, url: opts.url });
228
+ const result: HttpsResult = { from: opts.from, url: opts.url, ...res };
229
+
230
+ if (res.cert.selfSigned) {
231
+ throw new VantageAssertionError(
232
+ `${opts.url} from ${opts.from} presented a self-signed cert (issuer "${res.cert.issuer}"). ` +
233
+ 'A real cert from the e2e CA (Pebble / Let’s Encrypt) is required — no --insecure.',
234
+ );
235
+ }
236
+ if (opts.expectCN && res.cert.cn !== opts.expectCN) {
237
+ throw new VantageAssertionError(
238
+ `${opts.url} from ${opts.from} cert CN "${res.cert.cn}", expected "${opts.expectCN}".`,
239
+ );
240
+ }
241
+ if (opts.expectCA && !res.cert.issuer.includes(opts.expectCA)) {
242
+ throw new VantageAssertionError(
243
+ `${opts.url} from ${opts.from} cert issuer "${res.cert.issuer}", expected to contain "${opts.expectCA}".`,
244
+ );
245
+ }
246
+ if (opts.expectStatus !== undefined && res.status !== opts.expectStatus) {
247
+ throw new VantageAssertionError(
248
+ `${opts.url} from ${opts.from} returned status ${res.status}, expected ${opts.expectStatus}.`,
249
+ );
250
+ }
251
+ return result;
252
+ }
253
+
254
+ /** Diagnostic (§D3): the vantage's resolv.conf + matching /etc/hosts lines. Also asserts a host-pin (ISS-0095). */
255
+ async inspectResolution(opts: { from: Vantage; name: string }): Promise<ResolutionInspection> {
256
+ const res = await this.transport.inspectResolution(opts);
257
+ return { from: opts.from, name: opts.name, ...res };
258
+ }
259
+ }
260
+
261
+ /** True if `ip` satisfies the expected zone (managed zone exact, or routable `public`). */
262
+ function ipMatchesExpectedZone(ip: string, expected: ExpectedZone): boolean {
263
+ const c = classifyIp(ip);
264
+ if (expected === 'public') return c.kind === 'public';
265
+ return c.kind === 'zone' && c.zone === expected;
266
+ }
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { classifyIp, inCidr, isPublicLeakSafe, isSegmentedZoneIp, parseIpv4 } from './zone-classifier';
3
+
4
+ describe('parseIpv4', () => {
5
+ test('parses dotted-quad to unsigned 32-bit', () => {
6
+ expect(parseIpv4('0.0.0.0')).toBe(0);
7
+ expect(parseIpv4('255.255.255.255')).toBe(0xffffffff);
8
+ // High first octet must not go negative (sign-bit overflow guard).
9
+ expect(parseIpv4('192.168.0.253')).toBe(((192 << 24) | (168 << 16) | (0 << 8) | 253) >>> 0);
10
+ expect(parseIpv4('192.168.0.253')).toBeGreaterThan(0);
11
+ });
12
+ test('rejects malformed / out-of-range', () => {
13
+ expect(parseIpv4('not.an.ip')).toBeNull();
14
+ expect(parseIpv4('999.1.1.1')).toBeNull();
15
+ expect(parseIpv4('10.0.0')).toBeNull();
16
+ });
17
+ });
18
+
19
+ describe('inCidr', () => {
20
+ test('membership within /24', () => {
21
+ expect(inCidr('10.0.10.5', '10.0.10.0/24')).toBe(true);
22
+ expect(inCidr('10.0.10.255', '10.0.10.0/24')).toBe(true);
23
+ expect(inCidr('10.0.11.1', '10.0.10.0/24')).toBe(false);
24
+ });
25
+ test('high-octet subnet (192.168) — no sign-bit bug', () => {
26
+ expect(inCidr('192.168.0.253', '192.168.0.0/24')).toBe(true);
27
+ expect(inCidr('192.168.1.1', '192.168.0.0/24')).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe('classifyIp', () => {
32
+ test('managed zones (the segmented ones)', () => {
33
+ expect(classifyIp('10.0.10.10')).toEqual({ kind: 'zone', zone: 'dmz' }); // caddy
34
+ expect(classifyIp('10.0.20.30')).toEqual({ kind: 'zone', zone: 'app' }); // authentik
35
+ expect(classifyIp('10.0.30.5')).toEqual({ kind: 'zone', zone: 'secure' });
36
+ });
37
+ test('internal zone (the LAN — natIp lives here)', () => {
38
+ expect(classifyIp('192.168.0.253')).toEqual({ kind: 'zone', zone: 'internal' });
39
+ });
40
+ test('public: the CGNAT stand-in and real public space', () => {
41
+ expect(classifyIp('100.100.0.100')).toEqual({ kind: 'public' }); // sim firewall external
42
+ expect(classifyIp('8.8.8.8')).toEqual({ kind: 'public' });
43
+ expect(classifyIp('71.36.115.155')).toEqual({ kind: 'public' }); // the prod DDNS target
44
+ });
45
+ test('reserved ranges (incl. the 0.0.0.0 sentinel cheat)', () => {
46
+ expect(classifyIp('0.0.0.0').kind).toBe('reserved');
47
+ expect(classifyIp('127.0.0.1').kind).toBe('reserved');
48
+ expect(classifyIp('169.254.1.1').kind).toBe('reserved');
49
+ expect(classifyIp('224.0.0.1').kind).toBe('reserved');
50
+ });
51
+ test('invalid input', () => {
52
+ expect(classifyIp('nope').kind).toBe('invalid');
53
+ expect(classifyIp('256.0.0.1').kind).toBe('invalid');
54
+ });
55
+ });
56
+
57
+ describe('isSegmentedZoneIp', () => {
58
+ test('dmz/app/secure are segmented; internal and public are not', () => {
59
+ expect(isSegmentedZoneIp('10.0.10.10')).toBe(true); // caddy DMZ — the ISS-0101/0111 bug value
60
+ expect(isSegmentedZoneIp('10.0.20.30')).toBe(true);
61
+ expect(isSegmentedZoneIp('192.168.0.253')).toBe(false); // internal IS the LAN
62
+ expect(isSegmentedZoneIp('100.100.0.100')).toBe(false); // public
63
+ });
64
+ });
65
+
66
+ describe('isPublicLeakSafe (CLAUDE.md inviolable rule #1)', () => {
67
+ test('only public addresses are leak-safe', () => {
68
+ expect(isPublicLeakSafe('100.100.0.100')).toBe(true);
69
+ expect(isPublicLeakSafe('8.8.8.8')).toBe(true);
70
+ expect(isPublicLeakSafe('10.0.10.10')).toBe(false); // managed segmented zone
71
+ expect(isPublicLeakSafe('192.168.0.253')).toBe(false); // internal/RFC-1918 must not leak
72
+ expect(isPublicLeakSafe('0.0.0.0')).toBe(false); // reserved sentinel
73
+ });
74
+
75
+ test('the ISS-0101 scenario: a fronted name resolving to caddy DMZ IP is NOT leak-safe', () => {
76
+ // www.<domain> from a LAN device must be the natIp (internal) or public, never
77
+ // caddy's DMZ container IP. The doctor + e2e share this judgment.
78
+ const wwwResolvedWrong = '10.0.10.10';
79
+ expect(isSegmentedZoneIp(wwwResolvedWrong)).toBe(true);
80
+ expect(classifyIp('192.168.0.253').kind).toBe('zone'); // the RIGHT answer (natIp, internal)
81
+ });
82
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Zone classification for e2e network assertions (ISS-0117, §D5).
3
+ *
4
+ * Pure functions: given a resolved IPv4, say which celilo zone it belongs to (by
5
+ * matching the topology's `ZONE_SUBNETS`), or whether it is a reserved/special-use
6
+ * range, or a globally-routable "public" address. The vantage-assertion verbs
7
+ * (`resolveFrom` / `assertResolvesInZone`, ISS-0117 §D3) build on this to enforce
8
+ * e.g. "www.<domain> resolved from internalDevice must NOT be a segmented-zone
9
+ * container IP" — the ISS-0101 / ISS-0111 bug class.
10
+ *
11
+ * Classification uses `ZONE_SUBNETS` (exact) + a reserved-range check — deliberately
12
+ * NOT a blanket RFC-1918/CGNAT ban: the sim's "public" IP is a CGNAT stand-in
13
+ * (100.100.0.100), so in this topology 10.x / 192.168.x are *zones* and 100.x is
14
+ * *public*. (A real deployment would additionally treat RFC-1918 + RFC-6598 as
15
+ * non-public; that's the caller's policy, layered on top of this primitive.)
16
+ */
17
+
18
+ import { type Zone, ZONE_SUBNETS } from './types';
19
+
20
+ export type ZoneClass =
21
+ | { kind: 'zone'; zone: Zone }
22
+ | { kind: 'reserved'; label: string }
23
+ | { kind: 'public' }
24
+ | { kind: 'invalid' };
25
+
26
+ /**
27
+ * Segmented zones a device on the operator's LAN (`internal`) cannot route into —
28
+ * traffic to them must go via the firewall natIp. `internal` IS the LAN, so it is
29
+ * NOT segmented (a record at an internal-zone IP is reachable).
30
+ */
31
+ export const SEGMENTED_ZONES: readonly Zone[] = ['dmz', 'app', 'secure'];
32
+
33
+ /**
34
+ * Special-use IPv4 ranges that must never be a real service address (nor appear in
35
+ * any DNS answer). RFC-1918 and RFC-6598 (CGNAT) are intentionally absent — in this
36
+ * topology the former are the managed zones and the latter is the public stand-in.
37
+ */
38
+ const RESERVED: ReadonlyArray<{ cidr: string; label: string }> = [
39
+ { cidr: '0.0.0.0/8', label: 'unspecified/this-host (incl. the 0.0.0.0 sentinel)' },
40
+ { cidr: '127.0.0.0/8', label: 'loopback' },
41
+ { cidr: '169.254.0.0/16', label: 'link-local (RFC 3927)' },
42
+ { cidr: '224.0.0.0/4', label: 'multicast' },
43
+ { cidr: '240.0.0.0/4', label: 'reserved (RFC 1112)' },
44
+ ];
45
+
46
+ /** Parse a dotted-quad IPv4 to an unsigned 32-bit int, or null if malformed. */
47
+ export function parseIpv4(ip: string): number | null {
48
+ const m = ip.trim().match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
49
+ if (!m) return null;
50
+ const octets = m.slice(1, 5).map(Number);
51
+ if (octets.some((o) => o > 255)) return null;
52
+ return ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
53
+ }
54
+
55
+ /** True if `ip` falls within `cidr` (e.g. "10.0.10.0/24"). */
56
+ export function inCidr(ip: string, cidr: string): boolean {
57
+ const [base, bitsStr] = cidr.split('/');
58
+ const bits = Number(bitsStr);
59
+ const ipN = parseIpv4(ip);
60
+ const baseN = parseIpv4(base);
61
+ if (ipN === null || baseN === null || Number.isNaN(bits) || bits < 0 || bits > 32) return false;
62
+ if (bits === 0) return true;
63
+ const mask = (0xffffffff << (32 - bits)) >>> 0;
64
+ return (ipN & mask) >>> 0 === ((baseN & mask) >>> 0);
65
+ }
66
+
67
+ /** Classify a resolved IPv4 against the e2e topology. */
68
+ export function classifyIp(ip: string): ZoneClass {
69
+ if (parseIpv4(ip) === null) return { kind: 'invalid' };
70
+ for (const r of RESERVED) {
71
+ if (inCidr(ip, r.cidr)) return { kind: 'reserved', label: r.label };
72
+ }
73
+ for (const zone of Object.keys(ZONE_SUBNETS) as Zone[]) {
74
+ if (inCidr(ip, ZONE_SUBNETS[zone])) return { kind: 'zone', zone };
75
+ }
76
+ return { kind: 'public' };
77
+ }
78
+
79
+ /** True if `ip` is inside a segmented zone (dmz/app/secure) — unroutable from the LAN. */
80
+ export function isSegmentedZoneIp(ip: string): boolean {
81
+ const c = classifyIp(ip);
82
+ return c.kind === 'zone' && SEGMENTED_ZONES.includes(c.zone);
83
+ }
84
+
85
+ /**
86
+ * The "no leak past the public boundary" rule (ISS-0117 §D5; CLAUDE.md inviolable
87
+ * rule #1): a public DNS answer must be a routable public address — never a managed
88
+ * zone (incl. `internal`/RFC-1918) nor a reserved range.
89
+ */
90
+ export function isPublicLeakSafe(ip: string): boolean {
91
+ return classifyIp(ip).kind === 'public';
92
+ }
@@ -1,16 +0,0 @@
1
- $ORIGIN iamtheinternet.org.
2
- $TTL 300
3
-
4
- @ IN SOA ns1.iamtheinternet.org. admin.iamtheinternet.org. (
5
- 2024020247 300 60 604800 300
6
- )
7
-
8
- ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
- ; the e2e simulation is to model real internet connectivity, where
10
- ; private IPs are unreachable from outside. All public-facing names
11
- ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
- ; inbound to the right internal host.
13
- @ IN NS ns1.iamtheinternet.org.
14
- ns1 IN A 100.64.0.55
15
- @ IN A 0.0.0.0
16
- www IN A 100.100.0.100
@@ -1,16 +0,0 @@
1
- $ORIGIN lunacycle.net.
2
- $TTL 300
3
-
4
- @ IN SOA ns1.lunacycle.net. admin.lunacycle.net. (
5
- 2024020108 300 60 604800 300
6
- )
7
-
8
- ; Public DNS — RFC 1918 addresses NEVER appear here. The whole point of
9
- ; the e2e simulation is to model real internet connectivity, where
10
- ; private IPs are unreachable from outside. All public-facing names
11
- ; resolve to the firewall's external IP (100.100.0.100), which DNATs
12
- ; inbound to the right internal host.
13
- @ IN NS ns1.lunacycle.net.
14
- ns1 IN A 100.64.0.55
15
- @ IN A 100.100.0.100
16
- www IN A 100.100.0.100