aboutsummaryrefslogtreecommitdiff
path: root/core/net/common.odin
blob: 12add82258f04024d46123eb97b1a3706a9359c3 (plain)
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
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
#+build windows, linux, darwin, freebsd
package net

/*
	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
	For other protocols and their features, see subdirectories of this package.

	This file collects structs, enums and settings applicable to the entire package in one handy place.
	Platform-specific ones can be found in their respective `*_windows.odin` and similar files.
*/

/*
	Copyright 2022 Tetralux        <tetraluxonpc@gmail.com>
	Copyright 2022 Colin Davidson  <colrdavidson@gmail.com>
	Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
	Copyright 2024 Feoramund       <rune@swevencraft.org>.
	Made available under Odin's BSD-3 license.

	List of contributors:
		Tetralux:        Initial implementation
		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
		Jeroen van Rijn: Cross platform unification, code style, documentation
		Feoramund:       FreeBSD platform code
*/

import "base:runtime"

/*
	TUNEABLES - See also top of `dns.odin` for DNS configuration.

	Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new
	socket, and the client socket, respectively.
	This can also be set on a per-socket basis using the 'options' optional parameter to those procedures.

	When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being
	coalesced into fewer network packets.

	This makes the networking layer more eagerly send data when you ask it to,
	which can reduce latency by up to 200ms.

	This does mean that a lot of small writes will negatively effect throughput however,
	since the Nagle algorithm will be disabled, and each write becomes one
	IP packet. This will increase traffic by a factor of 40, with IP and TCP
	headers for each payload.

	However, you can avoid this by buffering things up yourself if you wish to send a lot of
	short data chunks, when TCP_NODELAY is enabled on that socket.
*/

ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)

// COMMON DEFINITIONS
Maybe :: runtime.Maybe

Network_Error :: union #shared_nil {
	General_Error,
	Platform_Error,
	Create_Socket_Error,
	Dial_Error,
	Listen_Error,
	Accept_Error,
	Bind_Error,
	TCP_Send_Error,
	UDP_Send_Error,
	TCP_Recv_Error,
	UDP_Recv_Error,
	Shutdown_Error,
	Socket_Option_Error,
	Set_Blocking_Error,
	Parse_Endpoint_Error,
	Resolve_Error,
	DNS_Error,
}

#assert(size_of(Network_Error) == 8)

General_Error :: enum u32 {
	None = 0,
	Unable_To_Enumerate_Network_Interfaces = 1,
}

// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
Platform_Error :: enum u32 {}

Parse_Endpoint_Error :: enum u32 {
	None          = 0,
	Bad_Port      = 1,
	Bad_Address,
	Bad_Hostname,
}

Resolve_Error :: enum u32 {
	None = 0,
	Unable_To_Resolve = 1,
}

DNS_Error :: enum u32 {
	None = 0,
	Invalid_Hostname_Error = 1,
	Invalid_Hosts_Config_Error,
	Invalid_Resolv_Config_Error,
	Connection_Error,
	Server_Error,
	System_Error,
}

// SOCKET OPTIONS & DEFINITIONS
TCP_Options :: struct {
	no_delay: bool,
}

default_tcp_options := TCP_Options {
	no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
}

/*
	To allow freely using `Socket` in your own data structures in a cross-platform manner,
	we treat it as a handle large enough to accomodate OS-specific notions of socket handles.

	The platform code will perform the cast so you don't have to.
*/
Socket     :: distinct i64

TCP_Socket :: distinct Socket
UDP_Socket :: distinct Socket

Socket_Protocol :: enum {
	TCP,
	UDP,
}

Any_Socket :: union {
	TCP_Socket,
	UDP_Socket,
}

/*
	ADDRESS DEFINITIONS
*/

IP4_Address :: distinct [4]u8
IP6_Address :: distinct [8]u16be
Address :: union {IP4_Address, IP6_Address}

IP4_Loopback :: IP4_Address{127, 0, 0, 1}
IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}

IP4_Any := IP4_Address{}
IP6_Any := IP6_Address{}

IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}

Endpoint :: struct {
	address: Address,
	port:    int,
}

Address_Family :: enum {
	IP4,
	IP6,
}

Netmask :: distinct Address

/*
	INTERFACE / LINK STATE
*/
Network_Interface :: struct {
	adapter_name:     string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage.
	friendly_name:    string,
	description:      string,
	dns_suffix:       string,

	physical_address: string, // MAC address, etc.
	mtu:              u32,

	unicast:          [dynamic]Lease,
	multicast:        [dynamic]Address,
	anycast:          [dynamic]Address,

	gateways:         [dynamic]Address,
	dhcp_v4:          Address,
	dhcp_v6:          Address,

	tunnel_type:      Tunnel_Type,

	link: struct {
		state:          Link_State,
		transmit_speed: u64,
		receive_speed:  u64,
	},
}

// Empty bit set is unknown state.
Link_States :: enum u32 {
	Up               = 1,
	Down             = 2,
	Testing          = 3,
	Dormant          = 4,
	Not_Present      = 5,
	Lower_Layer_Down = 6,
	Loopback         = 7,
}
Link_State :: bit_set[Link_States; u32]

Lease :: struct {
	address:  Address,
	netmask:  Netmask,
	lifetime: struct {
		valid:     u32,
		preferred: u32,
		lease:     u32,
	},
	origin: struct {
		prefix: Prefix_Origin,
		suffix: Suffix_Origin,
	},
	address_duplication: Address_Duplication,
}

Tunnel_Type :: enum i32 {
	None         = 0,
	Other        = 1,
	Direct       = 2,
	IPv4_To_IPv6 = 11,
	ISA_TAP      = 13,
	Teredo       = 14,
	IP_HTTPS     = 15,
}

Prefix_Origin :: enum i32 {
	Other                = 0,
	Manual               = 1,
	Well_Known           = 2,
	DHCP                 = 3,
	Router_Advertisement = 4,
	Unchanged            = 16,
}

Suffix_Origin :: enum i32 {
	Other                = 0,
	Manual               = 1,
	Well_Known           = 2,
	DHCP                 = 3,
	Link_Layer_Address   = 4,
	Random               = 5,
	Unchanged            = 16,
}

Address_Duplication :: enum i32 {
	Invalid    = 0,
	Tentative  = 1,
	Duplicate  = 2,
	Deprecated = 3,
	Preferred  = 4,
}

// DNS DEFINITIONS
DNS_Configuration :: struct {
	// Configuration files.
	resolv_conf: string,
	hosts_file:  string,

	// TODO: Allow loading these up with `reload_configuration()` call or the like,
	// so we don't have to do it each call.
	name_servers:       []Endpoint,
	hosts_file_entries: []DNS_Record,
}

DNS_Record_Type :: enum u16 {
	DNS_TYPE_A     = 0x1,  // IP4 address.
	DNS_TYPE_NS    = 0x2,  // IP6 address.
	DNS_TYPE_CNAME = 0x5,  // Another host name.
	DNS_TYPE_MX    = 0xf,  // Arbitrary binary data or text.
	DNS_TYPE_AAAA  = 0x1c, // Address of a name (DNS) server.
	DNS_TYPE_TEXT  = 0x10, // Address and preference priority of a mail exchange server.
	DNS_TYPE_SRV   = 0x21, // Address, port, priority, and weight of a host that provides a particular service.

	IP4            = DNS_TYPE_A,
	IP6            = DNS_TYPE_AAAA,
	CNAME          = DNS_TYPE_CNAME,
	TXT            = DNS_TYPE_TEXT,
	NS             = DNS_TYPE_NS,
	MX             = DNS_TYPE_MX,
	SRV            = DNS_TYPE_SRV,
}

// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.
DNS_Record_Base :: struct {
	record_name: string,
	ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated.
}

// An IP4 address that the domain name maps to. There can be any number of these.
DNS_Record_IP4 :: struct {
	using base: DNS_Record_Base,
	address:    IP4_Address,
}

// An IPv6 address that the domain name maps to. There can be any number of these.
DNS_Record_IP6 :: struct {
	using base: DNS_Record_Base,
	address:    IP6_Address,
}

/*
	Another domain name that the domain name maps to.
	Domains can be pointed to another domain instead of directly to an IP address.
	`get_dns_records` will recursively follow these if you request this type of record.
*/
DNS_Record_CNAME :: struct {
	using base: DNS_Record_Base,
	host_name:  string,
}

/*
	Arbitrary string data that is associated with the domain name.
	Commonly of the form `key=value` to be parsed, though there is no specific format for them.
	These can be used for any purpose.
*/
DNS_Record_TXT :: struct {
	using base: DNS_Record_Base,
	value:      string,
}

/*
	Domain names of other DNS servers that are associated with the domain name.
	TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these.
*/
DNS_Record_NS :: struct {
	using base: DNS_Record_Base,
	host_name:  string,
}

// Domain names for email servers that are associated with the domain name.
// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred.
DNS_Record_MX :: struct {
	using base: DNS_Record_Base,
	host_name:  string,
	preference: int,
}

/*
	An endpoint for a service that is available through the domain name.
	This is the way to discover the services that a domain name provides.

	Clients MUST attempt to contact the host with the lowest priority that they can reach.
	If two hosts have the same priority, they should be contacted in the order according to their weight.
	Hosts with larger weights should have a proportionally higher chance of being contacted by clients.
	A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise).

	The host may be "." to indicate that it is "decidedly not available" on this domain.
*/
DNS_Record_SRV :: struct {
	// base contains the full name of this record.
	// e.g: _sip._tls.example.com
	using base: DNS_Record_Base,

	// The hostname or address where this service can be found.
	target:        string,
	// The port on which this service can be found.
	port:          int,

	service_name:  string, // NOTE(tetra): These are substrings of 'record_name'
	protocol_name: string, // NOTE(tetra): These are substrings of 'record_name'

	// Lower is higher priority
	priority:      int,
	// Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight.
	// The number of seconds that it will take to update the record.
	weight:        int,
}

DNS_Record :: union {
	DNS_Record_IP4,
	DNS_Record_IP6,
	DNS_Record_CNAME,
	DNS_Record_TXT,
	DNS_Record_NS,
	DNS_Record_MX,
	DNS_Record_SRV,
}

DNS_Response_Code :: enum u16be {
	No_Error,
	Format_Error,
	Server_Failure,
	Name_Error,
	Not_Implemented,
	Refused,
}

DNS_Query :: enum u16be {
	Host_Address              = 1,
	Authoritative_Name_Server = 2,
	Mail_Destination          = 3,
	Mail_Forwarder            = 4,
	CNAME                     = 5,
	All                       = 255,
}

DNS_Header :: struct {
	id:                     u16be,
	is_response:            bool,
	opcode:                 u16be,
	is_authoritative:       bool,
	is_truncated:           bool,
	is_recursion_desired:   bool,
	is_recursion_available: bool,
	response_code:          DNS_Response_Code,
}

DNS_Record_Header :: struct #packed {
	type:   u16be,
	class:  u16be,
	ttl:    u32be,
	length: u16be,
}

DNS_Host_Entry :: struct {
	name: string,
	addr: Address,
}