`, for example, `d1-p5000`.
## Dissection
Dissection is the process of parsing a message to represent it in a structured format.
### dissect_url
```
function dissect_url(url)
```
Returns a table containing the parsed parts of a URL in the format `proto://creds@domain:port/uri`.
If any part is missing, the corresponding field will not be present in the table.
Example of parsing `https://user:pass@domain.com:12345/my_uri/script.php?a=1&b=3`
.proto
string https
.creds
string user:pass
.domain
string domain.com
.port
string 12345
.uri
string /my_uri/script.php?a=1&b=3
### dissect_nld
```
function dissect_nld(domain, level)
```
Retrieves a domain of a specific `level` from the `domain` string. For example, level=2 for 'www.microsoft.com' returns 'microsoft.com'.
If the specified level does not exist, it returns `nil`.
### dissect_http
```
function http_dissect_req(http)
function http_dissect_reply(http)
function http_reconstruct_req(hdis, unixeol)
```
Parses an HTTP request or response. The `http` parameter is a multi-line string.
The parsed result is a table with nested sub-tables.
Headers provide the start and end positions for both the header name and the value itself.
To find a header by name use [array_field_search](#array_search) with field name "header_low" which contains header name in lower case.
The HTTP request reconstructor takes a parsed table and recreates the raw string. The `unixeol` parameter replaces the standard HTTP line ending (0D0A) with 0A. This is non-standard and will break almost all servers except for Nginx.
Example of parsing an HTTP request `http://testhost.com/testuri`
.uri
string /test_uri
.headers
.1
.header
string Content-Length
.header_low
string content-length
.value
string 330
.pos_start
number 43
.pos_end
number 61
.pos_header_end
number 56
.pos_value_start
number 59
.2
.header
string Host
.header_low
string host
.value
string testhost.com
.pos_start
number 24
.pos_end
number 41
.pos_header_end
number 27
.pos_value_start
number 30
.method
string GET
Example of parsing an HTTP response
.code
number 200
.headers
.1
.pos_header_end
number 28
.pos_value_start
number 31
.header
string Content-Type
.header_low
string content-type
.value
string text/html
.pos_start
number 17
.pos_end
number 39
.2
.pos_header_end
number 54
.pos_value_start
number 57
.header
string Content-Length
.header_low
string content-length
.value
string 650
.pos_start
number 41
.pos_end
number 59
### dissect_tls
```
function tls_dissect(tls, offset, partialOK)
function tls_reconstruct(tdis)
```
Parsing and reconstruction of TLS. Capabilities of these functions:
1. Any TLS handshake without a TLS record (including Client/Server Hello). For example, data extracted from `desync.decrypt_data` in QUIC.
2. Any TLS records: Handshake, Certificate, Change Cipher Spec, and others.
3. Handshakes split across multiple TLS records (e.g., the result of `tpws --tlsrec`).
4. (Dissect only) Incomplete data blocks if `partialOK=true`. The system recovers as much as possible, though a full reassembly will not be feasible.
5. All handshakes are moved to a separate table. A dissection is performed for Client/Server Hello; others are kept as raw data fields.
6. TLS extensions from Client/Server Hello: server name, ALPN, supported versions, compress certificate, signature algorithms, delegated credentials, supported groups, EC point formats, PSK key exchange modes, key share, and QUIC transport parameters. Other extensions are not parsed and are kept as raw data fields.
7. If a record layer is present, reconstruction is performed according to the lengths of individual records. If the final segment does not fit, the TLS record is expanded to accommodate the remaining data.
8. If no changes are made, the dissect + reconstruct process yields binary-identical blobs.
The functions do not work with DTLS.
`tls_dissect` returns a table - a parsed raw TLS string starting from the specified `offset` (1-indexed). `reconstruct_dissect` returns the raw string of the assembled `tdis` parsing. In case of an error, `nil` is returned.
The simplest way to obtain a dissection sample is: `--payload=tls_client_hello --lua-desync=luaexec:code="var_debug(tls_dissect(desync.reasm_data))"`.
Then, initiate a TLS request.
Example of a TLS dissection from a request to https://google.com
.rec
.1
.ver
number 769
.type
number 22
.name
string handshake
.len
number 512
.encrypted
boolean false
.htype
number 1
.data
string 01 00 01 FC 03 03 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4 20 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44 00 3C 13 02 13 03 13 01 C0 2C C0 30 00 9F CC A9 CC A8 CC AA C0 2B C0 2F 00 9E C0 24 C0 28 00 6B C0 23 C0 27 00 67 C0 0A C0 14 00 39 C0 09 C0 13 00 33 00 9D 00 9C 00 3D 00 3C 00 35 00 2F 01 00 01 77 FF 01 00 01 00 00 00 00 0F 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D 00 0B 00 04 03 00 01 02 00 0A 00 16 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04 00 10 00 0E 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31 00 16 00 00 00 17 00 00 00 31 00 00 00 0D 00 30 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2B 00 05 04 03 04 03 03 00 2D 00 02 01 01 00 33 00 26 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D 00 15 00 AE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
.handshake
.1
.dis
.ver
number 771
.type
number 1
.name
string client_hello
.cipher_suites
.1
number 4866
.2
number 4867
.3
number 4865
.4
number 49196
.5
number 49200
.6
number 159
.7
number 52393
.8
number 52392
.9
number 52394
.10
number 49195
.11
number 49199
.12
number 158
.13
number 49188
.14
number 49192
.15
number 107
.16
number 49187
.17
number 49191
.18
number 103
.19
number 49162
.20
number 49172
.21
number 57
.22
number 49161
.23
number 49171
.24
number 51
.25
number 157
.26
number 156
.27
number 61
.28
number 60
.29
number 53
.30
number 47
.compression_methods
.1
number 0
.ext
.1
.type
number 65281
.name
string renegotiation_info
.data
string 00
.2
.dis
.list
.1
.name
string google.com
.type
number 0
.type
number 0
.name
string server_name
.data
string 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D
.3
.dis
.list
.1
number 0
.2
number 1
.3
number 2
.type
number 11
.name
string ec_point_formats
.data
string 03 00 01 02
.4
.dis
.list
.1
number 29
.2
number 23
.3
number 30
.4
number 25
.5
number 24
.6
number 256
.7
number 257
.8
number 258
.9
number 259
.10
number 260
.type
number 10
.name
string supported_groups
.data
string 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04
.5
.dis
.list
.1
string h2
.2
string http/1.1
.type
number 16
.name
string application_layer_protocol_negotiation
.data
string 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31
.6
.type
number 22
.name
string encrypt_then_mac
.data
string
.7
.type
number 23
.name
string extended_master_secret
.data
string
.8
.type
number 49
.name
string post_handshake_auth
.data
string
.9
.dis
.list
.1
number 1027
.2
number 1283
.3
number 1539
.4
number 2055
.5
number 2056
.6
number 2074
.7
number 2075
.8
number 2076
.9
number 2057
.10
number 2058
.11
number 2059
.12
number 2052
.13
number 2053
.14
number 2054
.15
number 1025
.16
number 1281
.17
number 1537
.18
number 771
.19
number 769
.20
number 770
.21
number 1026
.22
number 1282
.23
number 1538
.type
number 13
.name
string signature_algorithms
.data
string 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02
.10
.dis
.list
.1
number 772
.2
number 771
.type
number 43
.name
string supported_versions
.data
string 04 03 04 03 03
.11
.dis
.list
.1
number 1
.type
number 45
.name
string psk_key_exchange_modes
.data
string 01 01
.12
.dis
.list
.1
.group
number 29
.kex
string E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D
.type
number 51
.name
string key_share
.data
string 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D
.13
.type
number 21
.name
string padding
.data
string 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
.session_id
string 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44
.random
string 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4
.type
number 1
.name
string client_hello
.data
string 01 00 01 FC 03 03 87 36 D1 0E 19 78 8F 8B 41 5E 05 74 92 EF E7 9D 3E 83 F3 9D F4 C4 C6 6C 3E DC 5A 8C EF FD BC B4 20 1C AF 31 7A EB D2 FD 8B 1F C6 E8 DB CF 02 28 93 C4 AE 13 E1 17 ED 62 D8 3D 2F DE 03 67 A1 1A 44 00 3C 13 02 13 03 13 01 C0 2C C0 30 00 9F CC A9 CC A8 CC AA C0 2B C0 2F 00 9E C0 24 C0 28 00 6B C0 23 C0 27 00 67 C0 0A C0 14 00 39 C0 09 C0 13 00 33 00 9D 00 9C 00 3D 00 3C 00 35 00 2F 01 00 01 77 FF 01 00 01 00 00 00 00 0F 00 0D 00 00 0A 67 6F 6F 67 6C 65 2E 63 6F 6D 00 0B 00 04 03 00 01 02 00 0A 00 16 00 14 00 1D 00 17 00 1E 00 19 00 18 01 00 01 01 01 02 01 03 01 04 00 10 00 0E 00 0C 02 68 32 08 68 74 74 70 2F 31 2E 31 00 16 00 00 00 17 00 00 00 31 00 00 00 0D 00 30 00 2E 04 03 05 03 06 03 08 07 08 08 08 1A 08 1B 08 1C 08 09 08 0A 08 0B 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2B 00 05 04 03 04 03 03 00 2D 00 02 01 01 00 33 00 26 00 24 00 1D 00 20 E0 40 E1 0A BF AD 5B 08 48 16 E5 A6 A9 90 E4 28 A1 67 40 1F AF A4 7B 9B 0A F9 32 2A 01 95 8B 5D 00 15 00 AE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Successfully parsed elements are presented as `dis` subtables, while others remain as `raw data` fields.
Some elements include a `name` field for visual analysis purposes only; the `type` fields are the primary identifiers.
To locate values within lists, use the [array search functions](#array_search).
Numerous TLS-related constants are defined in `zapret-lib.lua`. Before hardcoding values, check if a suitable constant already exists.
The `handshake` table is indexed by handshake type. The most common types are `TLS_HANDSHAKE_TYPE_CLIENT` and `TLS_HANDSHAKE_TYPE_SERVER`. Since these have values 1 and 2 respectively, it might appear that handshake elements follow a sequential order starting from 1, but this is not the case.
Extensions and other lists are indexed numerically starting from 1 rather than by type. This is because their sequence matters and multiple elements of the same type may exist.
When adding custom data, you must reproduce the minimum required structure. You can choose to populate only the `raw data` field; if no `dis` subtable is present, this field will be used during reconstruction. If a `dis` subtable exists, it must be correctly populated according to the specific data element.
The following code example searches for the SNI extension within the `tdis` dissect. If missing, it inserts it at the beginning and then adds the domain "example.com".```
```
local idx_sni = array_field_search(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext, "type", TLS_EXT_SERVER_NAME)
if not idx_sni then
table.insert(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext, 1, { type = TLS_EXT_SERVER_NAME, dis = { list = {} } } )
idx_sni = 1
end
table.insert(tdis.handshake[TLS_HANDSHAKE_TYPE_CLIENT].dis.ext[idx_sni].dis.list, { name = "example.com", type = 0 } )
```
## Working with L3 and L4 protocol elements
### find_tcp_options
```
function find_tcp_option(options, kind)
```
Returns the first element of `dis.tcp.options` with the specified `kind`. Returns `nil` if not found.
### ip6hdr
```
function find_ip6_exthdr(exthdr, proto)
```
Returns the first element of `dis.ip6.exthdr` where `type = proto`.
```
function insert_ip6_exthdr(ip6, idx, header_type, data)
function del_ip6_exthdr(ip6, idx)
function fix_ip6_next(ip6, last_proto)
```
### ip protocol
```
function ip_proto_l3(dis)
function ip_proto_l4(dis)
function ip_proto(dis)
```
Functions discover ip protocol of the end payload.
* ip_proto_l3 - ipv4 - ip.ip_p , ipv6 - ip6.ip6_nxt or next from the last extension header. nil, if next field is not set.
* ip_proto_l4 - IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, IPPROTO_UDP depending on presence of tcp,udp,icmp,ip6. nil if tcp,udp,icmp are absent.
* ip_proto - ip_proto_l4. если он вернул nil, то ip_proto_l3.
```
function fix_ip_proto(dis, proto)
```
Set end protocol as proto. If proto is not passed or nil - use result of "ip_proto(dis)".
### packet_len
These functions work with the IPv6 header dissect `ip6` and its extension headers (`ip6.exthdr`).
When inserting or deleting extension headers, the correct chain of subsequent protocols is maintained, starting from the base IPv6 header.
- `insert_ip6_exthdr` inserts an extension header with the protocol `header_type` and `data` into the `ip6` dissect at the specified index `idx`. If `idx=nil`, it is appended to the end. The `data` size must be 6+N*4 for `IPPROTO_AH` and 6+N*8 for others; otherwise, errors will occur during reconstruction.
- `del_ip6_exthdr` removes the extension header at the specified index `idx`.
- `fix_ip6_next` restores the correct chain of subsequent protocols using `ip6.ip6_nxt` and the `type` fields within `ip6.exthdr`.
```
function l3_base_len(dis)
function l3_extra_len(dis, ip6_exthdr_last_idx)
function l3_len(dis)
function l4_base_len(dis)
function l4_extra_len(dis)
function l4_len(dis)
function l3l4_extra_len(dis)
function l3l4_len(dis)
function packet_len(dis)
```
Calculates the sizes of various dissect elements after reconstruction.
- `l3_base_len` – Base length of the IP/IPv6 header without options or extension headers.
- `l3_extra_len` – Length of IP options or the total length of all extension headers. If `ip6_exthdr_last_idx` is specified, it counts extension headers up to that index.
- `l3_len` – Total length of the IP/IPv6 header including options and extension headers.
- `l4_base_len` – Base length of the TCP or UDP header.
- `l4_extra_len` – Length of TCP options for TCP; 0 for UDP.
- `l4_len` – Total length of the TCP header with options, or the UDP header length.
- `l3l4_extra_len` – Sum of `l3_extra_len` and `l4_extra_len`.
- `l3l4_len` – Total length of IP/IPv6 and TCP headers, including all options and extension headers.
- `packet_len` – Total length of the reconstructed packet, including the L4 payload.
## Working with hostnames
### genhost
```
function genhost(len, template)
```
Generates a random hostname of length `len`.
- If a `template` is provided, it generates a random subdomain to fit the specified `len`. If the length `len` is insufficient, it returns a left-truncated version of the template.
- If `template=nil`, it generates a random subdomain for one of the known 3-letter TLDs. If `len < 7`, it generates a random domain of length `len` without dots.
Examples:
```
-- template "google.com", len=16 : h82aj.google.com
-- template "google.com", len=11 : .google.com
-- template "google.com", len=10 : google.com
-- template "google.com", len=7 : gle.com
-- no template, len=6 : b8c54a
-- no template, len=7 : u9a.edu
-- no template, len=10 : jgha7c.com
```
### host_ip
```
function host_ip(desync)
function host_or_ip(desync)
```
- `host_ip` returns a string representation of `desync.target.ip` or `desync.target.ip6`.
- `host_or_ip` returns `desync.track.hostname` if both `track` and `track.hostname` exist; otherwise, it returns `host_ip(desync)`.
## File name and path operations
```
function is_absolute_path(path)
function append_path(path, file)
function writeable_file_name(filename)
```
- `is_absolute_path` returns true if the path starts from the root. It accounts for CYGWIN path specifics.
- `append_path` appends a file or directory name `file` to `path`, using '/' as a separator.
- `writeable_file_name` returns `filename` if it contains an absolute path or if the `WRITEABLE` environment variable is not set. Otherwise, it retrieves the path from the `WRITEABLE` environment variable and appends the `filename` using `append_path`.
## Reading and writing Files
```
function readfile(filename)
```
Reads the entire file. Throws an error if opening or reading the file fails.
```
function z_readfile(filename, expected_ratio)
```
Automatically detects whether the file is gzipped. If so, it decompresses it; otherwise, it reads it as is. Throws an error if opening or reading the file fails.
`expected_ratio` is the expected ratio of decompressed data to compressed data (defaults to 4).
```
function writefile(filename, data)
```
Writes `data` to a file. Throws an error if opening the file fails.
## Data compression
```
function is_gzip_file(filename)
```
Returns true if the file is a gzip file, otherwise false. Throws an error if the file cannot be opened.
```
function gunzip_file(filename, expected_ratio, read_block_size)
```
Decompresses a file and returns it as a raw string. Throws an error if opening or reading the file fails. Returns `nil` in case of memory exhaustion. `read_block_size` determines the chunk size for reading (defaults to 16K).
`expected_ratio` is the expected ratio of decompressed data to compressed data (defaults to 4).
```
function gzip_file(filename, data, expected_ratio, level, memlevel, compress_block_size)
```
Compresses a raw string into a gzip file. Throws an error if opening or reading the file fails. Returns `nil` if the gzip data is corrupted or memory is exhausted.
`level` is the compression level from 1 to 9 (defaults to 9), `memlevel` is the allowed memory usage level from 1 to 8 (defaults to 8). `compress_block_size` is the chunk size for compression (defaults to 16K).
`expected_ratio` is the expected ratio of decompressed data to compressed data (defaults to 2).
## autottl
```
function parse_autottl(s)
function autottl(incoming_ttl, attl)
```
The `autottl` mechanism is used to automatically determine a TTL based on the incoming packet's TTL. The resulting TTL either falls slightly short of the destination or slightly exceeds the path length to it.
`delta` is the positive or negative difference relative to the estimated path length. `min-max` is the allowed range.
If the final result falls outside this range, the boundary values are assigned.
If `delta < 0` and the result is equal to or longer than the path, or if `delta >= 0` and the result is shorter than the path, the algorithm fails and returns `nil`.
Calculations are based on the assumption of symmetric inbound and outbound paths and the default TTLs used by major operating systems (64, 128, 255).
This heuristic is not always accurate due to these assumptions potentially being false, but it can sometimes be tuned to an acceptable margin of error.
- `parse_autottl` converts a string in the format `,-` into a table with identical fields. Throws an error if the format of `s` is invalid.
- `autottl` makes a heuristic guess about the hop length based on the TTL of incoming packets and calculates the TTL, taking into account the delta and the allowed range.
The `incoming_ttl` can be retrieved from `desync`. `attl` uses the table format obtained via `parse_autottl`.
## Operations with dissects
The following functions and transmission functions use standard option blocks, provided as fields in a separately passed table.
The options table follows the `desync.arg` format. `desync.arg` can be passed directly without modification.
### standard ipid
**ipid_options**
| Field | Description |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
| ip_id | ip_id assignment mode: seq, rnd, zero, none
seq - sequential
rnd - random
zero - zero
none - do not change |
| ip_id_conn | remember the last generated seq value and start from it in the next packet. Does not work without desync.track |
The `seq` mode will not always result in the expected or acceptable sequence.
The `ip_id` setting is only applied within specific functions; it is not automatically applied to all passing packets.
Since the OS does not track modified `ip_id` values, the sequence may reset in packets that are left untouched.
Windows replaces zero `ip_id`s with its own sequence, whereas other operating systems do not.
On any OS, it is possible to maintain a continuous linear `ip_id` order for a period of time using a mix of original and generated packets. To achieve this, apply the `ip_id=seq:ip_id_conn` policy to all desync functions that support `ipid`. For other payloads, use a combination of [send](#send) instances with the same `ip_id` policy and [drop](#drop) within a limited [`--out-range`](#in-profile-filters) interval. This should not be done indefinitely, as it increases CPU overhead.
### standard fooling
**fooling_options**
| Field | Description |
| :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ip_ttl | Set the TTL in the IPv4 header to the specified value. |
| ip6_ttl | Set the Hop Limit (HL) in the IPv6 header to the specified value. |
| ip_autottl | Set the TTL in the IPv4 header automatically using the `delta,min-max` pattern. If the TTL cannot be determined, the `ip_ttl` value is used if provided; otherwise, the TTL remains unchanged. |
| ip6_autottl | Set the Hop Limit (HL) in the IPv6 header automatically using the `delta,min-max` pattern. If the HL cannot be determined, the `ip6_ttl` value is used if provided; otherwise, the HL remains unchanged. |
| ip6_hopbyhop | Insert a "Hop-by-Hop Options" extension header. Defaults to 6 zeros, but a hex string can be specified. The length must be 6+N*8. |
| ip6_hopbyhop2 | Insert a second "Hop-by-Hop Options" extension header. |
| ip6_destopt | Insert a "Destination Options" extension header. Defaults to 6 zeros, but a hex string can be specified. The length must be 6+N*8. |
| ip6_destopt2 | Insert a second "Destination Options" extension header. |
| ip6_routing | Insert a "Routing Options" extension header. Defaults to 6 zeros, but a hex string can be specified. The length must be 6+N*8. |
| ip6_ah | Insert an "Authentication Header" (AH) extension header. Defaults to 2 zeros and 4 random bytes, but a hex string can be specified. The length must be 6+N*4. |
| tcp_seq | A positive or negative offset for the TCP Sequence Number. |
| tcp_ack | A positive or negative offset for the TCP Acknowledgment Number. |
| tcp_ts | A positive or negative offset for the TCP Timestamp. This only functions if the Timestamp option is already present. |
| tcp_md5 | Add a TCP MD5 Signature header if it is not already present. Defaults to random bytes, but a 16-byte hex string can be specified. |
| tcp_flags_set | Set TCP flags. Flags are provided as a comma-separated list: FIN, SYN, RST, PUSH, ACK, URG, ECE, CWR. |
| tcp_flags_unset | Clear (unset) TCP flags. Follows the same format as `tcp_flags_set`. |
| tcp_ts_up | Move the TCP Timestamp option to the very beginning of the options list, if present. |
| tcp_nop_del | Delete all NOP TCP options to free space in the header |
| fool | Name of the custom fooling function. It takes a dissect and a `fooling_options` table. |
IPv6 extension headers are added in the following order:
1. hopbyhop
2. hopbyhop2
3. destopt
4. routing
5. destopt2
6. ah
`tcp_ts_up` is a very strange phenomenon discovered during the testing of *nfqws2*.
It turns out that if a TCP timestamp option is present, Linux consistently drops packets with a valid SEQ but an invalid ACK-but only if the timestamp option comes first.
*nfqws1* did not respect the order of TCP options, resulting in the timestamp always being placed first.
Consequently, the old version worked stably, while the new one did not.
`tcp_ts_up` replicates the old behavior by moving the timestamp to the very top.
### standard ipfrag
The `ipfrag_options` contain only two standard parameters. The rest are handled by replaceable fragmenter functions, which have their own specific options.
**ipfrag_options**
| Field | Description |
| :-------------- | :------------------------------------------------------------------------------------------------------------------ |
| ipfrag | Name of the fragmenter function. If not specified, `ipfrag2` is used. The fragmenter returns an array of dissects (fragments). |
| ipfrag_disorder | Send fragments in reverse order. |
| ipfrag_pos_udp | (ipfrag2 fragmenter) UDP fragmentation position. Must be a multiple of 8; defaults to 8. |
| ipfrag_pos_tcp | (ipfrag2 fragmenter) TCP fragmentation position. Must be a multiple of 8; defaults to 32. |
| ipfrag_next | (ipfrag2 fragmenter) The "next protocol" type in the "fragment" extension header of the second fragment. |
### apply_ip_id
```
function apply_ip_id(desync, dis, ipid_options, def)
```
Applies the [ip_id policy](#standard-ipid) from `ipid_options` to the dissect `dis`.
If `dis` is `nil`, `desync.dis` is used.
If `ipid_options` is `nil`, `desync.arg` is used.
`def` contains the default assignment mode. If `nil`, "seq" is applied.
### apply_fooling
```
function apply_fooling(desync, dis, fooling_options)
```
Applies a set of L3/L4 header modifications ([fooling](#standard-fooling)), as described in `fooling_options`, to the dissect `dis`.
If `dis` is `nil`, `desync.dis` is used.
If `fooling_options` is `nil`, `desync.arg` is used.
### ipfrag2
```
function ipfrag2(dis, ipfrag_options)
```
The standard fragmenter function. It returns an array of two fragment dissects derived from the original dissect `dis`.
It is invoked via `rawsend_dissect_ipfrag` if the `ipfrag` field is missing in `ipfrag_options`.
You are unlikely to need to call this function manually.
If you need to split IP packets differently, you can create your own fragmenter by analogy and specify it in `ipfrag_options`.
In the case of IPv6, the fragment header is inserted after all `hopbyhop`, `routing`, and the first `destopt` headers. This constitutes the "unfragmentable part."
The fragment header follows, and everything after it is considered the "fragmentable part."
The unfragmentable part is transmitted in every fragment with modified fragment header fields; the rest is sliced across the data block following the fragment header according to the fragment offset.
According to the standard, for IPv6 fragmentation, the "next protocol" is only read from the first fragment (where offset=0).
In subsequent fragments, it does not have to match and is ignored. Manipulating the "next protocol" field of subsequent fragments is a well-known penetration attack technique described in various security articles, allowing one to bypass certain firewalls.
`ipfrag2` implements this capability for two fragments via the `ipfrag_next` parameter.
Some firewalls can only be bypassed using a larger number of fragments-this would require a custom fragmenter function.
### wssize_rewrite
```
function wsize_rewrite(dis, arg)
```
Rewrite `dis.tcp.th_win` and the scale factor in TCP options within the dissect, if present. Increasing the scaling factor is blocked.
- arg: wsize - window size
- arg: scale - scale factor
- returns true if any changes were made
### dis_reverse
```
function dis_reverse(dis)
```
Swap the source and destination IP addresses and ports, as well as the seq and ack numbers.
## IP addresses and interfaces
```
function update_ifaddrs()
```
A wrapper around the C function [get_ifaddrs](#obtaining-ip-addresses). It may happen that you need to find the addresses on each packet.
Calling get_ifaddrs every time is CPU intensive. Addresses and interfaces rarely change.
update_ifaddrs() maintains the cache, which is updated no more than once per second.
The result is stored in the global variable "ifaddrs".
```
function ip2ifname(ip)
```
Get the name of the interface on which the IP address is present using the ifaddrs cache. nil if not found.
## Sending
The following functions can accept multiple blocks of the options described above, each represented by the `options` parameter field.
All functions utilize [options.reconstruct](#standard-reconstruct) and [options.rawsend](#standard-rawsend). These correspond to the parameter format of the C function [rawsend_dissect](#rawsend).
### rawsend_dissect_ipfrag
```
function rawsend_dissect_ipfrag(dis, options)
```
Sends dissect `dis` with IP fragmentation as specified in `options.ipfrag`. If omitted, it is sent without fragmentation.
Uses a custom fragmenter function if `options.ipfrag.ipfrag` is provided.
Sends fragments in reverse order if `options.ipfrag.ipfrag_disorder` is specified.
### rawsend_dissect_segmented
```
function rawsend_dissect_segmented(desync, dis, mss, options)
```
Sends dissect `dis` with automatic TCP segmentation based on MSS, applying `options.fooling` and `options.ipid`.
The `ipid` is applied to each fragment. Segmentation is not possible for UDP and is not performed.
- if dis is nil, desync.dis is used.
- if mss is nil, desync.tcp_mss is used.
- if options is nil, options are created from desync.arg
### rawsend_payload_segmented
```
function rawsend_payload_segmented(desync, payload, seq, options)
```
Constructs a temporary dissect based on `desync.dis`, with optional payload replacement and optional `seq` offset, applying `options`, and sends it via `rawsend_dissect_segmented`.
The MSS is taken from `desync.tcp_mss`.
If `options` are missing, they are created based on `desync.arg`.
Standard `options` are formed as follows:
- `ipfrag`, `ipid`, and `fooling` take their values from `desync.arg`.
- `rawsend`: `repeats` is taken from `desync.arg.repeats`; `ifout` and `fwmark` are taken from `desync.arg` if present, otherwise from `desync` (the context passed to the desync function).
- `reconstruct`: only `desync.arg.badsum` is used; other options are ignored.
## Standard direction and payload filters
```
function direction_check(desync, def)
function direction_cutoff_opposite(ctx, desync, def)
```
The direction filter is a string ("in", "out", or "any") passed via `desync.arg.dir`. If the argument is missing, the `def` value is used.
- `direction_check` verifies if the current direction matches the filter.
- `direction_cutoff_opposite` performs an [instance cutoff](#instance_cutoff) on the current direction if it does not match the filter.
```
function payload_match_filter(l7payload, l7payload_filter, def)
function payload_check(desync, def)
```
These functions operate on a string representing a comma-separated list of [payloads](#protocol-detection). All empty packets have the payload `empty`, and unknown ones are `unknown`. Special values include `all` and `known`. `all` means any payload; `known` means anything that is not `unknown` or `empty`. A `~` prefix at the beginning denotes logical inversion (non-match).
- `payload_match_filter` checks if `l7payload` matches the `l7payload_filter` list, or `def` if `l7payload_filter` is `nil`. If both are `nil`, the list defaults to "known".
- `payload_check` calls `payload_match_filter(desync.l7payload, desync.arg.payload, def)`.
## Working with multi-packet payloads
Typically, operations are performed on the entire [reasm](#handling-multi-packet-payloads) rather than its individual parts. This is the purpose of reassembly: to avoid dealing with separate packets and instead process the entire message at once.
The standard scenario involves processing after receiving the first part of a [replay](#handling-multi-packet-payloads) and either ignoring or dropping the remaining parts. The choice between ignoring or dropping may depend on the success of actions involving [reasm](#handling-multi-packet-payloads). For example, whether or not a segmented [reasm](#handling-multi-packet-payloads) was successfully sent. If successful, all other parts must be dropped; otherwise, they will be sent as duplicates in the original segmentation. If an error occurs and the segmented packets could not be sent, dropping the rest would prevent the full message from reaching the recipient, leading to retransmissions. In such cases, it is better to leave them as is - this way nothing breaks.
```
function replay_first(desync)
function replay_drop_set(desync, v)
function replay_drop(desync)
```
- `replay_first` returns true if the current dissect is not a [replay](#handling-multi-packet-payloads) or is its first part.
- `replay_drop_set` marks a boolean flag `v` in `desync.track.lua_state` indicating whether subsequent parts of a [replay](#handling-multi-packet-payloads) should be dropped.
- `replay_drop` returns true if the current part of the [replay](#handling-multi-packet-payloads) needs to be dropped. If the part is the last one, it automatically clears the flag.
These functions work correctly with both [replays](#handling-multi-packet-payloads) and regular dissects. For regular dissects, `replay_first` is always true, `replay_drop_set` does not change the flag, and `replay_drop` is always false.
## Orchestration
This group includes functions that support the orchestration and shimming processes.
Shims are duplicates of C code functions for situations where we lack the `ctx` context required to communicate with the C code.
Once orchestration begins, all subsequent instances are called by the orchestrator or nested orchestrators.
The last available `ctx` is the one from the primary orchestrator. If this `ctx` is passed to other instances, they will act on behalf of the orchestrator rather than themselves; therefore, `ctx=nil` should be passed to them.
After an [execution plan](#execution_plan) is canceled, the C code stops servicing subsequent instances and does not provide a `ctx` for them. Consequently, if execution needs to continue in the standard style, duplicate mechanisms for [instance cutoff](#instance_cutoff) and range/payload filters are required.
To ensure `--lua-desync` functions work transparently under orchestration, standard shims must be used instead of direct C function calls that require a `ctx`. For nested orchestrators to function correctly, you must follow the standard scheme of storing the [execution plan](#execution_plan) in `desync.plan` and use the helper functions described below.
It may be difficult to understand how orchestration works from a dry description alone. It is recommended to study the code of actual orchestrators and use this description to clarify the meaning of specific actions.
### instance_cutoff_shim
```
function instance_cutoff_shim(ctx, desync, dir)
```
Performs a standard [instance cutoff](#instance_cutoff) in the direction `dir` if `ctx` is present; otherwise, it performs the cutoff via a duplicate mechanism whose state is stored in `desync.track.lua_state`. `dir = true` for the outgoing direction, `dir = false` for incoming, and `dir = nil` for both directions.
### cutoff_shim_check
```
function cutoff_shim_check(desync)
```
Checks the [instance cutoff](#instance_cutoff) state for `desync.func_instance` in the `desync.outgoing` direction.
### apply_arg_prefix
```
function apply_arg_prefix(desync)
```
Performs substitution of argument values from `desync.arg` that start with `%` and `#`.
### apply_execution_plan
```
function apply_execution_plan(desync, instance)
```
Copies the instance identification and its arguments from an [execution plan](#execution_plan) `instance` into the desync object, thereby recreating the desync state as if the `instance` were called directly by C code.
The [execution plan](#execution_plan) is provided by the C function `execution_plan()` as an array of `instance` elements.
### verdict_aggregate
```
function verdict_aggregate(v1, v2)
```
Aggregates verdicts v1 and v2. VERDICT_MODIFY overrides VERDICT_PASS, while VERDICT_DROP overrides both.
### plan_instance_execute
```
function plan_instance_execute(desync, verdict, instance)
```
Executes an [execution plan](#execution_plan) `instance`, taking into account the [instance cutoff](#instance_cutoff) and standard [payload](#in-profile-filters) and [range](#in-profile-filters) filters.
Returns the aggregation of the current verdict and the `instance` verdict.
### plan_instance_pop
```
function plan_instance_pop(desync)
```
Retrieves, removes, and returns the first element of the [execution plan](#execution_plan) from `desync.plan`. Returns `nil` if there are no elements.
### plan_clear
```
function plan_clear(desync)
```
Clears the [execution plan](#execution_plan) in `desync.plan` by removing all `instance` elements.
### orchestrate
```
function orchestrate(ctx, desync)
```
If the orchestrator is the primary one (i.e., `ctx` is present), it retrieves the [execution plan](#execution_plan), places it into `desync.plan`, and then executes `execution_plan_cancel()`.
If `ctx=nil`, it does nothing, assuming the plan is already in `desync.plan`.
### replay_execution_plan
```
function replay_execution_plan(desync)
```
Executes the entire [execution plan](#execution_plan) from `desync.plan`, respecting the [instance cutoff](#instance_cutoff) and standard [payload](#in-profile-filters) and [range](#in-profile-filters) filters.
# zapret-antidpi.lua DPI attack program library
## Standard parameter sets
Many functions accept standard sets of arguments classified by their purpose.
Additional filters for direction and payload within the anti-DPI functions are primarily implemented as a safety measure against poorly written command-line options. This "foolproof" protection ensures it is difficult to accidentally trigger flooding, as happened in winws1 with the `--dpi-desync-any-protocol` option.
Another goal is to enable filtering for protocols that the C code is unaware of and that are detected by Lua detectors, such as [detect_payload_str](#detect_payload_str).
By default, nfqws2 blocks incoming traffic (`--in-range=x`), allows unlimited outgoing traffic (`--out-range=a`), and passes all payloads (`--payload=all`), which matches the behavior of nfqws1 with the `--dpi-desync-any-protocol` option. In nfqws1, all attacks were hardcoded into the C code, so it was known which techniques worked with which payloads. Some required any packets (including empty ones), while others required only TLS Hellos or HTTP requests.
nfqws2 has no knowledge of what `--lua-desync` functions require. Therefore, filtering by direction and payload type is entirely your responsibility. By default, only incoming traffic is blocked because it is rarely used; if the user fails to specify a restriction, all traffic would be passed to Lua functions, unnecessarily taxing the CPU with gigabytes of downloaded data.
### standard direction
Direction filter. In most functions using a direction filter, the default value is "out", though some default to "any". Direction filtering can also be implemented using C code via [`--in-range` and `--out-range`](#in-profile-filters).
**standard direction**
| Field | Description |
| :---- | :-------------------------------------------------------------------------- |
| dir | in - incoming direction
out - outgoing direction
any - any direction |
### standard payload
The payload filter accepts a list of [payload types](#protocol-detection). All empty packets are assigned the `empty` payload type, while unrecognized ones are labeled `unknown`. Special values include `all` and `known`: `all` matches any payload, while `known` matches anything that is neither `unknown` nor `empty`.
**standard payload**
| Field | Description |
| :------ | :----------------------------------------------------------------------------------- |
| payload | A comma-separated list of allowed payloads. A leading `~` indicates inversion (NOT). |
## Base functions
### drop
```
function drop(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- By default, `payload=all` and `direction=any`, meaning it drops everything.
Issues a `VERDICT_DROP` if the filter conditions are met.
### send
```
function send(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- Default `ip_id` mode is `none`.
Sends the current dissect with optional modifications applied.
### pktmod
```
function pktmod(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
Applies modifications to the current dissect without sending it or issuing a verdict.
## HTTP fooling
### http_hostcase
```
function http_hostcase(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: spell - the exact spelling of the header. Defaults to "host".
Changes the case of the `Host:` HTTP header.
### http_domcase
```
function http_domcase(ctx, desync)
```
- arg: [standard direction](#standard-direction)
Changes the case of the domain within the `Host:` header. The case alternates for every character: `rUtRaCkEr.oRg`.
### http_methodeol
```
function http_methodeol(ctx, desync)
```
- arg: [standard direction](#standard-direction)
Inserts `\r\n` before the method, stripping the last two characters from the `User-Agent:` header content. This only works with Nginx; it breaks other servers.
If used with other http tampering functions should be the last !
### http_unixeol
```
function http_unixeol(ctx, desync)
```
- arg: [standard direction](#standard-direction)
Replaces the `0D0A` line endings with `0A`. The difference in length is compensated for by adding spaces to the end of the `User-Agent` header. This only works with Nginx; it breaks other servers.
## Window size replacement
### wsize
```
function wsize(ctx, desync)
```
- arg: wsize - TCP window size.
- arg: scale - scaling factor. Replaced in the TCP option if present. Only reduction is allowed; increasing the factor is blocked.
Changes `tcp.th_win` and/or the scaling factor in the TCP SYN,ACK packet, then executes an [instance cutoff](#instance_cutoff). If the modification is successful, it sets `VERDICT_MODIFY`.
The goal of this technique is to spoof the window size from the client or server side so that the peer responds by sending the next packet in parts, as the full packet would not fit into the spoofed window size. This may lead to performance slowdowns. This is an obsolete technique; it is recommended to use it only from the server side as a last resort for clients that do not employ any other bypass methods. From the client side, TCP segmentation techniques are preferred as they avoid speed penalties and offer more flexibility.
### wssize
```
function wssize(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: wsize - TCP window size.
- arg: scale - scaling factor. Replaced in the TCP option if present. Only reduction is allowed; increasing the factor is blocked.
- arg: forced_cutoff - a comma-separated list of payload types that trigger an [instance cutoff](#instance_cutoff) upon receipt. If `wssize` needs to be applied indefinitely, you can set `forced_cutoff=no` (using a non-existent payload type that will never occur).
Modifies `tcp.th_win` and/or the scaling factor in TCP options for all TCP packets in the flow's direction until the "cutoff" condition is met.
If a modification is performed, it returns `VERDICT_MODIFY`.
The "cutoff" occurs upon receiving any packet with data (if the `forced_cutoff` argument is not specified) or upon receiving one of the payloads specified in the `forced_cutoff` argument.
In this case, [instance cutoff](#instance_cutoff) is executed.
The goal of this technique is to force the server to fragment its responses while the DPI is inspecting them (TLS 1.2).
The idea is to keep the server "on its toes" by making it believe the client cannot receive large TCP segments, forcing it to slice its own responses-but only until the critical inspection phase has passed.
After that, the restriction must be lifted; otherwise, it will lead to a catastrophic drop in speed, potentially down to dial-up levels.
It reduces speed in any case. This is a phase-zero technique; when used with hostlists, it can only be applied if `--ipcache-hostname` is enabled.
When using hostlists, it may be necessary to duplicate this in a separate profile that is activated before the hostname is identified.
In this case, it will always be applied without checking the hostlist and will always reduce speed.
Typical parameters: `wsize=1:scale=6`. Recommended for use only when no alternatives are available.
## Fakes
Fakes are individual packets containing false information that the DPI should accept, but the server should not.
Fakes can be direct or hidden.
Direct fakes are standalone packets, while hidden fakes are part of original modified packets or groups of packets.
Direct fakes always require some form of header distortion so that the payload does not reach the server application; otherwise, it will break the connection. Without [fooling](#standard-fooling), a fake must replicate part of the original information to prevent the server from receiving false data.
Hidden fakes are ignored by the server due to the characteristics of the packets they are part of.
### syndata
```
function syndata(ctx, desync)
```
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: blob - a [blob](#passing-blobs) containing the fake payload. It must fit into a single packet; segmentation is not possible.
- arg: tls_mod - apply the specified [tls_mod](#tls_mod) to the blob payload.
The function adds a payload to the TCP SYN packet, applies modifications to it, and sends it instead of the original, issuing a `VERDICT_DROP`.
If a non-SYN packet passes through, [instance cutoff](#instance_cutoff) is executed.
Thus, the manipulation is performed on all SYN retransmissions, after which the function stops.
It is a phase-zero strategy that works with hostlists only in `--ipcache-hostname` mode.
### tls_client_hello_clone
```
function tls_client_hello_clone(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: blob - the name of the blob that receives the result
- arg: fallback - the name of the blob to be copied to the result if the payload is of the wrong type or invalid
- arg: sni_del_ext - delete the SNI extension; other parameters are ignored
- arg: sni_del - delete all hosts
- arg: sni_snt - replace the "server name type" field for all existing hosts
- arg: sni_snt_new - the "server name type" field for newly added hosts
- arg: sni_first - add a host to the beginning of the list
- arg: sni_last - add a host to the end of the list
Prepares a blob with the specified name in the desync table, filled with the result of modifying the current [reasm](#handling-multi-packet-payloads).
Works only with TCP and the `tls_client_hello` payload. If an SNI modification is specified and the SNI extension is missing, it is added to the beginning of the extensions list.
Order of operations:
1. sni_del_ext. All other SNI operations become irrelevant and will not be executed.
2. sni_del
3. sni_snt
4. sni_first
5. sni_last
This function does not affect traffic by itself; it only prepares data for other functions.
### fake
```
function fake(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: blob - a blob containing the fake payload. It can be of any length-segmentation is performed automatically.
- arg: optional - abort the operation if the blob is missing.
- arg: tls_mod - apply the specified [tls_mod](#tls_mod) to the blob payload.
- default payload filter - "known"
This is a direct fake-a separate packet or group of packets. The function does not issue a verdict and does not block the transmission of the original packet.
### rst
```
function rst(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: rstack - send RST,ACK instead of RST.
- default payload filter - "known"
Sends an empty TCP packet with RST or RST+ACK flags. The function does not issue a verdict and does not block the transmission of the original packet.
## TCP segmentation
### multisplit
```
function multisplit(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a comma-separated list of [markers](#markers) representing split points. Defaults to "2".
- arg: seqovl - a number representing the offset relative to the current sequence to create an additional segment part that extends to the left beyond the TCP window boundary.
- arg: seqovl_pattern - the [blob](#passing-blobs) used to fill the seqovl. Defaults to 0x00.
- arg: blob - replace the current payload with the specified [blob](#passing-blobs).
- arg: optional - abort the operation if a blob is specified but missing. If seqovl_pattern is specified but missing, use the 0x00 pattern.
- arg: nodrop - do not issue a VERDICT_DROP.
- default payload filter - "known"
Multisplit implements sequential segmentation of the current dissect or [reassembly](#handling-multi-packet-payloads) with splits at positions defined by the [marker](#markers) list. It optionally supports replacing a data block with an arbitrary [blob](#passing-blobs) and the seqovl technique.
A VERDICT_DROP is issued after all segments are successfully sent, unless "nodrop" is specified.
If [replaying](#handling-multi-packet-payloads) delayed packets and [reassembly](#handling-multi-packet-payloads) is present, desync.reasm_data is used instead of desync.dis.payload. Splitting occurs only during the replay of the first part of the [reassembly](#handling-multi-packet-payloads); for the remaining parts, a VERDICT_DROP is issued if the transmission was successful and "nodrop" is not specified. Since the entire [reassembly](#handling-multi-packet-payloads) has already been sent in segments, there is no need to re-send its original parts.
It can be used to send arbitrary data, including fakes, by replacing the current payload with an arbitrary blob.
There is no need to worry about part sizes or MTU fitting-additional automatic segmentation by MSS is applied.
seqovl can only be a number; markers are not supported. It is applied to the first segment being split. The seqovl_pattern is prepended to the first segment's payload according to the seqovl size, and tcp.th_seq is decreased by seqovl. This creates a data block on the left that extends beyond the TCP window, causing the server to ignore it, while the part within the TCP window is accepted.
seqovl is essentially a method for interleaving fake and real data, a tool for creating hidden fakes within real TCP segments. If a DPI does not track sequence numbers, it may ingest the entire transmitted segment and fall for the false information at the beginning, which the server will not actually accept.
A particular advantage of seqovl is that it eliminates the need for [fooling](#standard-fooling). The server accepts only a portion of the segment by manipulating sequence numbers, rather than by modifying IP or TCP header elements, which could lead to total rejection.
### multidisorder
```
function multidisorder(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a comma-separated list of [markers](#markers) - split points. Default is "2".
- arg: seqovl - marker - an offset relative to the current sequence to create an additional part of the segment extending to the left.
- arg: seqovl_pattern - the [blob](#passing-blobs) used to fill the seqovl. Default is 0x00.
- arg: blob - replace the current payload with the specified [blob](#passing-blobs).
- arg: optional - skip the operation if a blob is specified but missing. If seqovl_pattern is specified but missing, use the 0x00 pattern.
- arg: nodrop - disable issuing a VERDICT_DROP.
- default payload filter - "known"
Similar to [multisplit](#multisplit), but segments are sent in reverse order-from the last to the first.
The seqovl technique works differently in this case. It is applied to the second segment in the original sequence (the penultimate one sent). seqovl can be a marker. For example, you can set a split at "midsld" and set seqovl to "midsld-1". seqovl must be smaller than the first segment in the original sequence (the last one sent); otherwise, the condition is recognized as invalid and seqovl is cancelled.
The purpose of seqovl in the disorder variant is to rewrite the socket buffer on the receiving end. A TCP socket delivers data to the application sequentially, in the order of its original transmission. If a "forward" segment arrives first that does not form a continuous sequence with already accepted data, the information is held in the buffer without being released to the application. If an overlapping sequence segment arrives later, the information from it overwrites what is already in the buffer. All systems behave this way except for Windows; therefore, this technique does not work on Windows servers. Windows preserves the old information.
A seqovl_pattern of size seqovl (the result of marker resolution) is prepended to the penultimate segment sent (the 2nd in the original order), and tcp.th_seq is decreased by seqovl.
The last segment sent (the 1st in the original order) is sent unchanged, overwriting the fake data from the seqovl_pattern in the socket buffer with real data. The continuous stream sequence is restored, and the data is passed to the application socket.
### multidisorder_legacy
```
function multidisorder_legacy(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a comma-separated list of [markers](#markers) - split points. Default is "2".
- arg: seqovl - marker - an offset relative to the current sequence to create an additional part of the segment extending to the left.
- arg: optional - skip the operation if a blob is specified but missing. If seqovl_pattern is specified but missing, use the 0x00 pattern.
- arg: seqovl_pattern - the [blob](#passing-blobs) used to fill the seqovl. Default is 0x00.
A multidisorder implementation fully compatible with nfqws1.
The new multidisorder works with the entire [reasm](#handling-multi-packet-payloads) (reassembly), whereas the legacy nfqws1 version works with individual [replay](#handling-multi-packet-payloads) segments. Consequently, the sequence of parts will differ when handling multi-packet requests.
In the new version, the original segmentation is not preserved. If any split segment exceeds the MSS, it is further divided by the MSS and sent in ascending sequence order. In the legacy version, the original segmentation was preserved. Split points were normalized based on the offset of each individual [reasm](#handling-multi-packet-payloads) part. Segments were sent in reverse order only within each part, while the subsequent part followed with its original sequence increment. Similarly, seqovl was normalized and applied only within the original segment where the normalized position was located.
### fakedsplit
```
function fakedsplit(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a single [marker](#маркеры) acting as the split point. Defaults to "2".
- arg: seqovl - numeric value - an offset relative to the current sequence to create an additional segment part that extends to the left of the TCP window boundary.
- arg: seqovl_pattern - the [blob](#passing-blobs) used to fill the seqovl. Defaults to 0x00.
- arg: blob - replaces the current payload with the specified [blob](#passing-blobs).
- arg: optional - skip the operation if the blob is specified but missing. If seqovl_pattern is specified but missing, use the 0x00 pattern.
- arg: nodrop - prevents issuing a VERDICT_DROP.
- arg: nofake1, nofake2, nofake3, nofake4 - skip sending specific fake packets.
- arg: pattern - the [blob](#passing-blobs) used to fill the fake segments. Defaults to 0x00.
- Default payload filter: "known"
The function operates similarly to multisplit with a single split point, but it interleaves fake packets between real segments. Fake packets match the size of the segments being sent and are generated based on a pattern with an offset corresponding to the TCP sequence offset of the segment relative to the first one.
Fake packets require [fooling](#standard-fooling) to ensure they are not accepted by the server.
Transmission sequence:
1. Fake of the 1st part. (fake1)
2. Real 1st part.
3. Fake of the 1st part. (fake2)
4. Fake of the 2nd part. (fake3)
5. Real 2nd part.
6. Fake of the 2nd part. (fake4)
The purpose of this technique is to confuse the DPI regarding which data is original and which is fake. Since the segments are identical in size - one containing junk and the other containing real data - the DPI cannot determine which to process. Both appear as retransmissions with identical sequences and sizes.
- Only `fooling_opts.tcp_ts_up` is applied to the original segments; `reconstruct_opts` are not used.
- Both `fooling_opts` and `reconstruct_opts` are applied in full to the fake segments.
- `ipid_opts` and `rawsend_opts` are applied to both fakes and originals. `ipfrag_opts` are not used for either fakes or originals.
If the transmission is successful, a VERDICT_DROP is issued unless "nodrop" is specified.
The [blob](#passing-blobs) parameter allows replacing the current payload with an arbitrary [blob](#passing-blobs), enabling the transmission of any compatible payload using the same segmentation.
### fakeddisorder
```
function fakeddisorder(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a single [marker](#markers) - the split point. Defaults to "2".
- arg: seqovl - [marker](#markers) - offset relative to the current sequence to create an additional segment part extending to the left.
- arg: seqovl_pattern - [blob](#passing-blobs) used to fill the seqovl. Defaults to 0x00.
- arg: blob - replace the current payload with the specified [blob](#passing-blobs).
- arg: optional - abort the operation if a blob is specified but missing. If seqovl_pattern is specified but missing, use the 0x00 pattern.
- arg: nodrop - skip issuing a VERDICT_DROP.
- arg: nofake1, nofake2, nofake3, nofake4 - skip sending specific fake packets.
- arg: pattern - [blob](#passing-blobs) used to fill the fake segments. Defaults to 0x00.
- default payload filter - "known"
This function operates similarly to multidisorder with a single split point, but it intersperses fake segments among the real ones. The fakes match the size of the transmitted parts and are generated based on the pattern, with an offset corresponding to the TCP sequence offset of the transmitted part relative to the first one.
[Fooling](#standard-fooling) is required for fakes to prevent them from being accepted by the server.
Transmission sequence:
1. Fake of the 2nd part. (fake1)
2. Real 2nd part.
3. Fake of the 2nd part. (fake2)
4. Fake of the 1st part. (fake3)
5. Real 1st part.
6. Fake of the 1st part. (fake4)
In addition to confusing the DPI with real and fake segments, the sequence itself is also obfuscated.
- Only fooling_opts.tcp_ts_up is applied to the originals. reconstruct_opts are not applied.
- fooling_opts and reconstruct_opts are applied to the fakes in full.
- ipid_opts and rawsend_opts are applied to both fakes and originals. ipfrag_opts are not used for either fakes or originals.
If the transmission is successful, a VERDICT_DROP is issued unless "nodrop" is specified.
The [blob](#passing-blobs) option allows replacing the current payload with an arbitrary [blob](#passing-blobs), thereby sending any compatible payload using the same splitting logic.
### hostfakesplit
```
function hostfakesplit(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: host - template for [fake host generation](#genhost) - random.template
- arg: midhost - [marker](#markers) for an additional split of the segment containing the real host.
- arg: disorder_after - [marker](#markers) for an additional split of the final real part and sending segments in reverse order.
- arg: nofake, nofake2 - skip sending specific fake packets.
- arg: blob - replace the current payload with the specified [blob](#passing-blobs).
- arg: optional - abort the operation if the blob is specified but missing.
- arg: nodrop - skip issuing a VERDICT_DROP.
- default payload filter - "known"
This is a specialized "splitter" that intersperses fakes for payloads containing a hostname, such as http_req and tls_client_hello.
The two primary split points are the beginning of the hostname - the [marker](#markers) "host" - and the end of the hostname - the [marker](#markers) "endhost". Additional optional split points include the [marker](#markers) midhost (must be within the host..endhost range) and the [marker](#markers) disorder_after (must be greater than endhost). When splitting by disorder_after, the parts are sent in reverse order.
[Fooling](#standard-fooling) is required for fakes to prevent them from being accepted by the server.
Transmission sequence:
1. Real part before host
2. Fake host..endhost-1 (fake1)
3. Real part host..endhost, or 2 parts: host..midhost-1, midhost..endhost-1
4. Fake host..endhost-1 (fake2)
5. Real part after host, or 2 parts: disorder_after..-1, endhost..disorder_after-1
- Only `fooling_opts.tcp_ts_up` is applied to originals. `reconstruct_opts` are not applied.
- `fooling_opts` and `reconstruct_opts` are applied in full to fakes.
- `ipid_opts` and `rawsend_opts` apply to both fakes and originals. `ipfrag_opts` are not used for either fakes or originals.
In case of successful transmission, a `VERDICT_DROP` is issued unless "nodrop" is specified.
The [blob](#passing-blobs) allows replacing the current payload with an arbitrary [blob](#passing-blobs), thereby sending any compatible payload with the same splitting.
### tcpseg
```
function tcpseg(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: pos - a list of two [markers](#markers) defining the boundaries of the TCP segment
- arg: seqovl - a number representing the offset relative to the current sequence to create an additional segment part that extends to the left beyond the TCP window boundary
- arg: seqovl_pattern - a [blob](#passing-blobs) used to fill the seqovl. Defaults to 0x00
- arg: blob - replace the current payload with the specified [blob](#passing-blobs)
- arg: optional - skip the operation if a blob is specified but missing. If `seqovl_pattern` is specified but missing, use the 0x00 pattern.
- default payload filter - "known"
Sends a part of the current dissect, [reasm](#reassembly-features), or an arbitrary blob, limited by two `pos` [markers](#markers) with optional application of the `seqovl` technique in the same way as in [multisplit](#multisplit). Additional segmentation is performed automatically if the MSS is exceeded.
In the case of [reasm](#reassembly-features), it only works when receiving its first part (as it operates on the reasm as a whole, not its individual parts).
No verdict is issued.
Using `tcpseg`, you can perform `seqovl` without segmentation by using markers "0,-1". To replace the current dissect, it can be combined with `drop`.
### oob
```
function oob(ctx, desync)
```
- arg: [standard fooling](#standard-fooling)
- arg: [standard ipid](#standard-ipid)
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: char - one OOB character
- arg: byte - one OOB byte 0..255
- arg: urp - urgent pointer [marker](#markers), "b" or "e". "b" is default.
Function intercepts TCP handshake shifting sequence numbers one byte to the left then inserts OOB byte into the first non-empty payload.
After it's done it executes [instance cutoff](#instance_cutoff).
Target OS throws away OOB byte from the stream but DPI may analyze message with OOB byte as it's part thus breaking the message.
- OOB is obsolete but still supported in most OS. There are two RFCs. One assumes that th_urp points to the OOB byte,
another one - to the next byte. Therefore, the value th_urp=0 is invalid according to one of the standards, but it can still work.
To enable it, specify "urp=b".
- Marker "urp" defines 0-based position of the OOB byte. Resulting th_urp , except the "b" case, is set incremented by 1. This is what most of the modern OS expect.
- "urp=e" inserts an OOB byte after the very last byte of the payload - generally useless for DPI bypass, since DPI gets the entire original message.
- For protocols in which the server initially waits for a client request, `--in-range=-s1` is enough. In Windows `--wf-tcp-in` is not needed. Automatically intercepted incoming packets with the SYN flag are sufficient.
- For protocols in which the server sends data before the first message from client all incoming packets before that message should be intercepted. In Windows `--wf-tcp-in` is required.
- Cannot be filtered by payload because after the start it's not possible to stop and not to insert the byte. Inserting a byte without OOB breaks the data.
- Hostlist filtering is not possible.
- oob is "lasting" desync. If profile switch occurs before oob is finished it must be duplicated to that profile or TCP will break because of sequence desync.
- Can't work with functions that resend modified payload. multisplit, multidisorder, fakedsplit, fakeddisorder, etc will send duplicates without OOB.
- If the payload is [multi-segment](#handling-multi-packet-payloads), the entire [reasm](#handling-multi-packet-payloads) is sent. OOB is inserted into the segment where urp hits.
In this segment the th_urp is normalized by segment offset, the TH_URG flag is set. The rest of the parts are sent as is. The function drops the whole replay then [cuts itself off](#instance_cutoff).
## UDP Fooling
There are far fewer options for UDP than for TCP due to the simplicity of the protocol. There isn't much that can be done with it.
[Fakes](#fake) can help against stateful DPI, but they won't help against stateless ones. [IP-level fragmentation](#standard-ipfrag) might help.
For IPv6, [extension headers](#standard-fooling) may work.
Aside from that, the only option is distorting the payload itself. Not all programs will tolerate data distortion; many will simply discard corrupted packets. However, there are some where it is clear what can be "tweaked."
### udplen
```
function udplen(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: [standard payload](#standard-payload)
- arg: min - do not touch packets with an L4 payload length smaller than this
- arg: max - do not touch packets with an L4 payload length larger than this
- arg: increment - how much to increase (+) or decrease (-) the L4 payload length
- arg: pattern - the [blob](#passing-blobs) used to fill the end of the packet when increasing the length
- arg: pattern_offset - initial offset within the pattern
- default payload filter - "known"
The function increases or decreases the length of the UDP L4 payload. When decreasing, part of the information is truncated and lost; when increasing, the extra space is filled with the `pattern`. UDP segmentation is impossible - if the MTU or PMTU is exceeded, the packet will be fragmented by OS on IP level. An error in case of exceeding the MTU will only be reported on Linux; other systems will silently fail to send the packet (windivert and ipdivert have no means of error detection).
### dht_dn
```
function dht_dn(ctx, desync)
```
- arg: [standard direction](#standard-direction)
- arg: dn - the number N following 'd' in a DHT message
DHT uses the bencode format for transmitting messages. 'd' represents the directory data type. DHT messages typically start with 'd1' or 'd2' and end with 'e' (end). Some DPIs have these exact signatures hardcoded-only 'd1' or 'd1'+'d2'. However, one can use 'd3', 'd4', etc., if the content is edited correctly without violating the bencode format. This is what this function does. It only works on payloads with the "dht" type.
## Other Functions
### synack
```
function synack(ctx, desync)
```
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
Sends a SYN,ACK packet before the SYN to confuse the DPI regarding the TCP connection direction. This attack is referred to in literature as "TCB turnaround." It breaks NAT-usage through NAT is impossible. Usage on transit traffic requires nftables and POSTNAT mode. After a non-SYN packet passes, it performs an [instance cutoff](#instance_cutoff). It does not issue a verdict.
### synack_split
```
function synack_split(ctx, desync)
```
- arg: [standard ipfrag](#standard-ipfrag)
- arg: [standard reconstruct](#standard-reconstruct)
- arg: [standard rawsend](#standard-rawsend)
- arg: mode - "syn", "synack", or "acksyn"
This technique is intended for servers. In literature, it is known as "TCP split handshake." It replaces the outgoing SYN,ACK packet from the server with a SYN, two SYN + ACK packets, or two ACK + SYN packets. If the transmission is successful, it issues a VERDICT_DROP. After a non-SYN,ACK packet passes, it performs an [instance cutoff](#instance_cutoff).
Many DPIs expect a standard response to a SYN in the form of a SYN,ACK. In reality, the response to the SYN becomes another SYN, and then the client sends a SYN,ACK to the server. Consequently, the DPI loses track of which side is the client and which is the server, causing the inspection algorithm to fail. The attack may work even if the client does nothing to bypass blocking. It can be used in conjunction with client-side techniques for targeted TCP bypassing.
# zapret-auto.lua automation and orchestration library
The standard order of instance application is linear-from left to right, taking into account [in-profile filters](#in-profile-filters) and [instance cutoff](#instance_cutoff). nfqws2 provides no other options by default.
Of course, you can write your own Lua function that does what is needed when it is needed. However, you would have to reinvent the wheel, duplicate code, or worse-patch standard antidpi functions to add your own features and then maintain them yourself.
The essence of orchestration mechanisms is to separate the control logic from the logic of the actual actions. This way, nothing needs to be patched, and if you do write your own functions, you only need to write the control algorithm itself without mixing it with action algorithms.
Orchestration is inextricably linked to the concept of an [execution plan](#execution_plan). It includes a list of instances that need to be called sequentially with their parameters and [filters](#in-profile-filters). A basic linear orchestrator is built into the C code, but this role can also be taken over by a Lua function where any logic can be programmed.
For example, you can create automatic strategies - if one doesn't work, use another. The C code has similar logic only within the [automatic hostlist](#filtering-by-lists) mechanism, but it does not implement dynamic strategy switching.
## State storage
Automation logic typically spans across packets and relies on conntrack. Flow states are stored using `desync.track.lua_state` elements.
Other information spans across flows and is tied to the host. Global tables indexed by keys are used to store this information.
### automate_conn_record
```
function automate_conn_record(desync)
```
Returns a table serving as automation state storage bound to the stream. Automation functions can utilize any fields within it.
### standard_hostkey
```
function standard_hostkey(desync)
```
- arg: reqhost - require a hostname; do not operate via IP.
- arg: nld - the domain level number to which the hostname is truncated. If not specified, no truncation occurs.
A standard host-key generator. This function calculates a key string associated with the hostname or the IP address if the hostname is unavailable. If it fails to generate a key, it returns `nil`.
### automate_host_record
```
function automate_host_record(desync)
```
- arg: key - a key within the global `autostate` table. If not specified, the current instance name is used as the key.
- arg: hostkey - the name of the host-key generator function. If not specified, [standard_hostkey](#standard_hostkey) is used.
Returns a table serving as automation state storage bound to a specific host or IP address. It utilizes two keys: `key` and `hostkey`. The resulting location is `autostate.key.hostkey`. If the `hostkey` cannot be retrieved, it returns `nil`.
## Handling successes and failures
A simple, though not entirely precise, explanation of "success" and "failure" is "the site opens" versus "the site does not open." In automation logic, these states are abstract. Any process can result in a success or a failure. Detecting these states is necessary for tracking failures via a counter. The counter increments on failure and resets on success. When a target threshold is reached, a flag is returned, allowing external logic to perform an action-such as switching strategies.
### automate_failure_counter
```
function automate_failure_counter(hrec, crec, fails, maxtime)
```
- hrec - [host storage](#automate_host_record)
- crec - [stream storage](#automate_conn_record)
- fails - the target number of failures.
- maxtime - the time in seconds after which, since the last failure, the next failure restarts the count from scratch.
Returns `true` if the counter reaches the `fails` value. The counter is reset upon returning `true`.
### automate_failure_counter_reset
```
function automate_failure_counter_reset(hrec)
```
- hrec - [host storage](#automate_host_record)
Resets the failure counter value.
## Success and failure detection
Success and failure detectors are swappable functions that take `desync` and [crec](#automate_conn_record) as parameters and return `true` if a success or failure is detected.
### automate_failure_check
```
function automate_failure_check(desync, hrec, crec)
```
- hrec - [host storage](#automate_host_record)
- crec - [stream storage](#automate_conn_record)
- arg: success_detector - the name of the success detector function. Defaults to `standard_success_detector` if not specified.
- arg: failure_detector - the name of the failure detector function. Defaults to `standard_failure_detector` if not specified.
- arg: fails - the target failure counter value. Default is 3.
- arg: maxtime - the maximum time in seconds between failures before the counter resets. Default is 60 seconds.
This function maintains the failure counter by invoking the success and failure detectors. It returns `true` if the counter reaches the target value. The counter resets automatically in this case.
### standard_success_detector
Standard success and failure detectors require inbound and outbound traffic to be redirected in the volume necessary to trigger their criteria.
For relative sequence detection, packets up to the specified `rseq` plus the maximum payload length of one packet (1460 bytes for TCP) are required.
```
function standard_success_detector(desync, crec)
```
- crec - [stream storage](#automate_conn_record)
- arg: maxseq - the outgoing relative sequence threshold for marking a success. Default is 32768. Purpose: Sufficient data has been sent without the stream stalling due to blocking.
- arg: inseq - the incoming relative sequence threshold for marking a success. Default is 4096. Purpose: The peer has sent enough data to ensure it is not a DPI-generated response.
- arg: udp_out, udp_in - more than `udp_in` UDP packets received, provided that `udp_out` > 0. Purpose: The peer has sent enough data to ensure it is not a DPI-generated response.
Standard success detector.
### standard_failure_detector
```
function standard_failure_detector(desync, crec)
```
- crec - [stream storage](#automate_conn_record)
- arg: maxseq - count retransmissions within outgoing relative sequences from 1 to `maxseq`. Default is 32768.
- arg: retrans - consider it a failure if there are at least `retrans` retransmissions. Default is 3.
- arg: reset - send an RST to the retransmitter to terminate long wait times.
- arg: inseq - treat RSTs and HTTP redirects within incoming relative sequences from 1 to `inseq` as failures. Default is 4096.
- arg: no_rst - do not treat RST as a failure.
- arg: no_http_redirect - do not treat HTTP redirects as failures.
- arg: udp_out, udp_in - treat as a failure if >=`udp_out` packets are sent but <=`udp_in` packets are received. Purpose: We are sending a lot, but receiving little or no response.
Standard failure detector.
DPI-based HTTP redirects are defined the same way as in [autohostlists](#autohostlist-failure-detector).
## Orchestrators
### circular
```
function circular(ctx, desync)
```
- arg: [standard host storage](#automate_host_record)
- arg: [standard checker](#automate_failure_check)
- arg: (standard detector only) [standard success detector](#standard_success_detector)
- arg: (standard detector only) [standard failure detector](#standard_failure_detector)
This orchestrator tracks failures and cycles through strategies once the failure counter reaches the `fails` target. All subsequent instances are labeled with a "strategy" argument containing the strategy number, starting from 1. Instances without a "strategy" argument are not called by `circular`. Strategy numbers must be continuous from 1 to the last; gaps are not allowed and will trigger an error. If any instance of strategy N includes a "final" argument, that strategy becomes the last one-further cycling is blocked.
Usage example:
```
--lua-desync=circular:fails=4:retrans=2:maxseq=16384
--lua-desync=argdebug:v=1.1:strategy=1
--lua-desync=argdebug:v=1.2:strategy=1
--lua-desync=argdebug:v=2.1:strategy=2
--lua-desync=argdebug:v=3.1:strategy=3:final
--lua-desync=argdebug:v=3.2:strategy=3
--lua-desync=argdebug:v=3.3:strategy=3
```
In this example, debug output is used instead of real strategies to trace `circular` operations in the debug log. There are 3 strategies: the 1st strategy includes 2 instances, the 2nd includes 1, and the 3rd includes 3. The 3rd strategy is final; returning to the 1st is blocked. To test this, try accessing blocked sites via curl to observe the failure detector's operation.
### repeater
```
function repeater(ctx, desync)
```
- arg: instances - how many subsequent instances to repeat.
- arg: repeats - the number of repetitions.
- arg: stop - do not execute the instances following "instances" as a standalone pass.
- arg: clear - clear the execution plan after repetitions.
- arg: iff - the name of the [condition function](#iff-functions) for continuing the repetition cycle. If not specified, the condition is always true.
- arg: neg - invert the `iff` value. Default is false.
As the name suggests, the `repeater` orchestrator repeats the subsequent `instances` a specified number of `repeats`. The repetition follows the pattern `1-2-3-1-2-3-1-2-3-4-5-6`. In this example, `4-5-6` are the instances following the first three, assuming `instances=3`. If `stop` or `clear` is specified, `4-5-6` are not called. The `clear` flag additionally clears the execution plan, which is useful for interacting with higher-level orchestrators.
The `iff` function allows you to define an additional dynamic condition for continuing the repetition loop. If `xor(iff, neg) = false`, the loop is terminated.
`repeater` supports arbitrary levels of nesting.
In the example below, the resulting call sequence is: `1 1 1 2 2 2 1 1 1 2 2 2 3`.
The `stop` parameter in nested instances prevents the execution of instances that do not belong to their own repetition cycle.
The first `repeater` is not restricted by `stop`, so it proceeds to execute `3`.
```
--lua-desync=repeater:repeats=2:instances=4
--lua-desync=repeater:repeats=3:stop --lua-desync=argdebug:v=1
--lua-desync=repeater:repeats=3:stop --lua-desync=argdebug:v=2
--lua-desync=argdebug:v=3
```
### condition
```
function condition(ctx, desync)
```
- arg: `iff` - name of the [iff function](#iff-functions)
- arg: `neg` - invert the `iff` value; defaults to `false`
`condition` calls `iff`. If `iff xor neg = true`, all instances in the `plan` are executed; otherwise, the plan is cleared.
### stopif
```
function condition(ctx, desync)
```
- arg: `iff` - name of the [iff function](#iff-functions)
- arg: `neg` - invert the `iff` value; defaults to `false`
`stopif` calls `iff`. If `iff xor neg = true`, the plan is cleared; otherwise, no action is taken.
`stopif` is useful as a nested orchestrator. For example, it can be used with `circular` to block strategy execution under certain conditions. `condition` is unsuitable for this purpose because it has no awareness of higher-level orchestrators or the "strategy" parameter, and would simply execute instances indiscriminately until the end. `stopif` clears the plan, thereby halting further execution of the parent orchestrator.
### iff functions
These are used across several orchestrators and take `desync` as a parameter.
They can contain any logic programmable in Lua. The base set includes several `iff` functions for demonstration and testing purposes.
#### cond_true
```
function cond_true(desync)
```
Always returns `true`.
#### cond_false
```
function cond_false(desync)
```
Always returns `false`.
#### cond_random
```
function cond_random(desync)
```
- arg: `percent` - the probability of returning `true`. Defaults to 50.
Returns `true` randomly based on the `percent` probability; otherwise returns `false`.
#### cond_payload_str
```
function cond_payload_str(desync)
```
- arg: `pattern` - the string to search for in the payload.
Returns `true` if the substring `pattern` is present in `desync.dis.payload`.
This is a basic signature detector. If the C code does not recognize the protocol you need, you can write your own signature detector and run subsequent instances under a `condition` orchestrator using your detector as the `iff` function.
# Auxiliary programs
## ip2net
The `ip2net` utility is designed to convert IPv4 or IPv6 address lists into a list of subnets to reduce the overall list size. It reads from `stdin` and outputs to `stdout`.
```
-4 ; list is ipv4 (default)
-6 ; list is ipv6
--prefix-length=min[-max] ; range of prefix lengths to consider. e.g., 22-30 (ipv4), 56-64 (ipv6)
--v4-threshold=mul/div ; ipv4: include subnets where at least mul/div addresses are filled. e.g., 3/4
--v6-threshold=N ; ipv6: minimum number of IPs required to create a subnet
```
The list may contain entries in the format `ip/prefix` and `ip1-ip2`. These entries are passed to `stdout` without modification.
They are accepted by the `ipset` command. For `hash:net` lists, `ipset` can generate optimal `ip/prefix` coverage from `ip1-ip2` ranges.
FreeBSD's `ipfw` supports `ip/prefix` but does not support `ip1-ip2`.
`ip2net` filters input data, discarding invalid IP addresses.
A subnet is selected if it contains a specified minimum number of addresses.
For IPv4, the minimum is set as a percentage of the subnet size (`mul/div`, e.g., `3/4`); for IPv6, the minimum is specified directly.
The subnet size is determined by the following algorithm:
First, the tool searches within the specified prefix length range for subnets that contain the maximum number of addresses.
If multiple such subnets are found, the smallest one (the one with the longest prefix) is chosen.
For example, given the parameters `v6_threshold=2` and `prefix_length=32-64`, and the following IPv6 addresses:
```
1234:5678:aaaa::5
1234:5678:aaaa::6
1234:5678:aaac::5
Result:
1234:5678:aaa8::/45
```
These addresses are also part of a `/32` subnet. However, there is no point in using "blanket coverage" when the exact same addresses fit perfectly into a `/45` and their count remains the same.
If `v6_threshold` is changed to `4`, the result will be:
```
1234:5678:aaaa::5
1234:5678:aaaa::6
1234:5678:aaac::5
```
In this case, the IPs will not be merged into a subnet because there are too few of them.
If `prefix_length=56-64` is used, the result will be:
```
1234:5678:aaaa::/64
1234:5678:aaac::5
```
The CPU time required for calculations depends heavily on the width of the prefix length range, the size of the target subnets, and the length of the list.
If `ip2net` takes too long, avoid using excessively large subnets and narrow the prefix length range.
Note that `mul/div` arithmetic uses integers. If the 32-bit integer limit is exceeded, the results are unpredictable.
Avoid using values like `5000000/10000000`; `1/2` is much more efficient.
## mdig
This program is designed for multithreaded resolving of large lists via the system DNS.
It reads a list of domains from `stdin` and outputs the resolution results to `stdout`. Errors are sent to `stderr`.
```
--family=<4|6|46> ; select IP address family: ipv4, ipv6, ipv4+ipv6
--threads= ; number of threads. Default is 1.
--eagain= ; number of retries after EAI_AGAIN. Default is 10.
--eagain-delay= ; wait time in ms between EAI_AGAIN retries. Default is 500.
--verbose ; debug log to console
--stats=N ; output statistics every N domains
--log-resolved= ; save successfully resolved domains to a file
--log-failed= ; save failed domain resolutions to a file
--dns-make-query= ; output a binary DNS query for the domain to stdout. If --family=6, the query will be AAAA, otherwise A.
--dns-parse-query ; parse a binary DNS response and output all IPv4 and IPv6 addresses from it to stdout
```
The `--dns-make-query` and `--dns-parse-query` parameters allow you to resolve a single domain through an arbitrary channel.
For example, you can perform a DoH (DNS over HTTPS) request using only `mdig` and `curl` as follows:
```
mdig --family=6 --dns-make-query=rutracker.org | curl --data-binary @- -H "Content-Type: application/dns-message" https://cloudflare-dns.com/dns-query | mdig --dns-parse-query
```
# blockcheck2
`blockcheck2` is a tool for automating strategy testing. It is a POSIX shell script with a modular test structure.
A test consists of a set of pluggable shell scripts used to test a specific group of strategies. The strategies under test can be generated by the script's algorithm based on various conditions or the success of previous checks to reduce the overall testing time.
Test suites are located in the `blockcheck2.d` subdirectories. The name of the subdirectory corresponds to the name of the test.
By default, blockcheck2 runs in interactive mode, displaying messages and prompting the user for parameters. However, since there are many parameters, it only asks for the most essential ones. The rest are passed via [shell variables](#shell-variables).
A typical launch scheme using variables:
`BATCH=1 DOMAINS=bbc.com CURL_CMD=1 SKIP_DNSCHECK=1 /opt/zapret2/blockcheck2.sh`
If you need to record a log, use standard shell tools:
`/opt/zapret2/blockcheck2.sh | tee /tmp/blockcheck2.log`
In the [win bundle](https://github.com/bol-van/zapret-win-bundle), you can use the cygwin prompt (`cygwin/cygwin-admin.cmd`). Aliases have already been created there to launch blockcheck from the first version of zapret, blockcheck2, winws, and winws2 with the standard Lua scripts already connected. This is convenient because you don't have to worry about file paths or repeatedly typing or pasting long strings of text-especially when dealing with national characters and spaces in paths, which can lead to confusion with character escaping and encodings.
Sequential testing of multiple domains is possible. To do this, specify them separated by spaces.
URIs such as `rutracker.org/forum/index.php` are supported. There should be no protocol prefix like `https://`. By default, the root URI ('/') is used. HTTP is tested using the GET method, while HTTPS uses the HEAD method, as nothing is visible under TLS anyway. However, there are situations where blocking does not occur immediately, but rather upon a long server response. In this case, you can use [CURL_HTTPS_GET=1](#shell-variables) and specify a URI where the server returns a long response.
blockcheck2 is not a panacea; it is not a tool for generating "magic strings" that you can simply paste somewhere to make sites start working. It is a customizable tool for researching DPI and automating routine tasks. Understanding the results and how to apply them is the responsibility of the user.
You should also not expect highly complex strategy selection algorithms from blockcheck. Shell scripts are not a full-fledged programming language and lack the tools to work with complex data structures. Shell programming often becomes a struggle when dealing with complex data, as it must somehow be recorded into a linear set of environment variables.
blockcheck2 works on all supported platforms: Linux, FreeBSD, OpenBSD, and Windows. On Windows, the easiest way to use it is through the [win bundle](https://github.com/bol-van/zapret-win-bundle) - a minimal cygwin system pre-configured for zapret.
## DNS check
Nothing will work if the provider returns spoofed IP addresses for blocked domains, unless your client or OS supports routing requests through encrypted channels (e.g., DoH). Even if the browser works due to built-in DoH, there are other programs that lack such support. If the OS itself supports encrypted DNS, you can use that feature; if not, solving the DNS problem is up to you.
If IPs are only spoofed on the provider's DNS servers, and access to other DNS servers is neither blocked nor spoofed, you can use alternative DNS services. For example, public ones like 1.1.1.1, 8.8.8.8, or 9.9.9.9. If access to other DNS servers is blocked or spoofed, you must encrypt the DNS channel. It is also possible that spoofing only occurs on port 53, while DNS responses on other ports remain untouched. However, standard clients do not support such configurations. You would need to either set up transparent redirection on your router or use a DNS aggregator (like dnsmasq) that supports querying DNS on non-standard ports.
Yandex provides DNS on port 1253. On OpenWrt, this is configured quite simply:
**/etc/config/dhcp**
```
config dnsmasq
list server '77.88.8.88#1253'
```
**/etc/config/network**
```
config interface 'wan'
option peerdns '0'
```
blockcheck2 can detect whether DNS is being spoofed and whether requests to third-party DNS servers are being intercepted. If spoofing is detected, it automatically switches to DoH.
The list of external DNS servers, domains for spoofing tests, the selected DoH server, and the list of DoH servers for automatic selection can be modified via [shell variables](#shell-variables). You can also use the same method to opt out of testing.
The `SECURE_DNS` variable allows you to manually disable the switch to DoH or, conversely, force it even if no spoofing is detected.
## Main testing modes
### Multiple attempts
Strategy instability is a common phenomenon. A provider might use load balancing, causing different requests to pass through different DPI systems. As a result, a strategy might work one moment and fail the next. Strategy stability is tested through multiple repetitions-attempts. The number of attempts is set either in the dialog or via [shell variables](#shell-variables).
Parallel mode is [supported](#shell-variables). In this mode, each attempt is executed in a separate child process, and results are then aggregated from all processes. This mode is enabled only via the [PARALLEL variable](#shell-variables). It can significantly speed up testing but may also trigger a rate limit-a situation where the server restricts or bans you due to excessive hammering.
### Scanning levels
- **standard** - uses a test algorithm that excludes strategies deemed irrelevant based on previous successes or other criteria. In the case of multiple attempts, testing does not stop upon failure. The success rate and curl errors can also provide useful information for situational analysis.
- **quick** - same as standard, but when using multiple attempts, testing stops after the first failure.
- **force** - tests as extensively as possible, regardless of previous test results.
### Supported protocols
blockcheck2 tests strategies using curl. It supports checking http, https via TLS 1.2, https via TLS 1.3, and http3 (quic). Support for TLS 1.3 and quic may be missing in the curl version installed on your system. If so, you can download a static curl binary from [curl.se](https://curl.se), save it somewhere, grant it execution permissions, and specify the `CURL=/curl` variable. Blockcheck will then use it instead of the system version. On OpenWRT devices with minimal disk space, you can use `/tmp`, which is a tmpfs stored in RAM.
TLS 1.2 is more difficult for DPI bypass than TLS 1.3 because the certificate is returned in plain text during the TLS Server Hello, exposing the domain name. DPI can block the connection based on the server's response, and this is quite difficult to counter. The best tactic for bypassing TLS 1.2 is to ensure the DPI is satisfied with the client's request so that it doesn't bother checking the server's response. If this fails, there is [wssize](#wssize), but it reduces speed and does not work with hostlists.
TLS 1.3 provides minimal unencrypted information during the TLS Server Hello. There is no domain information in plain text, and virtually no fingerprint, so DPI cannot block by domain by analyzing the server's response. However, it can still block the TLS protocol entirely if its rules prohibit TLS.
### IP block check
zapret cannot bypass IP-based blocks.
IP blocks come in various forms.
1. **Full IP block.** Absolutely nothing gets through-no pings, no port connections. Communication is impossible without a proxy.
2. **Port or L4 protocol block.** For example, pings (ICMP) work, but no TCP ports connect, while UDP traffic still passes. Alternatively, a specific port like TCP 443 might be blocked. A TCP port block means all packets with that destination port are dropped. The connection hangs on an endless loop of sending SYN requests; the 3-way handshake never completes. Communication is impossible without a proxy.
3. **Partial IP/port block.** The 3-way handshake completes successfully, but after that, anything you send results in a hang or an RST (reset) packet. The very fact that the connection establishes suggests the potential existence of "whitelisted" messages that could grant access. These might be requests with approved SNIs or packets using a different protocol altogether - something other than TLS. You can only bypass this block if you have specific information on how to "pierce" it, or if you are technically proficient and persistent enough to test various options manually.
**blockcheck2** is designed to assist in identifying IP blocks, but it does not provide an automated verdict - you must decide whether a block exists based on its actions.
The first step is checking port availability via `nc` or `ncat`. These must be installed, as they may not be included in your system out of the box. `ncat` is preferred because it offers more features and handles IPv6 reliably. If neither is installed, the test is canceled. Automated decision-making is hindered by the inconsistent exit codes across different versions of netcat. Some versions do not return success or failure codes at all, providing only version-specific text messages.
You must examine the log yourself: if you do not see a successful connection to the port, it likely indicates an IP or port block. It is possible that only a portion of the IP resources is blocked. If you proceed without addressing this, strategies will behave inconsistently because requests will be routed to random available IPs. If all IPs and ports are blocked, further testing is pointless, as every attempt will result in an error.
Next, a partial IP block is tested using `curl`. This investigates scenarios where the initial port connection is successful. The goal is to determine if blocking occurs on a blocked domain's IP when using an unblocked domain, and vice versa. If an unblocked domain fails on a blocked domain's IP, but works on its own native IP, there is a partial IP block. If blocked domains fail on unblocked IPs, while the unblocked domains themselves work, there is an SNI block. IP and SNI blocks can also be used in combination.
Evaluating the success or failure of these checks is the main challenge, which is why the interpretation falls to the user. What constitutes "success" or "failure"? This varies wildly and can depend on both the DPI (Deep Packet Inspection) system and the server itself.
For example, a TLS error when requesting `iana.org` via the IP of `rutracker.org` might actually indicate success. A certificate error can be either a success or a failure-an invalid certificate could be returned by the server itself or by the DPI via a Man-in-the-Middle (MiTM) attack. It is crucial to determine whether the server returned *any* response and whether that response truly came from the server or was generated by the DPI. A TLS alert when requesting a domain not hosted on that server is normal and common. Conversely, some servers are configured to serve a page regardless of the SNI provided in the TLS handshake - this is also normal.
A hang is usually a sign of failure, but it could also result from server-side issues or the server banning your IP. An RST is likely a failure, but it could be a legitimate response from the server or its DDoS protection system.
There are many variables at play here, so observe the results carefully and use your judgment.
#### Examples of domain-only blocking without IP blocking
```
> testing iana.org on its original
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (28) Operation timed out after 1002 milliseconds with 0 bytes received
> testing iana.org on 172.67.182.196 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on 104.21.32.39 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on its original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (28) Connection timed out after 1001 milliseconds
> testing iana.org on 172.67.182.196 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on 104.21.32.39 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on its original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
HTTP/1.1 307 Temporary Redirect
Location: https://www.gblnet.net/blocked.php
> testing iana.org on 172.67.182.196 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on 104.21.32.39 (rutracker.org)
HTTP/1.1 409 Conflict
> testing iana.org on its original ip
!!!!! AVAILABLE !!!!!
> testing rutracker.org on 192.0.43.8 (iana.org)
curl: (35) Recv failure: Connection reset by peer
> testing iana.org on 172.67.182.196 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
> testing iana.org on 104.21.32.39 (rutracker.org)
curl: (35) OpenSSL/3.2.1: error:0A000410:SSL routines::ssl/tls alert handshake failure
```
#### Example of full IP block or TCP port block in the absence of domain blocking
```
* port block tests ipv4 startmail.com:80
ncat -z -w 1 145.131.90.136 80
145.131.90.136 does not connect. netcat code 1
ncat -z -w 1 145.131.90.152 80
145.131.90.152 does not connect. netcat code 1
* curl_test_http ipv4 startmail.com
- checking without DPI bypass
curl: (28) Connection timed out after 2002 milliseconds
UNAVAILABLE code=28
- IP block tests (requires manual interpretation)
> testing iana.org on its original ip
!!!!! AVAILABLE !!!!!
> testing startmail.com on 192.0.43.8 (iana.org)
HTTP/1.1 302 Found
Location: https://www.iana.org/
> testing iana.org on 145.131.90.136 (startmail.com)
curl: (28) Connection timed out after 2002 milliseconds
> testing iana.org on 145.131.90.152 (startmail.com)
curl: (28) Connection timed out after 2002 milliseconds
```
### Standard tests
#### standard test
This is the primary test, consisting of a series of subtests located in `blockcheck2.d/standard`. If you do not need certain tests, you can copy the `standard` directory to `my` and keep only the necessary scripts there. The `def.in` file is mandatory.
It accepts several additional variables:
```
MIN_TTL - minimum TTL
MAX_TTL - maximum TTL. 0 disables TTL tests
MIN_AUTOTTL_DELTA - minimum negative autottl delta
MAX_AUTOTTL_DELTA - maximum negative autottl delta. 0 disables AUTOTTL tests
FAKE_REPEATS - number of fake repeats
FOOLINGS46_TCP - space-separated list of TCP foolings for IPv4+IPv6
FOOLINGS6_TCP - space-separated list of specific TCP foolings for IPv6
FAKE_HTTP - path to the HTTP fake file
FAKE_HTTPS - path to the HTTPS fake file
FAKED_PATTERN_HTTP - path to the fakedsplit/fakeddisorder pattern file for HTTP
FAKED_PATTERN_HTTPS - path to the fakedsplit/fakeddisorder pattern file for HTTPS
FAKE_QUIC - path to the QUIC fake file
SEQOVL_PATTERN_HTTP - path to the seqovl pattern file for HTTP
SEQOVL_PATTERN_HTTPS - path to the seqovl pattern file for HTTPS
MULTIDISORDER=multidisorder_legacy - replace multidisorder with the version from nfqws1
NOTEST_BASIC_HTTP=1 - disable 10-http-basic.sh tests
NOTEST_MISC_HTTP=1 - disable http 15-misc.sh tests
NOTEST_MISC_HTTPS=1 - disable https 15-misc.sh tests
NOTEST_MULTI_HTTP=1 - disable http 20-multi.sh tests
NOTEST_MULTI_HTTPS=1 - disable https 20-multi.sh tests
NOTEST_SEQOVL_HTTP=1 - disable http 23-seqovl.sh tests
NOTEST_SEQOVL_HTTPS=1 - disable https 23-seqovl.sh tests
NOTEST_SYNDATA_HTTP=1 - disable http 24-syndata.sh tests
NOTEST_SYNDATA_HTTPS=1 - disable https 24-syndata.sh tests
NOTEST_FAKE_HTTP=1 - disable http 25-fake.sh tests
NOTEST_FAKE_HTTPS=1 - disable https 25-fake.sh tests
NOTEST_FAKED_HTTP=1 - disable http 25-faked.sh tests
NOTEST_FAKED_HTTPS=1 - disable https 25-faked.sh tests
NOTEST_HOSTFAKE_HTTP=1 - disable http 35-hostfake.sh tests
NOTEST_HOSTFAKE_HTTPS=1 - disable https 35-hostfake.sh tests
NOTEST_FAKE_MULTI_HTTP=1 - disable http 50-fake-multi.sh tests
NOTEST_FAKE_MULTI_HTTPS=1 - disable https 50-fake-multi.sh tests
NOTEST_FAKE_FAKED_HTTP=1 - disable http 55-fake-faked.sh tests
NOTEST_FAKE_FAKED_HTTPS=1 - disable https 55-fake-faked.sh tests
NOTEST_FAKE_HOSTFAKE_HTTP=1 - disable http 60-fake-hostfake.sh tests
NOTEST_FAKE_HOSTFAKE_HTTPS=1 - disable https 60-fake-hostfake.sh tests
NOTEST_QUIC=1 - disable 90-quic.sh tests
```
#### custom test
A simple tester that uses strategy lists from files. Strategies must be on separate lines; line breaks within a single strategy are not allowed. Separate lists are used for different protocols: `list_http.txt`, `list_https_tls12.txt`, `list_https_tls13.txt`, and `list_quic.sh`. These files support comments starting with `#`.
When defining parameters, keep in mind that they will be interpreted as shell arguments. Special characters must be escaped according to shell rules.
For example, leaving `<` unquoted across the entire parameter or incorrectly quoting the `--luaexec=code=print("abc")` parameter will result in an error. If your Lua code uses strings, it is best to enclose them in single quotes and wrap the entire parameter in double quotes.
Note that `blockcheck2` will output strategy parameters without escaping.
The recommended way to use this is to copy it into its own subdirectory within `blockcheck2.d` and populate the `.txt` files with your tests. Then, select your custom test name from the dialog.
## Summary
At the end of the test, all successful strategies are displayed for each domain and each IP protocol version. If more than one domain was tested, the intersection of successful strategies (those that worked for all) is also provided. However, this intersection can only be fully relied upon when using `SCANLEVEL=force`. Otherwise, strategies that might have worked for subsequent domains might not have been tested for the first one.
## Shell variables
```
CURL - path to the curl executable
CURL_MAX_TIME - curl timeout in seconds
CURL_MAX_TIME_QUIC - curl timeout for QUIC. If not set, CURL_MAX_TIME is used
CURL_MAX_TIME_DOH - curl timeout for DoH servers
CURL_CMD=1 - display curl commands
CURL_OPT - additional curl options. `-k` to ignore certificates, `-v` for verbose protocol output
CURL_HTTPS_GET=1 - use the GET method instead of HEAD for HTTPS
DOMAINS - space-separated list of domains or domains with URIs to test
TEST - name of the test
IPVS=4|6|46 - IP protocol versions to test
ENABLE_HTTP=0|1 - enable plain HTTP test
ENABLE_HTTPS_TLS12=0|1 - enable HTTPS TLS 1.2 test
ENABLE_HTTPS_TLS13=0|1 - enable HTTPS TLS 1.3 test
ENABLE_HTTP3=0|1 - enable QUIC test
REPEATS - number of test attempts
PARALLEL=0|1 - enable parallel attempts. May overwhelm the site due to aggressive requests and lead to incorrect results
SCANLEVEL=quick|standard|force - scanning level
BATCH=1 - batch mode (no prompts or waiting for console input)
HTTP_PORT, HTTPS_PORT, QUIC_PORT - port numbers for the respective protocols
SKIP_DNSCHECK=1 - skip DNS check
SKIP_IPBLOCK=1 - skip port or IP blocking tests
PKTWS_EXTRA_POST - additional nfqws/dvtws/winws parameters added after the main strategy
PKTWS_EXTRA_POST_1 .. PKTWS_EXTRA_POST_9 - separate additional parameters that contain spaces
PKTWS_EXTRA_PRE - additional nfqws/dvtws/winws parameters added before the main strategy
PKTWS_EXTRA_PRE_1 .. PKTWS_EXTRA_PRE_9 - separate additional parameters that contain spaces
DNSCHECK_DNS - space-separated list of third-party DNS servers for DNS spoofing tests
DNSCHECK_DOM - space-separated list of domains for DNS spoofing tests
SECURE_DNS=0|1 - force enable or disable DoH
DOH_SERVERS - space-separated list of DoH URLs for automatic selection of a working server
DOH_SERVER - specific DoH URL (disables auto-selection)
UNBLOCKED_DOM - an unblocked domain used for IP block tests
SIMULATE=1 - enable simulation mode for debugging script logic. Disables real curl requests and replaces them with random results.
SIM_SUCCESS_RATE= - simulation success probability as a percentage
```
## Why it won't open
Blockcheck shows OK, but the site still won't open. Why?
Blockcheck only verifies the availability of a single specific "domain[/uri]" using a specific protocol, and nothing more.
A browser does far more than that. So, what's the difference?
1. It can all start with DNS. `blockcheck` might use system DNS or DoH, while the browser does the opposite. Sites like Instagram are partially blocked by IP. Success depends on the specific IP address returned by a particular DNS. If your DNS is poisoned by your ISP and your client doesn't support encrypted DNS, it's game over for all bypass methods. You will be directed to a block page IP, and naturally, no amount of `zapret` will help. Sometimes, the ISP provides no DNS response at all for a blocked domain or returns 127.0.0.1.
2. A website is not just a single domain or a single URI. Press F12 in your browser and check the "Network" tab to see where the site is actually connecting. It might be tripping up on a secondary domain.
3. A browser's goal is to open a site as quickly as possible without burdening the user with technical jargon that sounds like Greek to them. Therefore, it attempts to reach the site using various protocols to ensure the page opens faster. It might jump between IPv4 and IPv6, or between TLS and QUIC. That’s already four combinations. Each of these requires a separate `blockcheck` test. Did a single strategy bypass all of them? Did you migrate these strategies to your working config correctly? Did you merge them properly?
4. A particularly important parameter is Kyber. This post-quantum cryptography turns a single-packet TLS/QUIC request into a 2 or 3-packet one. This is a significant factor in DPI circumvention. Modern browsers usually use Kyber. For `curl`, it depends on the version and the age of the crypto library it is linked with. OpenSSL 3.5.0 supports Kyber; older versions do not. LibreSSL or mbedTLS do not support Kyber yet. But they will tomorrow, because that is where the trend is heading.
5. Censors will use any dirty trick available. Sometimes they even target the client's fingerprint. What is that? Primarily, it is the presence and order of TLS extensions characteristic of a specific browser or `curl`. If they cannot ban the IP addresses but need to eliminate a new VPN client, they analyze its handshake, find unique features, and block based on them. If something else breaks in the process, they simply don't care.
6. ECH (Encrypted Client Hello) is a technology for encrypted SNI transmission designed to prevent censors from seeing which resource is being accessed. It is an excellent technology, but unfortunately, it arrived too late. It missed the window to become an undisputed de facto standard. Consequently, they may block based on the presence of ECH itself or by the decoy SNI-for Cloudflare, this is "cloudflare-ech.com". `curl` might hit a site without ECH while the browser uses it-hence the discrepancy.
7. TLS protocol version. By default, `blockcheck2` tests TLS 1.2 as the most difficult case to bypass. A browser will most likely use TLS 1.3. There have been cases where censors intentionally blocked the TLS 1.3 protocol because it is used and required by the popular VLESS-REALITY bypass method. The level of disregard for collateral damage is now so high that breaking numerous legitimate resources no longer stops them.
8. The famous "16 KB" block. How do you test for a 16 KB block? Hit a URI with `curl` where the site returns a sufficiently long page. If the download hangs in the middle, that's it. By default, `blockcheck2` uses `HEAD` requests for HTTPS to avoid taxing the server and to save traffic-since nothing is visible under HTTPS anyway. This can be changed via [CURL_HTTPS_GET=1](#shell-variables). However, doing so will likely result in a stream of "UNAVAILABLE" errors. The standard option provides strategies that work when bypassing the 16 KB block, making it more informative. Why do they do this? The goal is to force everyone into Russian jurisdiction, and to achieve this, they are squeezing major hosting providers : Cloudflare, Hetzner, Akamai, AWS, and others. CDNs are often used for hidden proxy or VPN tunneling. They have to leave a few critical sites accessible. For example, hp.com-where else are you going to download LaserJet drivers? And HP is hosted on one of these providers. This creates a whitelist. Consequently, you need a payload that satisfies this whitelist. The trouble is that `blockcheck`, much like `zapret` itself, is merely a tool. It does not include ready-made recipes. You need to find what works to bypass specific hosts yourself. This is the situation today; tomorrow it will be something else. The author will not chase every change or constantly update `blockcheck` to give you ready-to-use copy-paste recipes. You need to analyze the situation and find a solution yourself. Currently, "16 KB" style whitelists are bypassed either by using a whitelisted SNI or some other whitelisted protocol type that isn't TLS. Tomorrow it will be something else. The [standard checker](#standard-test) accepts a whole list of variables for scan customization. There is an option to [insert something before and something after](#shell-variables) the strategy. Finally, `blockcheck` isn't the only option; sometimes it's more convenient to check things manually.
9. Following up on the previous point-the practice of implementing specific rules on certain IP ranges has become widespread. Therefore, you might find a strategy that works for most resources but fails on others.
10. There are reports of DPI behavior in the "punish the troublemaker" style. The DPI detects attempts to fool it and temporarily blocks access by IP. It might work once or twice, and then no connections go through. If the IP is dynamic, reconnecting to the ISP helps, but only until the next attempt. Another variation involves sending a UDP packet to a trigger IP (for example, via a torrent client) followed by a block of certain IP ranges. The author has not encountered such blocks personally, but dynamic blocking is the path China has already taken. It can be expected in Russia as well.
11. Strategy instability. Load balancing at the ISP level is a frequent occurrence. Traffic may pass through one DPI instance one moment and another the next, causing a strategy to work only intermittently. By default, `blockcheck2` only tests a single attempt. If there are doubts about stability, you should increase the number of attempts to at least 5. You can use [PARALLEL=1](#shell-variables), but this may trigger the resource's own protection against aggressive hammering.
The main goal of those implementing the blocks is to eliminate mass circumvention.
If the button doesn't work for 99% of people, the solution doesn't work for them.
In reality, technical research is required to see what they've come up with this time. It takes a lot of manual testing to identify their algorithms and find a workaround.
# Startup scripts
Startup scripts refer to the Linux wrapper that allows you to install, configure, remove, start, and stop the program. This also includes the maintenance system for IP and host lists. it supports OpenWrt and classic Linux distributions with `systemd` and `openrc`. For other Linux versions and firmwares, you can configure the parameters, but you will need to set up the autostart yourself.
Official support is guaranteed only for OpenWrt, starting from version 18. On older versions, it may work partially or not at all. On Keenetic, it works under Entware, but only through additional "support measures" that fall outside the scope of the zapret2 project and are maintained by third-party developers. For all other firmwares, the setup is entirely the user's responsibility. There are ready-made integrations for various firmwares, but their support lies entirely with their respective authors; they are not officially supported by the author of zapret.
nfqws2 can also operate standalone without startup scripts. However, you will need to handle the automatic startup, parameter passing, and firewall configuration yourself.
On Windows, no dedicated startup system is required-everything is typically handled by batch files to run winws2 in interactive mode or to manage the service.
On BSD, only the ipset list retrieval system is functional. On FreeBSD, it can load ipsets (tables) into ipfw. On OpenBSD, pf loads the IP list files directly.
## config file
Used by all components of the startup scripts, this file is named "config" and is located in the root of the zapret directory. It is a shell include file where variables are assigned and comments starting with '#' are supported. You can also use any shell constructs, such as variable referencing or arithmetic operators.
nfqws2 itself is unaware of the startup scripts or the config file and does not accept a path to it as a command-line argument.
| Parameter | Description |
| :--- | :--- |
| TMPDIR | Temporary directory to be used instead of /tmp. This is useful if the device has limited memory and the tmpfs in /tmp is insufficient. |
| WS_USER | The user account under which nfqws2 is executed. By default, this is determined automatically based on the operating system. |
| FWTYPE | The firewall type: iptables, nftables, or ipfw. This is automatically detected by default. |
| SET_MAXELEM | The maximum number of entries allowed in the created ipsets. |
| IPSET_OPT | ipset options for iptables. |
| IPSET_HOOK | A script that receives the ipset name as $1 and outputs a list of IPs to stdout, which are then added to the ipset. |
| IP2NET_OPT4
IP2NET_OPT6 | ip2net settings for scripts used to retrieve IP lists. |
| MDIG_THREADS | The number of mdig threads to use. This is utilized when resolving hostlists. |
| MDIG_EAGAIN | Number of attempts when receiving EAI_AGAIN |
| MDIG_EAGAIN_DELAY | Delay in ms between attempts when receiving EAI_AGAIN |
| AUTOHOSTLIST_INCOMING_MAXSEQ
AUTOHOSTLIST_RETRANS_MAXSEQ
AUTOHOSTLIST_RETRANS_THRESHOLD
AUTOHOSTLIST_RETRANS_RESET
AUTOHOSTLIST_FAIL_THRESHOLD
AUTOHOSTLIST_FAIL_TIME
AUTOHOSTLIST_UDP_IN
AUTOHOSTLIST_UDP_OUT | [Autohostlist](#autohostlist-failure-detector) parameters |
| AUTOHOSTLIST_DEBUGLOG | Enable autohostlist debug logging. The log is written to `ipset/zapret-hosts-auto-debug.log` |
| GZIP_LISTS | Whether to apply gzip compression to generated host and IP lists |
| DESYNC_MARK | Mark bit to prevent looping |
| DESYNC_MARK_POSTNAT | Mark bit for tagging flows using the POSTNAT scheme |
| FILTER_MARK | If set, intercepts only packets with this mark bit. Useful for creating specific filters, such as by local network source IP. |
| POSTNAT | Use post-NAT interception mode on nftables. Default is 1. On iptables, interception is always pre-NAT. |
| NFQWS2_ENABLE | Enable standard nfqws2 mode |
| NFQWS2_PORTS_TCP
NFQWS2_PORTS_UDP | Interception ports for standard nfqws2 mode |
| NFQWS2_TCP_PKT_OUT
NFQWS2_TCP_PKT_IN
NFQWS2_UDP_PKT_OUT
NFQWS2_UDP_PKT_IN | connbytes limiters by TCP/UDP and direction for standard nfqws2 mode |
| NFQWS2_PORTS_TCP_KEEPALIVE
NFQWS2_PORTS_UDP_KEEPALIVE | List of TCP/UDP ports for which the outgoing connbytes limiter is disabled in standard nfqws2 mode |
| NFQWS2_OPT | Command-line parameters for standard nfqws2 mode |
| MODE_FILTER | Filtering mode: none, ipset, hostlist, autohostlist |
| FLOWOFFLOAD | Offload mode: donttouch, none, software, hardware |
| OPENWRT_LAN
OPENWRT_WAN4
OPENWRT_WAN6 | Space-separated list of LAN and WAN interfaces for IPv4 and IPv6 in OpenWRT. Use netifd INTERFACES, NOT Linux interfaces! Defaults to "lan" and "wan" |
| IFACE_LAN
IFACE_WAN
IFACE_WAN6 | Space-separated list of LAN and WAN interfaces for IPv4 and IPv6 in classic Linux. Use Linux INTERFACES! |
| INIT_APPLY_FW | Whether [startup scripts](#startup-scripts) should apply firewall rules |
| INIT_FW_PRE_UP_HOOK
INIT_FW_POST_UP_HOOK
INIT_FW_PRE_DOWN_HOOK
INIT_FW_POST_DOWN_HOOK | Hook scripts called before/after bringing the firewall up and before/after taking it down |
| DISABLE_IPV4
DISABLE_IPV6
| Disable specific IP protocol versions |
| FILTER_TTL_EXPIRED_ICMP | Filter "time exceeded" messages in response to packets belonging to flows processed by zapret |
| GETLIST | [Script within ipset](#list-management-system) called from `ipset/get_config.sh`. Defaults to `ipset/get_ipban.sh` if not specified |
- "Standard nfqws2 mode" refers to the instance launched with the `NFQWS2_OPT` parameters when `NFQWS2_ENABLE` is active. This is in contrast to [custom scripts](#custom-scripts), which may launch non-standard or custom nfqws2 instances.
- netifd interfaces are those visible in `/etc/config/network` or LuCI, and are retrieved via the `ifstatus` command. These are not Linux interfaces. Linux interfaces are specified in the `device` parameter of an interface definition and are visible via the `ip link` command. For example, a netifd interface might be "lan", while the Linux interface is "br-lan". You must enter "lan" into `OPENWRT_LAN` rather than "br-lan"; otherwise, it will not work.
- Specifying LAN interfaces is only necessary for flow offloading in nftables and is not used for any other purpose.
- The `NFQWS2_OPT` command-line parameters should only include the strategy. Standard Lua files and service parameters like `--qnum` or `--user` are added automatically. You can add your own `--blob` or `--lua-init` parameters.
- `NFQWS2_OPT` accepts the `` and `` placeholders. These act as substitutions for standard lists and are replaced by `--hostlist` and `--hostlist-auto` parameters depending on the `MODE_FILTER` setting. `` adds the auto-hostlist as a regular list without the "auto" functionality. Only files that actually exist are included in the parameters. These placeholders can be used in different profiles; their placement depends entirely on the logic of your specific strategies.
- Directly specifying parameters in `NFQWS2_OPT`, such as `--hostlist=/opt/zapret2/ipset/zapret-hosts-user.txt`, is strongly discouraged. This breaks the `MODE_FILTER` logic and the list retrieval scripts, meaning their results might not be taken into account.
- Storing your own files in `/opt/zapret2` is risky, as the installer may delete them during a zapret2 update. Use a location outside the zapret2 directory.
## List management system
Located in the `ipset` directory, this system consists of shell scripts that manage files with fixed names within the same directory.
### Standard list files
Lists are categorized into hostlists and IP lists, and further divided into user-defined and auto-generated files.
User-defined lists are maintained manually and are not modified by the software. Auto-generated lists are the result of program execution (such as downloaded lists) and are not intended for manual editing. Any changes to auto-generated lists may be overwritten.
User-defined hostlists can contain hostnames, IPv4 addresses, IPv6 addresses, or CIDR subnets. Auto-generated hostlists can only contain hostnames.
All lists can be compressed with gzip. In this case, ".gz" is added to their filenames. Gzip compression is typically not used for user-defined lists because they are generally small and difficult to edit when compressed. For auto-generated lists, gzip is usually employed to save disk space. Whether compression is applied to generated lists depends on the [GZIP_LISTS](#config-file) configuration variable.
IP lists are separated into IPv4 and IPv6. IPv6 lists are identified by a "6" character appended to the filename before the extension.
Depending on the mode, hostlists can be resolved into IP lists using [mdig](#mdig) or applied as is. If hostlists are applied as is in nfqws2, only domain names are considered, while IP addresses and subnets are ignored.
Inclusion IP lists are loaded into kernel sets and applied in table rules only if [MODE_FILTER=ipset](#config-file) is specified.
The exclusion IP list is loaded into kernel sets and is always applied in table rules.
| Hostlist | Type | Purpose | IP Lists |
| :---------------------------- | :-------------- | :------------------ | :---------------------------------------------------- |
| zapret-hosts-user.txt | user-defined | inclusion | zapret-ip-user.txt
zapret-ip-user6.txt |
| zapret-hosts-user-exclude.txt | user-defined | exclusion | zapret-ip-exclude.txt
zapret-ip-exclude6.txt |
| zapret-hosts-user-ipban.txt | user-defined | traffic redirection | zapret-ip-user-ipban.txt
zapret-ip-user-ipban6.txt |
| -- | generated | traffic redirection | zapret-ip-ipban.txt
zapret-ip-ipban6.txt |
| zapret-hosts.txt | generated | inclusion | zapret-ip.txt
zapret-ip6.txt |
### ipset scripts
All scripts that generate hostlists call `get_ipban.sh`. All scripts that generate IP lists call `get_user.sh`. Essentially, ipban is always resolved regardless of whether you use hostlists or IP lists, while user lists are always resolved if you use any scripts to retrieve IP lists.
#### clear_lists.sh
Deletes all generated lists from the ipset.
#### create_ipset.sh
Loads all available IP lists into the corresponding ipsets. Here, "ipset" refers to iptables ipsets, nftables sets, or ipfw tables. The script selects the ipset backend based on the [FWTYPE](#config-file) config variable or automatically based on the OS and installed components if the variable is not set. Whether to load IPv4 or IPv6 versions depends on [DISABLE_IPV4](#config-file) and [DISABLE_IPV6](#config-file).
It takes one command-line parameter. This can be "clear" (to clear ipsets) or "no-update" (to load only if ipsets haven't been created yet, without performing an update).
ipset names:
- nozapret, nozapret6 - IP address exclusions
- zapret, zapret6 - IP address inclusions
- ipban, ipban6 - a separate inclusion list for third-party redirection or proxying
ipfw uses sets that include both IPv4 and IPv6 addresses, so the "6" variants are not used.
#### get_config.sh
Executes the ipset script specified in the [GETLIST](#config-file) config variable. If the variable is not set, it executes `get_ipban.sh`.
#### get_user.sh
Resolves `zapret-hosts-user.txt`, `zapret-hosts-exclude.txt`, and `zapret-hosts-ipban.txt`.
#### get_ipban.sh
Resolves `zapret-hosts-exclude.txt` and `zapret-hosts-ipban.txt`.
#### get_exclude.sh
Resolves `zapret-hosts-exclude.txt`.
#### get_antifilter_*.sh
Downloads IP or hostlists from .
Roskomnadzor, in its tireless concern for the well-being of the citizens of the Russian Federation, maintains several lists of resources that citizens are forbidden from visiting. Unfortunately, due to a lack of energy caused by constant thoughts about the future of Russia, they are unable to deliver the contents of these lists to every citizen.
We have decided to provide whatever assistance we can to Roskomnadzor and offer everyone up-to-date and complete lists of IP addresses that should not be visited. Based on these, you can even automate your "non-visiting" of them.
The website states that the lists are taken from zapret-info, which has since met its demise, so their relevance is questionable.
#### get_antizapret_domains.sh
Downloads hostlists from .
Hostlist from the legacy censorship circumvention service "prostovpn.org".
#### get_refilter_*.sh
Downloads IP or host lists from .
Re:filter is an attempt to maintain an up-to-date list of domains and IP addresses blocked in Russia, as well as those that are popular yet restricted for users within the country.
It excludes casinos, pornography, prostitution, drugs, and similar content.
#### get_reestr_*.sh
Downloads IP or host lists from .
IP lists contain both IPv4 and IPv6 addresses.
- `get_reestr_resolvable_domains.sh` - A list of blocked domains that actually resolve. More than half of all blocked domains are already dead; they are removed to avoid bloating the list.
- `get_reestr_preresolved.sh` - Periodically resolves the list of blocked domains. This does not help with "jumping" domains that have frequently changing IPs or domains that resolve differently based on GeoIP.
- `get_reestr_preresolved_smart.sh` - The previous list plus subnets of certain problematic ASNs, while excluding confirmed unblocked (whitelisted) ASNs. Problematic IPs include popular CDNs with rotating IPs and hosters subject to more stringent filtering rules in Russia. As of this writing, problematic ASNs include: AS32934 (Facebook, Instagram), AS13414 (Twitter), AS13335 (Cloudflare), AS15169 (Google), AS16509 (Amazon), AS16276 (OVH), AS24940 (Hetzner). Whitelisted ASNs: AS47541 (VK), AS35237 (Sberbank), AS47764 (Mail.ru), AS13238 (Yandex).
### ipban system
This is simply a system for resolving (and potentially downloading) a list, which results in the creation of kernel ipsets named "ipban" and "ipban6."
`zapret` itself does not perform any actions with them. They are intended for you to manually configure PBR (Policy-Based Routing) or selective proxy redirection for connections targeting "ipban" addresses. As the name suggests, IP addresses in this list cannot be bypassed autonomously; a VPN or proxy is required. PBR or selective redirection is used to avoid routing all traffic through them. Configuration instructions are beyond the scope of this project, but some information can be found in the [zapret1 documentation](https://github.com/bol-van/zapret/tree/master/docs).
A typical `ipban` usage scenario begins with creating an ipset from the saved lists.
```
#!/bin/sh
. /opt/zapret2/init.d/openwrt/functions
#. /opt/zapret2/init.d/sysv/functions
create_ipset no-update
```
You cannot be certain which will start first-your script or `zapret`.
Startup must be synchronized and should not run in parallel.
The best way to achieve this is by using [INIT_FW_*_HOOK](#config-file).
## Startup scripts
These are available only for Linux and OpenWRT. The Linux version is located in `init.d/sysv`, and the OpenWRT version is in `init.d/openwrt`. The main executable is `zapret2`. The required action is passed via the `$1` argument. The startup procedure is split into starting the daemons (nfqws2 processes) and initializing the firewall (applying ip/nftables rules).
| Command ($1) | Action |
| :----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| start
stop
restart | Starts/stops/restarts daemons and the firewall. The firewall will not start if [INIT_APPLY_FW](#config-file) is not set to 1. On OpenWRT with fw3 (iptables), the firewall is started separately; these commands only affect daemons and do not touch the firewall. |
| start_daemons
stop_daemons
restart_daemons | Starts/stops/restarts daemons. |
| start_fw
stop_fw
restart_fw | Starts/stops/restarts the firewall. On OpenWRT with fw3 (iptables), these commands work, but the firewall is started separately via a firewall include in `/etc/config/firewall`. Individual operations are not recommended. |
| reload_ifsets | (nftables only) Reloads wanif, wanif6, and lanif sets. |
| list_ifsets | (nftables only) Displays wanif, wanif6, lanif, and flowtable. |
| list_table | (nftables only) Displays the zapret2 table. |
The sysv variant is intended for any Linux distribution other than OpenWRT. On systems with different init systems, the sysv script is still used, while the integration with the native init system acts merely as an adapter to trigger it. For unsupported init systems or custom firmwares, you must determine where to hook "zapret2 start" and "zapret2 stop" yourself to ensure proper autostart and shutdown within your environment.
### Firewall integration
If the OS uses a firewall management system, conflicts may occur between it and zapret. Most commonly, these manifest as race conditions-a competition over who populates the rules first or clears the other's rules. This leads to chaos: sometimes it works, sometimes it doesn't, or only one component functions, making the overall state unpredictable. Race conditions usually happen with iptables because it uses shared tables. nftables generally avoids these issues since each application uses its own table. However, if the firewall management system decides to flush the entire ruleset, a race condition will still occur.
If you encounter race conditions or conflicts, the best solution is synchronization. Disable `INIT_APPLY_FW` in the [config](#config-file) file; this prevents the `start` command scripts from launching the firewall and creating a conflict. Next, determine how to trigger a third-party script to apply additional rules once your system's primary firewall is up. This script should be `zapret2 start_fw`. You can also integrate `stop_fw` and `restart_fw` in a similar fashion. Alternatively, you can take the opposite approach: use the `zapret` firewall initialization as a base and utilize [firewall hooks](#config-file) to trigger your system's firewall management commands. Ensure that your firewall management system does not overwrite or wipe the `zapret` rules.
If your firewall management system only works with its own rules and is highly incompatible with third-party additions, you might consider bypassing the startup scripts entirely. Instead, determine how to manually add `NFQUEUE` rules according to its specific logic and run the daemons separately via your distribution's init system. If this is undesirable or impossible, you may want to consider switching to a different firewall management system or abandoning it altogether.
#### OpenWRT firewall integration
OpenWRT comes with ready-made firewall integration. The startup scripts automatically detect `fw3` and disable firewall management via `start/stop/restart`. Instead, a firewall include `firewall.zapret2` is added to `/etc/config/firewall`, which launches the `zapret` rules synchronously after `fw3` is initialized. Additionally, a `90-zapret2` hook is placed in `/etc/hotplug.d/iface` to handle interface up/down events. In the `fw3` version, `fw3` is restarted so that rules are applied to new interfaces or removed for those that no longer exist. In the `nftables` version, only the `wanif`, `wanif6`, `lanif`, and `flowtable` sets are reloaded.
### Custom scripts
The standard [NFQWS2_OPT](#config-file) instance cannot always solve highly specific tasks. Interception is performed only by port. There is no way to include additional conditions-for example, intercepting a specific payload on any port, setting a specific `connbytes` filter, or using a special kernel `ipset` to apply unique strategies to that intercepted traffic.
Since these requirements are often very specific, they are not included in the core functionality. Instead, a system of custom scripts has been created. These are shell includes located in `init.d/sysv/custom.d` or `init.d/openwrt/custom.d`. Their primary task is to apply the firewall rules you need and launch `nfqws2` instances with the required parameters. Other auxiliary actions are also possible.
A custom script can contain the following shell functions, which are called by the startup system:
- **zapret_custom_daemons** - starts and stops daemons. `$1 = 1` for start, `0` for stop.
- **zapret_custom_firewall** - applies and removes `iptables` rules. `$1 = 1` for apply, `0` for remove.
- **zapret_custom_firewall_nft** - applies `nftables` rules. A stop function is not required because the main code clears the `nft` chains along with custom rules upon stopping.
- **zapret_custom_firewall_nft_flush** - called when `nftables` is stopped to allow for the removal of objects outside the standard chains, such as custom sets or custom chains.
If you do not need `iptables` or `nftables`, you do not need to write functions for that specific firewall type. It is highly recommended to use the core code's helpers within these functions; this allows you to follow the startup script ideology without needing to focus on low-level details. You can freely reference variables from the [config](#config-file) and add your own.
The best way to start writing your own scripts is to study the examples provided in `init.d/custom.d.examples.linux`.
#### Custom helpers
These are functions from the main startup script code that are useful when writing custom scripts.
##### Retrieving dynamic numbers
```
alloc_dnum()
# $1 - name of the variable to which the daemon number is assigned
alloc_qnum()
# $1 - name of the variable to which the queue number is assigned
```
Functions for obtaining dynamic numbers from the pool are necessary to prevent conflicts between different startup scripts. A queue number is a unique value; two instances cannot bind to the same queue, as the second one will fail with an error. A daemon number is required for PID tracking. If numbers overlap, start/stop/restart operations will not function correctly.
These functions should be called from the main body of the script, not from within a function. Custom scripts are always executed in alphabetical order-this is the standard scheme for ".d" directories in Unix. Given the same set of scripts, the same `qnum` and `dnum` values will always be returned for each script during both startup and shutdown. This allows them to be used as unique identifiers without colliding with other scripts. If the composition of custom scripts is changed after startup, this rule is broken, which will lead to issues. Therefore, it is best not to modify the set of custom scripts while zapret2 is running.
##### Working with daemons
```
do_nfqws()
# $1 - 1 to start, 0 to stop
# $2 - daemon number
# $3 - nfqws2 parameters
```
Starts or stops an `nfqws2` instance. Base parameters are appended automatically. These include user selection, fwmark, and the connection of standard Lua scripts: [zapret-lib.lua](#zapret-liblua-base-function-library), [zapret-antidpi.lua](#zapret-antidpilua-dpi-attack-program-library), and [zapret-auto.lua](#zapret-autolua-automation-and-orchestration-library).
You must specify the queue number yourself using `--qnum`.
```
filter_apply_hostlist_target()
# $1 - name of the variable containing nfqws2 options
```
Replaces the `` and `` markers in $1 depending on the [MODE_FILTER](#config-file) and the presence of list files in ipset.
```
standard_mode_daemons()
# $1 - 1 to start, 0 to stop
```
Starts or stops the [standard instance](#config-file) of `nfqws2`.
Any config variables can be overridden locally.
##### Working with iptables
```
fw_nfqws_post()
fw_nfqws_pre()
# $1 - 1 to enable, 0 to disable
# $2 - iptables filter for IPv4
# $3 - iptables filter for IPv6
# $4 - queue number
```
Enables or disables redirection rules to the queue for `nfqws2`. Filters are written separately for IPv4 and IPv6 because they may contain protocol-specific elements that would cause errors on the other IP version.
"post" refers to the chain for outgoing traffic, while "pre" refers to incoming traffic.
`$FW_EXTRA_PRE` is prepended to the rules, and `$FW_EXTRA_POST` is appended.
What should NOT be included in the filter:
- Checking standard exclude ipsets: `nozapret`, `nozapret6`.
- Checking by [DESYNC_FWMARK](#config-file).
Whether rules are applied for each IP version depends on the [config](#config-file) settings.
```
zapret_do_firewall_standard_tpws_rules_ipt()
# $1 - 1 to enable, 0 to disable
```
Applies or removes iptables rules for the [standard](#config-file) `nfqws2` instance.
You can locally override [config](#config-file) variables and apply `FW_EXTRA_POST` and `FW_EXTRA_PRE`.
```
filter_apply_ipset_target()
# $1 - name of the variable containing iptables filter rules
# $2 - name of the variable containing ip6tables filter rules
```
Adds checks for the standard `zapret`/`zapret6` sets in the outgoing direction (dst) to variables $1 and $2.
```
reverse_nfqws_rule_stream()
# stdin - iptables or ip6tables filter rules
reverse_nfqws_rule()
# $@ - iptables or ip6tables filter rules
```
Swaps iptables filter elements from the forward to the reverse direction (changes `dst` to `src`). The result is sent to stdout.
```
ipt()
ipta()
ipt_del()
ipt6()
ipt6a()
ipt6_del()
# $@ - iptables rule
ipt_add_del()
ipta_add_del()
ipt6_add_del()
ipt6a_add_del()
# $1 - 1 to add, 0 to delete
# $2+ - all other arguments define the iptables rule
```
A set of functions for managing iptables rules. All functions that add rules first check for the rule's existence; if it is already present, it will not be added. This is necessary to prevent rule duplication. All rules are prefixed with `$FW_EXTRA_PRE` and suffixed with `$FW_EXTRA_POST`. The suffix "6" indicates IPv6 support; its absence indicates IPv4.
All rules begin with the chain name, omitting iptables commands such as `-A`, `-D`, `-I`, etc.
- `ipt` - iptables -I - prepend to the beginning.
- `ipta` - iptables -A - append to the end.
- `ipt_del` - iptables -D - delete.
- `ipt_add_del` - add via -I or delete. `$1`: 1 for addition, 0 for deletion. The rule itself starts from `$2`.
- `ipta_add_del` - same as above, but uses -A instead of -I.
```
ipt_first_packets()
# $1 - number of packets or the string "keepalive"
```
Outputs to stdout: `-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes $RANGE`. `RANGE` is defined as "1:$1". If `$1` is "keepalive", nothing is output (no connbytes filter).
##### Working with nftables
```
nft_fw_nfqws_post()
nft_fw_nfqws_pre()
# $1 - nftables filter for ipv4
# $2 - nftables filter for ipv6
# $3 - queue number
```
Enables or disables redirection rules to the queue for nfqws2. Filters are written separately for IPv4 and IPv6 because they may contain version-specific elements that would trigger an error on the other IP version.
"post" refers to the chain for outgoing traffic, "pre" for incoming traffic. The chain type is selected based on the [POSTNAT](#config-file) variable. If you need a specific chain, you can override `POSTNAT` for a single call or as a local variable within the function.
Rules are prefixed with `$FW_EXTRA_PRE` and suffixed with `$FW_EXTRA_POST`.
What should NOT be included in the filter:
- Checks for standard exclude ipsets - `nozapret`, `nozapret6`.
- Checks based on [DESYNC_FWMARK](#config-file).
Whether rules are applied for each IP version depends on the [config](#config-file) settings.
```
zapret_do_firewall_standard_tpws_rules_nft()
```
Applies nftables rules for the [standard](#config-file) nfqws2 instance.
You can locally override [config](#config-file) variables and apply `FW_EXTRA_POST` and `FW_EXTRA_PRE`.
```
nft_filter_apply_ipset_target()
# $1 - name of the variable containing nftables filter rules for ipv4
# $2 - name of the variable containing nftables filter rules for ipv6
```
Adds checks for standard `zapret`/`zapret6` sets in the outbound direction (dst) to variables `$1` and `$2`.
```
nft_reverse_nfqws_rule()
# $@ - nftables filter rule
```
Reverses nftables filter elements from the forward to the reverse direction - replaces `dst` with `src`. The result is output to stdout.
```
nft_add_chain()
# $1 - chain name
# $2 - parameters inside { }
nft_delete_chain()
# $1 - chain name
nft_create_set()
# $1 - set name
# $2 - parameters inside { }
nft_del_set()
# $1 - set name
nft_flush_set()
# $1 - set name
nft_set_exists()
# $1 - set name
nft_add_set_element()
# $1 - set or map name
# $2 - element
nft_add_set_elements()
# $1 - set or map name
# $2,$3,... - elements
nft_flush_chain()
# $1 - chain name
nft_add_rule()
# $1 - chain name
# $2+ - nftables rule
nft_insert_rule()
# $1 - chain name
# $2+ - nftables rule
```
These functions operate on the `zapret2` table, allowing you to avoid direct `nft` calls with hardcoded table names. The names are self-explanatory.
```
nft_first_packets()
# $1 - number of packets or "keepalive"
```
Outputs to stdout: `ct original packets $RANGE`. `RANGE` is defined as "1-$1" if `$1 > 1`, or "1" if `$1 = 1`. If `$1` is "keepalive", nothing is output (no connbytes filter).
##### Additional functions
Many useful helpers are located in `common/base.sh`. Their purpose is easily understood from the code.
## Installer
Consists of the files `install_bin.sh`, `install_prereq.sh`, `install_easy.sh`, and `uninstall_easy.sh`. These utilize files from the `common` directory.
- `install_bin.sh` - an automated tool for finding and configuring architecture-appropriate binary files from the `binaries` folder. It creates symlinks in the `nfq2`, `mdig`, and `ip2net` directories pointing to files within one of the subdirectories in `binaries`. It is specifically designed for stripped-down firmware where many standard utilities are missing. While it works on almost everything, it is not 100% foolproof. The absence or limited functionality of a standard system utility might break the script-a common issue with unsupported firmwares like Padavan, Merlin, and similar. If it fails, create the symlinks manually.
- `install_prepreq.sh` - a prerequisite installer for the packages required for zapret to function. It works on OpenWRT and most Linux distributions, but not all, as package management systems and individual system configurations vary significantly. If the script fails, you must install the packages manually.
- `install_easy.sh` - the main installer. Designed to be run from any location. It operates in an interactive mode, prompting the user with questions. It automatically configures binaries and installs prerequisites (no need to run the previous scripts separately). On unsupported Linux systems, it cannot configure autostart-you will have to do this manually.
- `uninstall_easy.sh` - the uninstaller. It cannot remove autostart on unsupported systems. It offers to remove prerequisites only on OpenWRT; on other systems, it does not. It does not delete the installation directory itself. To remove it completely, you must delete it manually.
The individual files `install_bin.sh` and `install_prereq.sh` are useful when you do not intend to perform a full installation but need a working [blockcheck2](#blockcheck2) or want to run the [startup scripts](#startup-scripts) manually.
The installer always copies files to `/opt/zapret2` if the source is located elsewhere. When replacing the target directory, it deletes all existing files there. It can optionally preserve standard files: config, [custom scripts](#custom-scripts), [user lists](#standard-list-files) from ipset, and the autohostlist.
When copying to `/opt/zapret2`, it sets the correct permissions for files and directories, even if they were corrupted (for example, by repacking in Windows) in the source location. To run it from a location with broken permissions, simply call `sh install_easy.sh`.
Binary files are only present in releases. They are missing after a `git clone`. If the `binaries` folder is absent, the installer will attempt to compile them. To do this, it requires a C compiler, `make`, and several dev packages. See `docs/compile` for details.
The actions performed by the installer for integration with various Linux versions are described below. You can opt out of the installer by performing these steps manually and configuring the [config file](#config-file).
### OpenWRT integration principles
1. Autostart.
```
ln -s /opt/zapret2/init.d/openwrt/zapret2 /etc/init.d
/etc/init.d/zapret2 enable
```
2. Updating firewall rules on interface up/down events.
```
ln -s /opt/zapret2/init.d/openwrt/90-zapret2 /etc/hotplug.d/iface
```
3. (fw3 only) Firewall integration.
```
ln -s /opt/zapret2/init.d/openwrt/firewall.zapret2 /etc
uci add firewall include
uci set firewall.@include[-1].path="/etc/firewall.zapret"
uci set firewall.@include[-1].reload=1
uci commit
```
4. List updates - a cron job calling `/opt/zapret2/ipset/get_config.sh` at a random time during the night every 2 days to update lists. This assumes the router runs 24/7.
### OpenWRT cheat sheet
- Start service: `/etc/init.d/zapret2 start`
- Stop service: `/etc/init.d/zapret2 stop`
- Restart service: `/etc/init.d/zapret2 restart`
- Service status: `/etc/init.d/zapret2 status`
- Disable autostart: `/etc/init.d/zapret2 disable`
- Enable autostart: `/etc/init.d/zapret2 enable`
### systemd integration principles
1. Autostart
```
cp /opt/zapret2/init.d/systemd/zapret2.service /lib/systemd/system
systemctl daemon-reload
systemctl enable zapret2
```
2. List update timer - runs at a random time of day every 2 days. It is designed for any system, including desktops that may only be powered on during the day.
```
cp /opt/zapret2/init.d/systemd/zapret2-list-update.* /lib/systemd/system
systemctl daemon-reload
systemctl enable zapret2-list-update.timer
systemctl start zapret2-list-update.timer
```
### systemd cheat sheet
- Start service: `systemctl start zapret2`
- Stop service: `systemctl stop zapret2`
- Restart service: `systemctl restart zapret2`
- Service status: `systemctl status zapret2`
- Disable autostart: `systemctl disable zapret2`
- Enable autostart: `systemctl enable zapret2`
### OpenRC integration principles
1. Autostart
```
ln -s /opt/zapret2/init.d/openrc/zapret2 /etc/init.d
rc-update add zapret2
```
2. List updates - a cron job to call `/opt/zapret2/ipset/get_config.sh` at a random time during the day every 2 days. This is designed for any system, including desktops that may only be powered on during the day.
### OpenRC cheat sheet
- Start service: `rc-service zapret2 start`
- Stop service: `rc-service zapret2 stop`
- Restart service: `rc-service zapret2 restart`
- Service status: `rc-service zapret2 status`
- Disable autostart: `rc-update del zapret2`
- Enable autostart: `rc-update add zapret2`
## Alternative installation on systemd
On classic Linux distributions with systemd, you can use the provided template unit `init.d/systemd/nfqws2@.service` to run nfqws2 instances.
1. `cp /opt/zapret2/init.d/systemd/nfqws2\@.service /lib/systemd/system`
2. `systemctl daemon-reload`
3. `mkdir /etc/zapret2`
4. Create a text file in `/etc/zapret2` containing the nfqws2 command-line parameters. The file should be named `INSTANCE.conf`, where `INSTANCE` is a name of your choice.
5. Enable autostart: `systemctl enable nfqws2@INSTANCE`
6. Start: `systemctl start nfqws2@INSTANCE`
7. Stop: `systemctl stop nfqws2@INSTANCE`
8. Restart: `systemctl restart nfqws2@INSTANCE`
This method does not apply ip/nf tables rules - you will have to handle that separately, as well as write the rules themselves. The rules must be placed somewhere so they are applied after the system starts. For example, you can create a separate systemd unit that runs a shell script or `nft -f /path/to/file.nft`.
# Other firmwares
For static binaries, the host environment doesn't matter: PC, Android, set-top box, router, or any other device. Any firmware or Linux distribution will work; static binaries run on everything. They only require a kernel with the necessary build options or modules. However, in addition to binaries, the project uses scripts that rely on certain standard utilities.
Main reasons why you cannot simply install this system on any device out of the box:
* Lack of shell access to the device
* Lack of root privileges
* Absence of a r/w partition for writing and persistent file storage
* No way to add items to autostart
* Absence of cron
* Non-disableable flow offload or other proprietary netfilter implementations
* Missing kernel modules or specific kernel build options
* Missing iptables modules (`/usr/lib/iptables/lib*.so`)
* Missing standard utilities (like `ipset`, `curl`) or their "stripped-down" (lightweight) versions
* A limited or non-standard `sh` shell
If your firmware provides everything required, you can adapt zapret for your device to one degree or another.
Rebuilding the kernel or its modules will likely be quite difficult. To do this, you would at least need the source code for your firmware. User-mode components can be added relatively painlessly if there is a place to store them. For devices that have a r/w area, the Entware project exists. Some firmwares even offer a simplified installation of Entware via a web interface. Entware provides a repository of user-mode components that are installed in `/opt`. These can compensate for the lack of software in the base firmware, with the exception of the kernel.
You can attempt to use the [SysV init script](#startup-scripts).
If the system reports missing basic programs, you should install them via Entware.
Before running the script, the path to these additional programs must be added to your `PATH`.
Note that Entware is patched to replace standard file paths like `/etc/passwd` with `/opt/etc/passwd`.
Zapret's static binaries are built without accounting for this, so the `--user` option may not work-the system looks for the user in `/etc/passwd` (which is on a read-only partition), while `adduser` adds users to `/opt/etc/passwd`.
Therefore, you may need to uncomment `WS_USER` in the [config](#config-file) and specify a user that already exists in `/etc/passwd`.
For Keenetic devices, there are additional critical nuances; without addressing them, `zapret` will either crash every few minutes or fail to work with UDP.
There are ready-made third-party solutions available for Keenetic integration.
A detailed description of settings for other firmwares is beyond the scope of this project.
OpenWrt is one of the few relatively full-featured Linux systems for embedded devices.
It was chosen for this project based on the following characteristics:
* Full root access to the device via shell. This is usually absent in factory firmwares but present in many alternative ones.
* Read/write (r/w) root partition. This is a nearly unique feature of OpenWrt. Factory and most alternative firmwares are built on a squashfs root (r/o), where configurations are stored in a specially formatted area of internal memory called NVRAM. Systems without an r/w root are severely limited; they cannot install software from repositories without complex workarounds and are primarily designed for users who are only slightly more advanced than average, offering fixed functionality managed through a web interface. Alternative firmwares can usually mount an r/w partition to some area of the file system, while factory firmwares typically only mount USB drives and even then, they may only support FAT and NTFS rather than Unix file systems.
* The ability to move the root file system to external media (extroot) or create an overlay on it.
* The presence of the `opkg` package manager and a software repository.
* Flow offloading is predictably, standardly, and selectively manageable, as well as disableable.
* All kernel modules are available in the repository and can be installed via `opkg`, eliminating the need to recompile the kernel.
* All `iptables` modules are available in the repository and can be installed via `opkg`.
* The repository contains a vast number of standard programs and additional software.
* The availability of an SDK, allowing you to compile any missing components.
# Windows
zapret was initially written for unix. It uses unix emulation layer cygwin on Windows to help migration.
If winws2 is run standalone cygwin1.dll is required in it's directory. If winws2 is run inside cygwin environment - cygwin1.dll must NOT be present in it's directory or it won't run.
cygwin emulate shared PID namespace and deliver signals only within one cygwin1.dll instance !
To send signals sending program (kill, killall) must be run with the same cygwin1.dll as winws2.
It's possible to install cygwin and use winws2 inside cygwin installation.
But it may be more comfortable to use [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle) which includes minimal cygwin.
cygwin prompt is pre-configured with aliases for blockcheck, blockcheck2, winws, winws2, winws2 with standard Lua scripts.
32-bit Windows versions are not supported by zapret-win-bundle.
## Windows 7
Requirements for windows driver signing have changed in 2021.
Official free updates of windows 7 ended in 2020.
After 2020 for the years paid updates were available (ESU).
One of the updates from ESU enables signatures used in windivert 2.2.2-A.
There are several options :
1. Take `windivert64.sys` and `windivert.dll` version `2.2.0-C` or `2.2.0-D` from [here](https://reqrypt.org/download) and replace existing files.
2. [Hack ESU](https://hackandpwn.com/windows-7-esu-patching)
3. Use "BypassESU" patcher. Google it by name.
4. Use [UpdatePack7R2](https://blog.simplix.info). If you are in Russia or Belarus temporary change region in Control Panel.
## Windows Server
winws2 is linked against wlanapi.dll which is absent by default.
To solve this problem run power shell as administrator and execute command `Install-WindowsFeature -Name Wireless-Networking`.
Then reboot the system.
## Windows ARM64
The main problem is lack of a signed windivert driver. Therefore, it is required to enable the test signature mode: 'bcdedit/set {current} testsigning on'.
There's unsigned WinDivert64.sys driver in [zapret-win-bundle](https://github.com/bol-van/zapret-win-bundle).
There is also cmd file for rolling the driver to the arm64 Win11.
Another problem is lack of cygwin for non-x86 platforms. However, win11 has x64 emulation, so you can use the x64 build, but with replaced WinDivert64.sys driver.
There is no need to replace WinDivert.dll - only WinDivert64.sys needs to be replaced. It's verified and confirmed working.
There is no x64 emulation on win10 arm64, but there is x86 32-bit emulation.
Therefore, theoretically you can use the win32 variant and put the WinDivert64.sys driver for arm64 to the same dir.
This hasn't been verified.