Freedom — Xray-core
Freedom is Xray's bypass outbound — it sends traffic to the real destination without any proxy protocol in the middle. It also carries two anti-DPI features: TCP/TLS fragmentation and arbitrary noise-packet injection.
Outbound
settings for an outbound of "protocol": "freedom":
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
targetStrategy | string | asis | asis | useip | useipv4 | useipv6 | useipv4v6 | useipv6v4 | forceip | forceipv4 | forceipv6 | forceipv4v6 | forceipv6v4 | How the destination address is resolved before dial-out. `asis` passes through whatever the routing engine handed in; `useip*` resolves to IP only if needed; `forceip*` always resolves and replaces. The `*v4v6` / `*v6v4` suffixes control which family is tried first. |
domainStrategy | string | (inherits targetStrategy) | <same values> | Legacy alias for `targetStrategy`. Read only when `targetStrategy` is empty. |
redirect | string | (unset) | <host:port> | Override the destination of every dial. Useful for forwarding to a local service regardless of routing. |
userLevel | uint32 | 0 | <uint32> | Default policy level for connections passing through this outbound. |
fragment | *Fragment | (disabled) | Fragment | Per-segment TCP fragmentation for evading length-based DPI. |
noise | *Noise | (removed) | (use noises) | Removed field. Use the `noises` array. Setting `noise` triggers an explicit error at config build. |
noises | []*Noise | (unset) | [Noise] | List of noise packets sent ahead of (or between) real packets to throw off pattern matching. |
proxyProtocol | uint32 | 0 | 0 | 1 | 2 | Prepend a PROXY-protocol header on outgoing connections. 0 disables; 1 / 2 set v1 (text) or v2 (binary). |
ipsBlocked | *StringList | (removed) | (use finalRules) | Removed field. Migrated to `finalRules`; setting it only logs a warning and has no effect. |
finalRules | []*FreedomFinalRuleConfig | (unset) | [FreedomFinalRuleConfig] | Per-connection allow/block rules evaluated at dial time — a lightweight firewall on the outbound itself, matched by network / port / IP. |
Source: infra/conf/freedom.go:19-30 · pinned at v26.6.1 (94ffd50)
fragment
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
packets | string | (all) | tlshello | <from-to> | | Which packets to fragment. `tlshello` fragments only the TLS ClientHello (subtler, useful for SNI obfuscation). A range like `"1-3"` fragments packets at that offset range. Empty fragments every packet — most aggressive. |
length | *Int32Range | (required) | {from, to} | Range of bytes per fragment. Min must be > 0. |
interval | *Int32Range | (required) | {from, to} | Range of milliseconds to delay between fragments. |
maxSplit | *Int32Range | (unbounded) | {from, to} | Range of total splits per packet. Optional cap. |
Source: infra/conf/freedom.go:32-37 · pinned at v26.6.1 (94ffd50)
noises[]
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
type | string | (required) | rand | str | hex | base64 | Packet payload form. `rand` generates random bytes whose length comes from `packet` (a range string). `str`/`hex`/`base64` interpret `packet` literally. |
packet | string | (required) | <bytes or range> | Payload content. Format depends on `type`. |
delay | *Int32Range | (unset) | {from, to} | Range of milliseconds to wait before sending this noise packet. |
applyTo | string | ip | ip | all | ipv4 | ipv6 | Restrict noise injection to a single IP family. `ip` and `all` apply to both. |
Source: infra/conf/freedom.go:39-44 · pinned at v26.6.1 (94ffd50)
finalRules[]
A small allow/block firewall the freedom outbound applies as it dials out. Rules are evaluated in order; the first match decides. This replaces the removed ipsBlocked field.
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
action | string | (required) | allow | block | What to do with a matching connection. `block` refuses it; `allow` lets it through — list `allow` rules first to carve exceptions out of a broader `block`. |
network | *NetworkList | (any) | tcp | udp | tcp,udp | Match only these networks. |
port | *PortList | (any) | <port / range list> | Match only these destination ports. |
ip | *StringList | (any) | <IP / CIDR / geoip:> | Match only these destination IPs. Accepts `geoip:` categories. |
blockDelay | *Int32Range | (immediate) | {from, to} | For a `block` action, wait a random delay in this millisecond range before dropping the connection — stalls a prober instead of failing fast. |
Source: infra/conf/freedom.go:46-52 · pinned at v26.6.1 (94ffd50)
Examples
Plain freedom outbound (passthrough):
{
"outbounds": [
{ "tag": "direct", "protocol": "freedom", "settings": {} }
]
}Force IPv4-then-IPv6 resolution:
{
"outbounds": [
{
"tag": "direct-v4",
"protocol": "freedom",
"settings": { "targetStrategy": "forceipv4v6" }
}
]
}TLS-hello fragmentation (good first try for SNI obfuscation):
{
"outbounds": [
{
"tag": "frag",
"protocol": "freedom",
"settings": {
"fragment": {
"packets": "tlshello",
"length": { "from": 10, "to": 20 },
"interval": { "from": 10, "to": 20 }
}
}
}
]
}Noise injection (4 random bytes ahead of every IPv4 connection, delayed 0-1 ms):
{
"outbounds": [
{
"tag": "noisy",
"protocol": "freedom",
"settings": {
"noises": [
{
"type": "rand",
"packet": "4-4",
"delay": { "from": 0, "to": 1 },
"applyTo": "ipv4"
}
]
}
}
]
}Block QUIC (UDP/443) outright, and stall connections to private ranges to slow down probing:
{
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"finalRules": [
{ "action": "block", "network": "udp", "port": "443" },
{ "action": "block", "ip": ["geoip:private"], "blockDelay": { "from": 1000, "to": 3000 } }
]
}
}
]
}Forward everything to a local service (testing helper):
{
"outbounds": [
{
"tag": "redir",
"protocol": "freedom",
"settings": { "redirect": "127.0.0.1:8080" }
}
]
}Notes
targetStrategyis the modern field;domainStrategyis read only as a fallback whentargetStrategyis empty (infra/conf/freedom.go:46-48). New configs should set onlytargetStrategy.- Unknown strategy strings produce a startup error rather than a silent fallback (
infra/conf/freedom.go:72-73). noise(singular) is removed. The error message at startup tells you to migrate tonoises = [ { ... } ](infra/conf/freedom.go:145-147).ipsBlockedis removed and folded intofinalRules. Replace it with a{ "action": "block", "ip": [...] }rule; the old field now only logs a warning and is otherwise ignored.finalRulesactions areallow/blockonly (unknown actions error at startup).ipacceptsgeoip:categories;blockDelayapplies solely toblockrules.fragment.packets: "tlshello"is the recommended starting point — fragmenting only the TLS ClientHello breaks SNI-based DPI but leaves the rest of the connection untouched (the byte-overhead is one-time).fragment.length.fromandfragment.intervalare required whenfragmentis present (infra/conf/freedom.go:102-118). ThemaxSplitrange is optional.proxyProtocoloutside1..2is silently ignored — only those two values produce header output (infra/conf/freedom.go:162-164).
Cross-core notes
- sing-box calls this Direct and the outbound has almost no options of its own — destination overrides were removed in 1.13; use route actions for that behavior. See Direct — sing-box.
- mihomo has a similarly minimal Direct type; the built-in
DIRECTproxy is implicit in every config. See Direct — mihomo.
Source: infra/conf/freedom.go:19-52 · v26.6.1 (94ffd50)
