1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
use pkt::dns::{DnsFlags, rcode};
use pkt::netbios::{name, ns};
use crate::err::Error::RuntimeError;
use crate::libapi::{FuncDef, Module};
use crate::str::Buf;
use crate::sym::Symbol;
use crate::val::{Val, ValDef};
const OPCODE: Module = module! {
/// # NetBIOS Name Service Opcodes
resynth mod opcode {
QUERY => Symbol::u8(ns::opcode::QUERY),
REGISTRATION => Symbol::u8(ns::opcode::REGISTRATION),
RELEASE => Symbol::u8(ns::opcode::RELEASE),
WACK => Symbol::u8(ns::opcode::WACK),
REFRESH => Symbol::u8(ns::opcode::REFRESH),
REFRESH_ALT => Symbol::u8(ns::opcode::REFRESH_ALT),
MH_REGISTRATION => Symbol::u8(ns::opcode::MH_REGISTRATION),
}
};
const RRTYPE: Module = module! {
/// # NetBIOS Name Service RR types
resynth mod rrtype {
NULL => Symbol::u16(ns::rrtype::NULL),
NB => Symbol::u16(ns::rrtype::NB),
NBSTAT => Symbol::u16(ns::rrtype::NBSTAT),
}
};
const RCODE: Module = module! {
/// # NetBIOS Name Service Response codes
resynth mod rcode {
ACT_ERR => Symbol::u8(ns::rcode::ACT_ERR),
CFT_ERR => Symbol::u8(ns::rcode::CFT_ERR),
}
};
const NBNS_FLAGS: FuncDef = func!(
/// Returns netbios-ns flags
resynth fn flags(
/// NetBIOS name service opcode
opcode: U8,
=>
/// If true, this is a response; if false, a query
response: Bool = false,
/// Authoritative Answer flag
aa: Bool = false,
/// Truncation flag
tc: Bool = false,
/// Recursion Desired flag
rd: Bool = false,
/// Recursion Available flag
ra: Bool = false,
/// Reserved (Z) bit
z: Bool = false,
/// Must be zero (maps to DNS AD bit)
ad: Bool = false, // must be zero
/// Broadcast/multicast flag
b: Bool = false,
/// Response code
rcode: U8 = rcode::NOERROR,
=>
Void
) -> U16
|mut args| {
let opcode: u8 = args.next().into();
let response: bool = args.next().into();
let aa: bool = args.next().into();
let tc: bool = args.next().into();
let rd: bool = args.next().into();
let ra: bool = args.next().into();
let z: bool = args.next().into();
let ad: bool = args.next().into();
let b: bool = args.next().into();
let rcode: u8 = args.next().into();
Ok(Val::U16(DnsFlags::default()
.response(response)
.opcode(opcode)
.aa(aa)
.tc(tc)
.rd(rd)
.ra(ra)
.z(z)
.ad(ad)
.cd(b) // CD is called B in NBNS
.rcode(rcode)
.build())
)
}
);
const NB_FLAGS: FuncDef = func!(
/// Build the NB_FLAGS field for an NB resource record RDATA (RFC 1002 §4.2.1.1).
///
/// Flags layout (MSB first): G(1) ONT(2) reserved(13)
///
/// ONT values: 0=B-node, 1=P-node, 2=M-node, 3=H-node.
/// The default produces 0x0000: unique name, B-node.
///
/// Pass `reserved:` with a non-zero value to exercise DPI engine behaviour on
/// unexpected reserved-bit patterns.
resynth fn nb_flags(
=>
/// Group name (true) or unique name (false)
group: Bool = false,
/// Owner Node Type: 0=B-node, 1=P-node, 2=M-node, 3=H-node
ont: U8 = 0,
/// Reserved bits (bits 12-0); normally zero
reserved: U16 = 0,
=>
Void
) -> U16
|mut args| {
let group: bool = args.next().into();
let ont: u8 = args.next().into();
let reserved: u16 = args.next().into();
let flags: u16 = ((group as u16) << 15)
| ((ont as u16 & 0x3) << 13)
| reserved;
Ok(Val::U16(flags))
}
);
const NAME_FLAGS: FuncDef = func!(
/// Build a name flags field for an NBSTAT name table entry (RFC 1002 §4.2.18).
///
/// Flags layout (MSB first): G(1) ONT(2) DRG(1) CNF(1) ACT(1) PRM(1) reserved(9)
///
/// ONT values: 0=B-node, 1=P-node, 2=M-node, 3=H-node.
/// The default produces 0x0400: active, unique name, B-node.
///
/// Pass `reserved:` with a non-zero value to exercise DPI engine behaviour on
/// unexpected reserved-bit patterns.
resynth fn name_flags(
=>
/// Owner Node Type: 0=B-node, 1=P-node, 2=M-node, 3=H-node
ont: U8 = 0,
/// Name is active
active: Bool = true,
/// Group name (true) or unique name (false)
group: Bool = false,
/// Name is in the process of being deregistered
drg: Bool = false,
/// Name is in conflict
cnf: Bool = false,
/// Permanent node name (not registered via NBNS)
prm: Bool = false,
/// Reserved bits (bits 8-0); normally zero
reserved: U16 = 0,
=>
Void
) -> U16
|mut args| {
let ont: u8 = args.next().into();
let active: bool = args.next().into();
let group: bool = args.next().into();
let drg: bool = args.next().into();
let cnf: bool = args.next().into();
let prm: bool = args.next().into();
let reserved: u16 = args.next().into();
let flags: u16 = ((group as u16) << 15)
| ((ont as u16 & 0x3) << 13)
| ((drg as u16) << 12)
| ((cnf as u16) << 11)
| ((active as u16) << 10)
| ((prm as u16) << 9)
| reserved;
Ok(Val::U16(flags))
}
);
const NAME_ENTRY: FuncDef = func!(
/// Build a single name table entry for an NBSTAT RDATA block.
///
/// Pads the name to 15 bytes (space-filled), appends the one-byte suffix and
/// two-byte flags. Each entry is exactly 18 bytes and is consumed by
/// `netbios::ns::nbstat_rdata()`.
///
/// The default suffix (0x00) is the workstation service. Common suffixes:
/// 0x00 = workstation, 0x03 = messenger (logged-in user), 0x20 = file server.
///
/// The default flags (0x0400) represent an active, unique, B-node name;
/// use `netbios::ns::name_flags()` to construct non-default values.
resynth fn name_entry(
=>
/// One-byte NetBIOS name suffix (service type)
suffix: U8 = 0x00,
/// Two-byte name flags field
flags: U16 = 0x0400,
=>
Str
) -> Str
|mut args| {
let suffix: u8 = args.next().into();
let flags: u16 = args.next().into();
let data: Buf = args.join_extra(b"").into();
let padded = name::pad(data.as_ref(), suffix).ok_or(RuntimeError)?;
let mut out = Vec::with_capacity(18);
out.extend_from_slice(&padded); // 15-byte padded name + 1-byte suffix
out.extend(flags.to_be_bytes());
Ok(Val::str(out))
}
);
const STATISTICS: FuncDef = func!(
/// Build the 64-byte statistics block appended to every NBSTAT RDATA section.
///
/// The block contains a 6-byte unit ID (MAC address) followed by 58 bytes of
/// counters. Pass the MAC address as the collect argument; omit it to use all
/// zeros.
resynth fn statistics(
=>
=>
Str
) -> Str
|mut args| {
let mac: Buf = args.join_extra(b"").into();
let mut stats = vec![0u8; 64];
let mac = mac.as_ref();
stats[..mac.len().min(6)].copy_from_slice(&mac[..mac.len().min(6)]);
Ok(Val::str(stats))
}
);
const NBSTAT_RDATA: FuncDef = func!(
/// Build the complete RDATA section for an NBSTAT resource record.
///
/// Accepts `netbios::ns::name_entry()` values as collect arguments, prepends
/// the one-byte name count (derived from the total length), and appends a
/// zeroed 64-byte statistics block.
///
/// To include a MAC address in the statistics block, append
/// `netbios::ns::statistics("|aa bb cc dd ee ff|")` manually and omit this
/// function in favour of assembling the RDATA yourself.
resynth fn nbstat_rdata(
=>
=>
Str
) -> Str
|mut args| {
let entries: Buf = args.join_extra(b"").into();
let count = entries.len() / 18;
let mut rdata = Vec::with_capacity(1 + entries.len() + 64);
rdata.push(count as u8);
rdata.extend(entries.as_ref());
rdata.extend([0u8; 64]); // zeroed statistics block
Ok(Val::str(rdata))
}
);
pub const NS: Module = module! {
/// # NetBIOS Name Service
resynth mod ns {
opcode => Symbol::Module(&OPCODE),
rrtype => Symbol::Module(&RRTYPE),
rcode => Symbol::Module(&RCODE),
flags => Symbol::Func(&NBNS_FLAGS),
nb_flags => Symbol::Func(&NB_FLAGS),
name_flags => Symbol::Func(&NAME_FLAGS),
name_entry => Symbol::Func(&NAME_ENTRY),
statistics => Symbol::Func(&STATISTICS),
nbstat_rdata => Symbol::Func(&NBSTAT_RDATA),
}
};
const NAME_ENCODE: FuncDef = func! (
/// First-level encode a NetBIOS name, including padding and the one-byte suffix field.
///
/// Returns the raw 32 encoded bytes only — no DNS label length prefix or
/// terminating null byte. To produce a complete DNS-format name label
/// suitable for use in an NBNS packet, wrap the result with `dns::name()`:
///
/// ```resynth
/// dns::name(netbios::name::encode("BILLG"))
/// ```
resynth fn encode(
=>
/// One-byte suffix identifying the NetBIOS name type
suffix: U8 = 0,
=>
Str
) -> Str
|mut args| {
let suffix: u8 = args.next().into();
let data: Buf = args.join_extra(b"").into();
let res = name::encode(data.as_ref(), suffix).ok_or(RuntimeError)?;
Ok(Val::Str(res.as_ref().into()))
}
);
const SUFFIX: Module = module! {
/// # NetBIOS name suffixes
///
/// The one-byte suffix field identifies the service type registered under a
/// NetBIOS name. These are the widely-used Microsoft values (see MS-NBTE).
///
/// ## Unique names (registered per host)
///
/// | Constant | Value | Service |
/// |----------|-------|---------|
/// | `WORKSTATION` | `0x00` | Workstation service (redirector) |
/// | `MESSENGER` | `0x03` | Messenger service — identifies the logged-in user |
/// | `RAS_SERVER` | `0x06` | Remote Access Server |
/// | `DOMAIN_MASTER` | `0x1B` | Domain master browser |
/// | `DOMAIN_CONTROLLER` | `0x1C` | Domain controller |
/// | `MASTER_BROWSER` | `0x1D` | Local master browser |
/// | `FILE_SERVER` | `0x20` | File and print server (Server service) |
/// | `RAS_CLIENT` | `0x21` | Remote Access client |
///
/// ## Group names (registered by multiple hosts)
///
/// | Constant | Value | Service |
/// |----------|-------|---------|
/// | `DOMAIN_NAME` | `0x00` | Domain name (group) |
/// | `BROWSER_ELECTIONS` | `0x1E` | Browser elections |
resynth mod suffix {
WORKSTATION => Symbol::u8(0x00),
MESSENGER => Symbol::u8(0x03),
RAS_SERVER => Symbol::u8(0x06),
DOMAIN_MASTER => Symbol::u8(0x1B),
DOMAIN_CONTROLLER => Symbol::u8(0x1C),
MASTER_BROWSER => Symbol::u8(0x1D),
BROWSER_ELECTIONS => Symbol::u8(0x1E),
FILE_SERVER => Symbol::u8(0x20),
RAS_CLIENT => Symbol::u8(0x21),
}
};
pub const NAME: Module = module! {
/// # NetBIOS names
resynth mod name {
encode => Symbol::Func(&NAME_ENCODE),
suffix => Symbol::Module(&SUFFIX),
}
};
pub const NETBIOS: Module = module! {
/// # Microsoft NetBIOS
///
/// NetBIOS over TCP/IP — name encoding, name-service queries, and protocol constants.
///
/// ## Wire format
///
/// NetBIOS Name Service (NBNS, UDP port 137) uses the DNS wire format: the same
/// 12-byte header, question, and resource record structures. Use the `dns` module
/// helpers (`dns::hdr`, `dns::flags`, `dns::name`, `dns::answer`) to build the
/// packet framing, and the `netbios` helpers for name encoding and NBNS-specific
/// constants.
///
/// ### Example: positive Name Query Response for "BILLG" (workstation)
///
/// ```resynth
/// import ipv4;
/// import dns;
/// import netbios;
///
/// ipv4::udp::unicast(
/// 192.168.1.100/137,
/// 192.168.1.1/137,
/// dns::hdr(
/// 0x1234,
/// netbios::ns::flags(netbios::ns::opcode::QUERY, response: true, aa: true, ra: true),
/// ancount: 1,
/// ),
/// dns::answer(
/// dns::name(netbios::name::encode("BILLG")),
/// atype: netbios::ns::rrtype::NB,
/// ttl: 300,
/// netbios::ns::nb_flags(), # NB_FLAGS: B-node, unique (defaults)
/// 192.168.1.100, # NB_ADDRESS
/// ),
/// );
/// ```
resynth mod netbios {
ns => Symbol::Module(&NS),
name => Symbol::Module(&NAME),
}
};