Skip to content

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":

FieldTypeDefaultAllowed valuesDescription
targetStrategystringasisasis | useip | useipv4 | useipv6 | useipv4v6 | useipv6v4 | forceip | forceipv4 | forceipv6 | forceipv4v6 | forceipv6v4Deprecated — at startup a non-asis value is moved to streamSettings.sockopt.domainStrategy (same values) with a warning; set that instead. 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.
domainStrategystring(inherits targetStrategy)<same values>Legacy alias for targetStrategy. Read only when targetStrategy is empty. Deprecated together with targetStrategy.
redirectstring(unset)<host:port>Override the destination of every dial. Useful for forwarding to a local service regardless of routing.
userLeveluint320<uint32>Default policy level for connections passing through this outbound.
fragment*Fragment(disabled)FragmentPer-segment TCP fragmentation for evading length-based DPI.
noise*Noise(unsupported)(use noises)Not supported — use the noises array. Setting noise is 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.
proxyProtocoluint3200 | 1 | 2Prepend a PROXY-protocol header on outgoing connections. 0 disables; 1 / 2 set v1 (text) or v2 (binary).
ipsBlocked*StringList(ignored)(use finalRules)Not supported — express IP blocks as 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. Ignored (with a warning) when streamSettings.sockopt.dialerProxy is set.

Source: infra/conf/freedom.go:19-30 · pinned at v26.9.9 (52a412d)

fragment ​

FieldTypeDefaultAllowed valuesDescription
packetsstring(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.9.9 (52a412d)

noises[] ​

FieldTypeDefaultAllowed valuesDescription
typestring(required)rand | str | hex | base64Packet payload form. rand generates random bytes whose length comes from packet (a range string). str/hex/base64 interpret packet literally.
packetstring(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.
applyTostringipip | all | ipv4 | ipv6Restrict noise injection to a single IP family. ip and all apply to both.

Source: infra/conf/freedom.go:39-44 · pinned at v26.9.9 (52a412d)

finalRules[] ​

A small allow/block firewall the freedom outbound applies as it dials out. Rules are evaluated in order; the first match decides. IP blocking belongs here — the ipsBlocked field is ignored.

FieldTypeDefaultAllowed valuesDescription
actionstring(required)allow | blockWhat 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,udpMatch 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.9.9 (52a412d)

Examples ​

Plain freedom outbound (passthrough):

json
{
  "outbounds": [
    { "tag": "direct", "protocol": "freedom", "settings": {} }
  ]
}

Force IPv4-then-IPv6 resolution:

json
{
  "outbounds": [
    {
      "tag": "direct-v4",
      "protocol": "freedom",
      "settings": {},
      "streamSettings": { "sockopt": { "domainStrategy": "forceipv4v6" } }
    }
  ]
}

TLS-hello fragmentation (good first try for SNI obfuscation):

json
{
  "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):

json
{
  "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:

json
{
  "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):

json
{
  "outbounds": [
    {
      "tag": "redir",
      "protocol": "freedom",
      "settings": { "redirect": "127.0.0.1:8080" }
    }
  ]
}

Notes ​

  • targetStrategy / domainStrategy in freedom settings are deprecated. At startup Xray moves a non-asis value into streamSettings.sockopt.domainStrategy and logs a warning; an outbound-level targetStrategy on a freedom outbound is migrated the same way (infra/conf/xray.go:341-364). The migrated value overwrites a sockopt.domainStrategy you set yourself, so configure only one of them. domainStrategy is still read only as a fallback when targetStrategy is empty (infra/conf/freedom.go:46-48).
  • With finalRules and a domain destination, freedom first resolves the domain (via sockopt.domainStrategy, or the system resolver when that is AsIs) to check block rules, then re-checks the address it actually connected to.
  • When streamSettings.sockopt.dialerProxy is set, freedom is not the final hop: finalRules are ignored (a warning is logged) and the domain is not pre-resolved.
  • Freedom rejects streamSettings.sockopt.addressPortStrategy at startup.
  • Unknown strategy strings produce a startup error rather than a silent fallback (infra/conf/freedom.go:72-73).
  • noise (singular) is not supported: setting it fails at startup with a message pointing to noises = [ { ... } ] (infra/conf/freedom.go:145-147).
  • ipsBlocked is not supported — express it as a { "action": "block", "ip": [...] } rule in finalRules; the field itself only logs a warning and is otherwise ignored.
  • finalRules actions are allow / block only (unknown actions error at startup). ip accepts geoip: categories; blockDelay applies solely to block rules.
  • 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.from and fragment.interval are required when fragment is present (infra/conf/freedom.go:102-118). The maxSplit range is optional.
  • proxyProtocol outside 1..2 is 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 — it has no destination-override fields; use route actions for that behavior. See Direct — sing-box.
  • mihomo has a similarly minimal Direct type; the built-in DIRECT proxy is implicit in every config. See Direct — mihomo.

Source: infra/conf/freedom.go:19-52 · v26.9.9 (52a412d)

Core Tutorial by Argsment