Preface
The YtFlowCore Book is prepared for those who want to customize their YtFlowCore (or its UI front ends like YtFlowApp) Profiles.
Prerequisites
To edit YtFlow profiles, you may need a dedicated editor. YtFlowApp has a built-in configuration editor. The ytflow-edit
TUI tool from YtFlowCore also works.
Although we are trying to minimize necessary prior knowledge, understanding of network fundamentals can greatly improve your experience while exploring YtFlow. You are not expected to be a network expert (while some of you already are) to get started, as long as you can tell the difference between TCP and UDP.
Since we present plugin parameters with JSON, you will need to get familiar with the JSON format. Can you spot a mistake in this JSON document?
#![allow(unused)] fn main() { use serde_json::{from_str, Value}; let s = r#" { "host": "localhost", "port": 1080, "password": "password", "tcp_next": "socket", } "#; println!("{:?}", from_str::<Value>(&s[1..])); }
About This Book
The YtFlowCore Book is open source and available at GitHub. Feel free to contribute by submitting a new issue to request clarification, or making pull requests to fix typos.
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Introduction
YtFlowCore is a modern proxy framework that allows heavy customization. You can design your own network flow by chaining plugins and tuning parameters. Expert users will find it interesting to play around with custom profiles, while novice users may choose to benefit from preset configurations that our UI front end, such as YtFlowApp, has set up for you.
To achieve maximum flexibility, the plugins are designed with KISS principle in mind. Sometimes, you will find it cumbersome to deploy so many plugins, only to achieve something appears as a simple button in other apps. However, such explicitness avoids confusion when you debug and customize your network. For example, the FakeIP infrastructure can be decomposed into:
- A simple dispatcher that hijacks all DNS requests,
- A FakeIP DNS resolver, providing fake IP addresses and corresponding domain names, and
- A DNS server, acting as 'FakeDNS', and responsible for translating mapped FakeIPs back into domain names from incoming connections.
These plugins are fully configurable, and work intuitively in your profile. Let's say you want to implement split routing based on countries/regions of destination addresses using rule-dispatcher
. In order to lookup geolocations from the database, you will need a real-IP DNS resolver to get IP addresses associated with domain names. You may ask, "will the real-IP DNS resolver mess up with the FakeIP one?" The answer is, no. In this profile, a destination address will only be overwritten by the destination resolver, while DNS resolvers do no change the destination address. You will learn more about plugins in Chapter 4.
Revision History
- 2023-06-05: Added link to
rule-dispatcher
.
Basics
In this chapter, we will cover basic concepts in YtFlowCore.
Profile and Plugin
Profile and Plugin are two core entities in YtFlow. When YtFlowCore launches, it retrieves the specified profile from the database, along with all plugins included in the profile. Then, the plugins are loaded and started to respond to incoming connections. Errors occur during parsing or loading plugins will be reported to the user. In this case, YtFlowCore may or may not reject running the profile, depending on the implementation.
Profile
A profile consists of a collection of plugins that collectively defines runtime behavior of YtFlowCore. There is not too much interesting fields inside a profile itself. Instead, you may customize the network flow by modifying the plugins.
Remember that a YtFlowCore instance only runs one profile at a time. If you want to switch to a different profile, you will need to restart the YtFlowCore instance, or reconnect through the UI.
Plugin
A plugin in a profile is an atomic instance of certain functions, such as stream encoding and decoding, destination rewrite, and dispatching to different plugins. They are designed to be as small as possible, so that you can make the most of them by chaining them in a creative way.
You may have already noticed that outbound client plugins, such as shadowsocks-client
and socks5-client
, care nothing about which address or port your proxy server is running on. They only encode destination addresses into the streams, but do not change the destination. To redirect all streams to the proxy server, you will need a redirect
plugin.
Note that plugins are a integrated part of YtFlowCore. Currently, there is no such mechanism to dynamically load a new type of plugin from a shared library.
Parameters
To customize a plugin, you modify the parameters of it. For each plugin, the parameters are encoded in a CBOR object. You don't have to know what CBOR exactly is, because the editor will automatically convert CBOR to JSON and vice versa. Sometimes you will see such object in the editor:
{
"__byte_repr": "utf8",
"data": "my_trojan_password"
}
Don't be scared. Most of the time, you can treat them as strings with content of the data
field. You see this because the editor encountered a binary buffer field, which may possibly contain non-UTF-8 data. In this case, the actual data is displayed in Base64 format.
{
"__byte_repr": "base64",
"data": "3q2+7w=="
}
A plugin can have many access points. Plugins chain to each other by specifying names and access points of other plugins in its parameters.
Entry Plugin
Some plugins like socket-listener
and ip-stack
are made to work as entry plugins. They are supposed to be the entry points of the whole profile, and therefore do not provide any access point.
In the editor, you can set a plugin as entry plugin, or unset to deactivate it. There can be multiple entry plugins in a profile. Note that every plugin can be an entry plugin, but most plugins will have no effect at runtime.
Access Point and Descriptor
An access point is like a "listening port" of a plugin. A plugin can expose access points for other plugins to connect to, and connect to access points of other plugins as well. A descriptor specifies the plugin name and the access point that a plugin connects to.
Types of Access Point
Access points are typed, which means you cannot arbitrarily connect a plugin to an access point. Currently, there are 6 types of access points:
- Stream Handler
- Datagram Session Handler
- Stream Outbound
- Datagram Session Outbound
- Resolver
- TUN
We will discuss these types and explain why they are categorized as such in the following sections.
Streams, Stream Handler and Stream Outbound
In YtFlowCore, a TCP connection is represented by a Stream. A plugin that work with TCP streams can implement a new Stream on top of an underlying Stream to receive and send protocol headers (e.g. SOCKS5), or to encrypt and decrypt data (e.g. Shadowsocks).
A Stream Handler handles streams. When a Stream is produced by an upstream plugin, it is passed to the Stream Handler. The Stream Handler may do some I/O, wrap a new Stream around it, pass it to a downstream Stream Handler, or even terminate the Stream. Stream Handlers are often used on the inbound side.
A Stream Outbound works the other way around. It initiates streams. An upstream plugin asks the Stream Outbound for a new Stream upon request. Then, the Stream Outbound creates a new Stream, or reuses a Stream from a downstream Stream Outbound, and returns it to the upstream plugin. Stream Outbounds are often used on the outbound side.
To exchange data from two Streams, a forward
plugin is used. It exposes a Stream Handler and requires a Stream Outbound to work. When a Stream is received from the Stream Handler access point, the plugin requests a new Stream from the Stream Outbound, and then forwards data between the two streams. Note that forward
only forwards data, and does nothing about outbound selection, routing or DNS resolution.
Datagram Sessions, Datagram Session Handler and Datagram Session Outbound
As you may have guessed, Datagram Session is the UDP counterpart of Stream. A Datagram Session represents a UDP receipient, to which a plugin can send or receive datagrams.
Datagram Session Handler and Datagram Session Outbound work the same way as Stream Handler and Stream Outbound, but they deal with Datagram Sessions. forward
is also used to forward datagrams between two Datagram Sessions.
Resolver
A Resolver can be used to fulfill requests related to domain names, such as resolving domain names to/from IP addresses, or possibly other type of requests like TXT or SRV. The requests and responses use internal representations, thus a Resolver does not necessarily work with raw DNS payloads directly. Rather, a dns-server
should be used to serve DNS messages and host-resolver
should be used to send requests to a real DNS server.
TUN
A TUN is used to send and receive Layer 3 Raw IP packets. Currently, only ip-stack
requires a TUN to extract Streams and Datagram Sessions.
Descriptor
Descriptors are used to locate access points. Usually, it is the combination of a plugin name and a predefined short string, separated by a dot. For example, trojan-outbound.tcp
may refer to the Stream Outbound access point of a trojan-client
plugin called trojan-outbound
.
Moreover, a descriptor may refer to an access point with different types. For example, a socket
plugin called phy-socket
has a descriptor named phy-socket
, which is both a Stream Outbound and Datagram Session Outbound.
Revision History
- 2023-04-29: Removed Netif.
- 2023-06-05: Removed Netif in Access Point list.
Recipes
This chapter provides a few sample profiles for common scenarios. You will develop an intuition about how YtFlowCore plugins works. The recipes can be used as a starting point to build your own network flow as well.
Classic Proxy Client
Objectives
- Build a Shadowsocks Client with a SOCKS5 inbound, akin to the classic Shadowsocks client.
- Focus on TCP only.
Plugins
┌──────────┐ ┌───────────────┐ ┌──────────────┐ ┌───────────┐
│ │ │ │ │ │ │ │
│ listener │ tcp │ socks5-server │ tcp │ main-forward │ tcp │ ss-client │
│ ├────►│ ├────►│ ├────►│ │
└──────────┘ └───────────────┘ └──────────────┘ └────┬──────┘
│
┌──────────────┐ │
│ │ │
│ sys-resolver │ │ tcp
│ │ │
└───▲──────────┘ │
│ resolver │
┌─┴──────────┐ ┌──────▼──────┐
│ │ │ │
│ phy-socket │ tcp │ proxy-redir │
│ │◄────┤ │
└────────────┘ └─────────────┘
listener
(socket-listener
)
{
"tcp_listen": ["127.0.0.1:1080"],
"udp_listen": [],
"tcp_next": "socks5-server.tcp",
"udp_next": "reject.udp"
}
socks5-server
(socks5-server
)
{
"tcp_next": "main-forward.tcp",
"udp_next": "reject.udp"
}
main-forward
(forward
)
{
"tcp_next": "ss-client.tcp",
"udp_next": "null.udp"
}
ss-client
(shadowsocks-client
)
{
"method": "aes-128-gcm",
"password": {
"__byte_repr": "utf8",
"data": "my_ss_password"
},
"tcp_next": "proxy-redir.tcp",
"udp_next": "null.udp"
}
proxy-redir
(redirect
)
{
"dest": {
"host": "my.proxy.server.com.",
"port": 8388
},
"tcp_next": "phy-socket",
"udp_next": "null.udp"
}
phy-socket
(socket
)
{
"resolver": "sys-resolver.resolver"
}
sys-resolver
(system-resolver
)
null
reject
(reject
)
null
null
(null
)
null
Explanation
listener
binds to a local host endpoint and listens for incoming connections.- For each incoming connection,
socks5-server
performs a SOCKS5 handshake and obtains a destination address from the SOCKS5 request, saygoogle.com:443
. main-forward
initiates a new connection withss-client
.- Since
ss-client
has encoded the destination address into the Shadowsocks protocol,proxy-redir
will redirect the connection to the proxy address (my.proxy.server.com.:8388
). Otherwise, encrypted Shadowsocks payload will be sent directly togoogle.com:443
, which is not expected. phy-socket
resolves the IP address ofmy.proxy.server.com.
using asystem-resolver
.- The Shadowsocks server at
my.proxy.server.com.:8388
receives the request and starts relay betweengoogle.com:443
and your PC.
Revision History
- 2023-04-29: Removed
phy
. - 2024-04-05: Fixed reference to
resolve-dest
inproxy-redir
.
Plugins
This chapter includes usages, parameters and examples of the plugins in YtFlowCore. You may scan through these descriptions, and refer back to them as a reference when you are editing a profile.
Unless otherwise specified, all parameters are required.
dns-server
Respond to DNS request messages using results returned by the specified resolver. Also provides domain name lookup (map_back
) for resolved IP addresses.
Access Points
udp
: Datagram Session Handler. DNS messages are exchanged over the Datagram Session.tcp_map_back.[tcp_map_back]
: Stream Handlers for each descriptor defined intcp_map_back
. Streams with IP addresses as their destinations are redirected to the corresponding domain name based on DNS resolution history.udp_map_back.[udp_map_back]
: Datagram Session Handlers for each descriptor defined inudp_map_back
. Datagrams with IP addresses as their destinations are redirected to the corresponding domain name based on DNS resolution history.
Parameters
{
"concurrency_limit": 64,
"ttl": 60,
"resolver": "fake-ip.resolver",
"tcp_map_back": ["main-forward.tcp"],
"udp_map_back": ["main-forward.udp"]
}
concurrency_limit
: Maximum number of DNS requests the plugin can handle at a time. Exceeding this limit will cause new requests to wait.ttl
: Time-to-live (TTL) of DNS responses.resolver
: Descriptor of the Resolver to query DNS records.tcp_map_back
: List of descriptors of Stream Handlers. Each descriptor specified here will become an access point of the plugin (e.g.tcp_map_back.main-forward.tcp
), using which a stream with an IP address as its destination can be redirected to the corresponding domain name based on DNS resolution history, and passed down to the specified access point.udp_map_back
: List of descriptors of Datagram Session Handlers. Each descriptor specified here will become an access point of the plugin (e.g.udp_map_back.main-forward.udp
), using which a datagram with an IP address as its destination can be redirected to the corresponding domain name based on DNS resolution history, and passed down to the specified access point.
Revision History
- 2023-04-29: Added
map_back
related content.
dyn-outbound
Select an outbound proxy from the database at runtime.
This plugin requires a database file to work. If a database file is not provided, it will fail to load.
Access Points
tcp
: Stream Outbound.udp
: Datagram Session Outbound.
Parameters
{
"tcp_next": "ss-client.tcp",
"udp_next": "null.udp"
}
tcp_next
: Descriptor of the Stream Outbound for proxies to establish new outbound Streams.udp_next
: Descriptor of the Datagram Session Outbound for proxies to establish new outbound Datagram Sessions.
Details
Proxies in the database are managed by the UI application. During runtime, a user can select a proxy from the UI via RPC. The selection will be stored in the database so that the plugin will automatically load the selected proxy on next startup.
If a selected proxy cannot be loaded during plugin initialization, all incoming connections will be rejected until another proxy is selected and loaded successfully.
Revision History
- 2023-04-29: Added
dyn-outbound
.
fake-ip
Assign a fake IP address for each domain name. This is useful for TUN inbounds where incoming connections carry no information about domain names.
Access Points
resolver
: Resolver. Resolve domain names to fake IP addresses.
Parameters
{
"prefix_v4": [11, 17],
"prefix_v6": [38, 12, 32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
"fallback": "real-ip.resolver"
}
prefix_v4
: Specify the first two bytes of the fake IPv4 addresses. Forprefix_v4: [11, 17]
, the first IPv4 address to assign will be11.17.0.1
.prefix_v6
: Specify the first 14 bytes of the fake IPv6 addresses. Forprefix_v6: [38, 12, 32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
, the first IPv6 address to assign will be260c:2001::1:1
.fallback
: Descriptor of the fallback Resolver. Used to retrieve other types of Resource Records such as SRV and TXT. Not used as of YtFlowCore version 0.1.
Revision History
- 2023-04-29: Removed
map_back
related content.
forward
Establish a new connection for each incoming connection, and forward data between them.
Access Points
tcp
: Stream Handler. Forward data between incoming Streams and newly established outbound Streams.udp
: UDP Stream Handler. Forward datagrams between incoming Datagram Sessions and newly established outbound Datagram Sessions.
Parameters
{
"request_timeout": 200,
"tcp_next": "ss-client.tcp",
"udp_next": "ss-client.udp"
}
request_timeout
(optional, defaults to100
): Specify how long (in milliseconds) to wait for initial data from the inbound Stream.0
means do not wait for initial data, and establish outbound connections immediately. This option does not apply to Datagram Sessions. See Details for more information.tcp_next
: Descriptor of the Stream Outbound to establish new outbound Streams.udp_next
: Descriptor of the Datagram Session Outbound to establish new outbound Datagram Sessions.
Details
To enable potential 0-RTT capability for the entire outbound link, forward
plugin tries reading some data from the inbound Stream before establishing an outbound Stream, and pass the data all the way to downstream Stream Outbounds. Thus, downstream plugins can take advantage of TLS 1.3 0-RTT or TCP Fast Open features. In most cases, this is OK because common Layer 7 protocols like HTTP or TLS send request data immediately after establishing a connection.
host-resolver
Resolve real IP addresses by querying DNS servers.
Access Points
resolver
: Resolver. Resolve domain names to real IP addresses.
Parameters
{
"udp": ["redir-8888.udp", "redir-8844.udp"],
"tcp": ["redir-8888.tcp", "redir-8844.tcp"],
"doh": [{
"url": "https://1.1.1.1/dns-query",
"next": "tls-client.tcp"
}]
}
udp
: Descriptor of the Datagram Session Outbounds to send DNS requests.tcp
: Descriptor of the Stream Outbounds to send DNS requests. Not used as of YtFlowCore 0.1.doh
(optional): Descriptors of RFC 8484 DNS-over-HTTPS endpointsdoh[].url
: absolute URL of the endpoint. Outbound requests (in plaintext) will be sent to this URL. Forhttps
URLs, atls-client
plugin should be present in the outbound chain specified bydoh[].next
. Cautious should be taken when using domain names in the host part, which may cause infinite loops if the outbound chain references the plugin itself. Aredirect
plugin is not needed as the Stream destination is set to the authority part of the URL.doh[].next
: Descriptor of the Stream Outbound to establish new outbound Streams.
Details
The host-resolver
plugin is powered by trust_dns_resolver
.
The DNS-over-HTTPS functionality is powered by hyper
HTTP Client. Both h2
and http/1.1
are supported. h2
will be used when selected by the lower layer tls-client
plugin through ALPN.
Revision History
- 2023-04-29: Removed
map_back
related content. - 2023-06-05: Added
doh
.
http-obfs-client
simple-obfs HTTP client.
Access Points
tcp
: Stream Outbound. Returns a Stream handshaked with HTTP headers.
Parameters
{
"host": "dl.microsoft.com",
"path": "/",
"next": "proxy-redir.tcp"
}
host
: Specify the value ofHost
HTTP header.path
: Specify the path of the HTTP request.next
: Descriptor of the Stream Outbound to perform HTTP obfuscation.
Details
This plugin is compatible with simple-obfs, and does not perform HTTP response checking or real WebSocket handshaking. It simply appends an HTTP request header before the Stream, and discards the response header.
Example of a generated HTTP response:
GET / HTTP/1.1
Host: dl.microsoft.com
User-Agent: curl/7.5.0
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Key: b7ZPFMiCJm6AZJ3dPx9OcQ==
Content-Length: 233
[233 bytes payload]
http-obfs-server
simple-obfs HTTP server.
Access Points
tcp
: Stream Handler. Consumes an HTTP request from a Stream and reply with an HTTP response.
Parameters
{
"next": "forward.tcp"
}
next
: Descriptor of the Stream Handler to consume the Stream handshaked with HTTP headers.
Details
This plugin is compatible with simple-obfs. It extracts the value of Sec-Websocket-Key
from HTTP request headers and replies with HTTP response headers. It does not perform real WebSocket handshaking.
http-proxy-client
HTTP Proxy client. Use HTTP CONNECT to connect to the proxy server.
Access Points
tcp
: Stream Outbound. Initiates an HTTP CONNECT request on the stream, and consumes the response.
Parameters
{
"user": {
"__byte_repr": "utf8",
"data": ""
},
"pass": {
"__byte_repr": "utf8",
"data": ""
},
"tcp_next": "proxy-redir.tcp"
}
user
: Specify the user name of the credential. An empty string disables authentication.pass
: Specify the password of the credential. An empty string disables authentication.tcp_next
: Descriptor of the Stream Outbound to send HTTP proxy requests.
ip-stack
Handle TCP or UDP connections from a TUN.
Access Points
No access point is provided. This plugin should be used as entry plugin.
Parameters
{
"tun": "uwp-vpn-tun.tun",
"tcp_next": "rev-resolver.tcp",
"udp_next": "dns-dispatcher.udp"
}
tun
: Descriptor of the TUN device to send and receive packets.tcp_next
: Descriptor of the Stream Handler.udp_next
: Descriptor of the Datagram Session Handler.
Details
ip-stack
is similar to tun2socks
. It encapsulates Streams and Datagram Sessions from raw IP packets flow through the underlying TUN interface.
Currently the endpoint IPv4 address is a hardcoded value
192.168.3.1
and the endpoint IPv6 address is a hardcoded valuefd00::2
.
Revision History
- 2023-06-05: Update IPv6 address.
list-dispatcher
Match the connection against a list of matchers defined in a resource, and use the handler of the action or fallback handler if there is no match.
Access Points
tcp
: Stream Handler defined inaction.tcp
and selected by the rules, orfallback.tcp
if none matches.udp
: Datagram Session Handler defined inaction
and selected by the rules, orfallback.udp
if none matches. Dispatching applies to Datagram Sessions only. Datagrams flowing through are not inspected.resolver
: Resolver defined inactions
and the requesting domain name is selected by the rules, orfallback.resolver
if none matches.
Parameters
{
"source": "ad-domain-list",
"action": {
"tcp": "reject.tcp",
"udp": "reject.udp",
"resolver": "null.resolver"
},
"fallback": {
"tcp": "proxy-forward.tcp",
"udp": "proxy-forward.udp",
"resolver": "fake-ip.resolver"
}
}
source
: Specify an existing ruleset or inline rules. Use a string for the name of the ruleset, or an object for inline rules. See Rule Source section for more details.source.format
: Specify the format of the inline rules. Can be one of the following:source.text
: An array of the lines of the inline rules, excluding line endings.
action
: Specify the Stream Handler, Datagram Session Handler and Resolver to use when the list matches the request.actions.tcp
(optional): Descriptor of the Stream Handler to handle Streams when there is a match. If omitted, Streams will be discarded when matched.actions.udp
(optional): Descriptor of the Datagram Session Handler to handle Datagram Sessions when there is a match. If omitted, Datagram Sessions will be discarded when matched.actions.resolver
(optional): Descriptor of the Resolver to use when the requested domain name is matched by the list. If omitted, no response will be generated when matched.
fallback
: The action to use when no rule matches. Seeaction
for the format.
Rule Source
The source
parameter specifies the ruleset or inline rules for matching. The format can be one of the following:
surge-domain-set
surge-domain-set
is a line-based text format. Each line is a either a full domain name, or a domain suffix preceded by a dot (.
).
# This is a comment.
.lan
localhost.ptlogin2.qq.com
This ruleset will match my-pc.lan
, localhost.ptlogin2.qq.com
, but not my-pc.lan.com
or my-pc.localhost.ptlogin2.qq.com
.
Examples
Use system-resolver
for specified domains, and fake-ip
for the rest.
{
"action": {
"resolver": "system-resolver.resolver"
},
"fallback": {
"resolver": "fake-ip.resolver"
},
"source": {
"format": "surge-domain-set",
"text": [
".msftconnecttest.com",
".msftncsi.com",
".lan",
"localhost.ptlogin2.qq.com"
]
}
}
Reject connections to domain names listed in loyalsoldier-surge-reject
:
{
"action": {
"tcp": "reject.tcp",
"udp": "reject.udp"
},
"fallback": {
"tcp": "proxy-forward.tcp",
"udp": "resolve-proxy.udp"
},
"source": "loyalsoldier-surge-reject"
}
Revision History
- 2024-04-05: Added
list-dispatcher
.
netif
A dynamic network interface.
Access Points
tcp
: Stream Outbound. Initiate a TCP connection bound to the selected network interface. Domain names will be resolved usingoutbound_resolver
or theresolver
of this plugin.udp
: Datagram Outbound. Send a UDP packet bound to the selected network interface. Domain names will be resolved usingoutbound_resolver
or theresolver
of this plugin.resolver
: Resolver. Resolve domain names using configuration associated with this network interface.
Parameters
{
"family_preference": "Ipv4Only",
"type": "Auto"
}
family_preference
: Specify the preferred address family. Can be one of the following (case sensitive):Both
Ipv4Only
Ipv6Only
type
: Determine how to pick a network interface. Can be one of the following (case insensitive):Auto
: Select a wired, physical interface if available, otherwise a wireless interface.Manual
: Select a network interface by name.
netif
: Specify the name of the network interface. Only takes effect whentype
isManual
.outbound_resolver
(optional):
Examples
Select a network interface automatically:
{
"family_preference": "Both",
"type": "Auto"
}
Select the network interface called eth0
:
{
"family_preference": "Ipv4Only",
"type": "Manual",
"netif": "eth0"
}
Details
To keep track of usable network interfaces, the plugin keeps watching network changes regardless of type
.
When family_preference
is set to Auto
and there is at least one IPv6 address and one IPv4 address assigned to the network interface, the plugin utilizes RFC 8305: Happy Eyeballs Version 2 strategy to establish a TCP connection to both IPv4 and IPv6 networks in a concurrent manner. For Datagram Sessions, there is no guarantee which address family will be used to send packets.
Platform-specific Implementation Details
Platform | Socket bind | Network change monitoring | Host name resolution |
---|---|---|---|
macOS/iOS | IP_BOUND_IF / IPV6_BOUND_IF | nw_path_monitor_t | dnssd |
Windows | socket bind to interface address | Windows.Networking.Connectivity.NetworkInformation.NetworkStatusChanged | host_resolver via DNS addresses of the interface |
Linux | SO_BINDTODEVICE | netlink | systemd-resolved D-Bus API, fallback to host-resolver via DNS addresses of the interface retrieved from NetworkManager D-Bus API |
Revision History
- 2023-04-29: Removed
netif
access point. - 2023-04-29: Refactored parameters; removed
Virtual
. - 2023-04-29: Happy Eyeballs Version 2.
- 2024-04-05: Added
outbound_resolver
.
null
Silently drop any outgoing requests.
Access Points
tcp
: Stream Outbound.udp
: Datagram Session Outbound.resolver
: Resolver.
Parameters
null
Note
The plugin returns an error for any requests, instead of being a black hole like /dev/null
.
redirect
Change the destination of connections or datagrams.
Access Points
tcp
: Stream Outbound. Change the destination of Streams.udp
: Datagram Session Outbound. Change the destination of Datagram Sessions and all datagrams flowing through.
Parameters
{
"dest": {
"host": "my.proxy.server.com.",
"port": 8388
},
"tcp_next": "phy-socket",
"udp_next": "phy-socket"
}
dest.host
: Hostname or IP address of the destination.dest.port
: Destination port.tcp_next
: Descriptor of the Stream Outbound to establish new outbound Streams.udp_next
: Descriptor of the Datagram Session Outbound to establish new outbound Datagram Sessions.
reject
Silently reject any incoming requests.
Access Points
tcp
: Stream Handler.udp
: Datagram Stream Handler.
Parameters
null
resolve-dest
Resolve domain names in flow destinations to IP addresses.
Access Points
tcp
: Stream Handler. Available only whentcp_next
is set. Try to replace the destination domain name with an IP address it resolves to.udp
: Datagram Session Handler. Available only whenudp_next
is set. For Datagram Session Handlers and every datagram flowing through, try to replace the destination domain name with an IP address it resolves to.
Parameters
{
"resolver": "fake-ip.resolver",
"tcp_next": "main-forward.tcp",
"udp_next": "main-forward.tcp",
}
resolver
: Descriptor of the Resolver.tcp_next
(optional): Descriptor of the Stream Handler to handle the Stream with destination addresses resolved by the resolver. Thetcp
access point will be available only when this parameter is set.udp_next
(optional): Descriptor of the Datagram Session Handler to handle the Datagram Session with destination addresses resolved by the resolver for all incoming packets. Destination addresses for outgoing packets will be mapped back to domain names based on resolution history within the session at a best-effort basis. Theudp
access point will be available only when this parameter is set.
Revision History
- 2023-04-29: Removed
reverse
. - 2023-06-05: Fixed
tcp_next
,udp_next
types. - 2023-06-05: Specify UDP mapback behavior.
rule-dispatcher
Match the connection against rules defined in a resource, and use the handler of a corresponding action or fallback handler if there is no match.
Access Points
tcp
: Stream Handler defined inactions
and selected by the rules, orfallback.tcp
if none matches.udp
: Datagram Session Handler defined inactions
and selected by the rules, orfallback.udp
if none matches. Dispatching applies to Datagram Sessions only. Datagrams flowing through are not inspected.resolver
: Resolver defined inactions
and the requesting domain name is selected by the rules, orfallback.resolver
if none matches.
Parameters
{
"resolver": "doh-resolver.resolver",
"source": "enhanced-proxy-list",
"geoip": "dreamacro-geoip",
"actions": {
"direct": {
"tcp": "direct-forward.tcp",
"udp": "direct-forward.udp",
"resolver": "phy.resolver"
},
"reject": {
"tcp": "reject.tcp",
"udp": "reject.udp",
"resolver": "null.resovler"
}
},
"rules": {
"cn": "direct"
},
"fallback": {
"tcp": "proxy-forward.tcp",
"udp": "proxy-forward.udp",
"resolver": "fake-ip.resolver"
}
}
resolver
(optional): Specify the resolver to resolve domain names to IP addresses as additional inputs for rule matching. If omitted, destination addresses with domain names will not be matched against IP address related rules. A secure DNS resolver is recommended. Seehost-resolver
for a DNS-over-HTTPS resolver.geoip
(optional): Specify the resource name of a GeoIP database to use for matching. If omitted, the rules with typegeoip
inquanx-filter
rulesets will be skipped during matching. The GeoIP database is a MaxMind DB File containing country data. Do not specify this parameter if the ruleset has typegeoip-country
which is redundant and will not take effect.source
: Specify an existing ruleset or inline rules. Use a string for the name of the ruleset, or an object for inline rules. See Rule Source section for more details.source.format
: Specify the format of the inline rules. Can be one of the following:source.text
: An array of the lines of the inline rules, excluding line endings.
actions
: Specify the groups of Stream Handlers, Datagram Session Handlers and Resolvers.actions[].tcp
(optional): Descriptor of the Stream Handler to handle Streams when the action is selected. If omitted, Streams will be discarded when the action is selected.actions[].udp
(optional): Descriptor of the Datagram Session Handler to handle Datagram Sessions when the action is selected. If omitted, Datagram Sessions will be discarded when the action is selected.actions[].resolver
(optional): Descriptor of the Resolver to use when the action is selected. If omitted, no response will be generated for resolution requests.
rules
: Specify the rule-action mapping. The meaning of the keys depends on the type of the ruleset. Forgeoip-country
, the keys are ISO 3166-1 alpha-2 country codes (case-insensitive). Forquanx-filter
, the keys are the targets of the ruleset.fallback
: The action to use when no rule matches. Seeactions
for the format.
Rule Source
The source
parameter specifies the ruleset or inline rules for matching. The format can be one of the following:
geoip-country
A MaxMind DB File containing country data. The rules are matched against the country code of the destination IP address. It cannot be used as inline rules.
quanx-filter
quanx-filter
is a line-based text format. Each line consists of a few comma-separated components specifying the matching criteria and a target. The target is used as the key in the rules
parameter.
; This is a comment.
# This is also a comment.
; Take action `direct` when the destination domain name is exactly www.example.com
host, www.example.com, direct
domain, www.example.com, direct # equivalent to the above line
; Take action `proxy` when the destination domain name ends with ip.sb
host-suffix, ip.sb, proxy
domain-suffix, ip.sb, proxy # equivalent to the above line
; Take action `direct` when the destination domain name contains google-analytics
host-keyword, google-analytics, reject
domain-keyword, google-analytics, reject # equivalent to the above line
; Take action `direct` when the destination IP address is 114.114.114.114.
; Skipped if the destination address is a domain name.
ip-cidr, 114.114.114.114/32, direct, no-resolve
# Take action `direct` when the destination IP address is in the range.
; If the destination address is a domain name, the IP address resolved by the resolver parameter will be matched.
ip-cidr, 1.1.1.1/16, direct
; Take action `proxy` when the destination IP address is in the range 2001:4860:4860::8800/120.
; Skipped if the destination address is a domain name.
ip6-cidr, 2001:4860:4860::8800/120, proxy, no-resolve
ip-cidr6, 2001:4860:4860::8800/120, proxy, no-resolve ; equivalent to the above line
; take action `direct` when the destination IP address is located in China.
; If the destination address is a domain name, the resolved IP address will be matched.
geoip, cn, direct
; take action `proxy` for all unmatched connections.
; This line is **optional**.
final, proxy
The first component specifies the type of the rule:
host
/domain
: Match the exact domain name of the destination against the second component. Take the action specified in the third component if matched.host-suffix
/domain-suffix
: Match the suffix of the domain name of the destination against the second component. Take the action specified in the third component if matched.host-keyword
/domain-keyword
: Match the keyword in the domain name of the destination against the second component. Take the action specified in the third component if matched.ip-cidr
: Match the destination IP address against the CIDR block specified in the second component. Take the action specified in the third component if matched. If the fourth componentno-resolve
exists, the rule is skipped when the destination address is a domain name.ip6-cidr
/ip-cidr6
: Match the destination IPv6 address against the CIDR block specified in the second component. Take the action specified in the third component if matched. If the fourth componentno-resolve
exists, the rule is skipped when the destination address is a domain name.geoip
: Match the destination IP address against the country code specified in the second component. Take the action specified in the third component if matched. If the fourth componentno-resolve
exists, the rule is skipped when the destination address is a domain name. The GeoIP database is specified in thegeoip
parameter. The rule is skipped if thegeoip
parameter is not set.final
: Take the action specified in the second component for all unmatched connections. As opposed to other implementations, thefinal
rule is optional. When omitted, thefallback
action is used for all unmatched connections.
During matching, the rules are evaluated in the order they appear in the ruleset. The first rule that matches the connection is selected. A host name resolution is not performed until it reaches an ip-cidr
, ip6-cidr
, or geoip
rule without no-resolve
.
Examples
Dispatch connections targeting IP addresses located in China to direct-forward
forward
plugin using a managed GeoIP database:
{
"actions": {
"direct": {
"tcp": "direct-forward.tcp",
"udp": "resolve-local.udp"
}
},
"fallback": {
"tcp": "proxy-forward.tcp",
"udp": "resolve-proxy.udp"
},
"resolver": "doh-resolver.resolver",
"rules": {
"cn": "direct"
},
"source": "dreamacro-geoip"
}
Dispatch connections based on custom inline rules:
{
"actions": {
"direct": {
"tcp": "direct-forward.tcp",
"udp": "resolve-local.udp"
},
"proxy": {
"tcp": "proxy-forward.tcp",
"udp": "resolve-proxy.udp"
},
"reject": {
"tcp": "reject.tcp",
"udp": "reject.udp"
}
},
"fallback": {
"tcp": "geoip-dispatcher.tcp",
"udp": "geoip-dispatcher.udp"
},
"rules": {
"direct": "direct",
"proxy": "proxy",
"reject": "reject"
},
"source": {
"format": "quanx-filter",
"text": [
"domain, www.example.com, direct",
"domain-suffix, ip.sb, proxy",
"domain-keyword, google-analytics, reject",
"ip-cidr, 114.114.114.114/32, direct, no-resolve",
"ip6-cidr, 2001:4860:4860::8800/120, proxy, no-resolve"
]
}
}
Revision History
- 2023-06-05: Added
rule-dispatcher
. - 2024-04-05: Added
quanx-filter
.
shadowsocks-client
Shadowsocks client.
Access Points
tcp
: Stream Outbound.udp
: Datagram Session Outbound.
Parameters
{
"method": "aes-128-gcm",
"password": {
"__byte_repr": "utf8",
"data": "my_ss_password"
},
"tcp_next": "proxy-redir.tcp",
"udp_next": "null.udp"
}
method
: The encryption method, or cipher. Supported methods:none
/plain
rc4
rc4-md5
aes-128-cfb
aes-192-cfb
aes-256-cfb
aes-128-ctr
aes-192-ctr
aes-256-ctr
camellia-128-cfb
camellia-192-cfb
camellia-256-cfb
aes-128-gcm
aes-256-gcm
chacha20-ietf
chacha20-ietf-poly1305
xchacha20-ietf-poly1305
password
: Specify the password to generate encryption keys.tcp_next
: Descriptor of the Stream Outbound to establish new outbound Streams.udp_next
: Descriptor of the Datagram Session Outbound to establish new outbound Datagram Sessions.
Revision History
- 2023-04-29: Added
udp
access point.
simple-dispatcher
Match the source/dest address against a list of simple rules, and use the corresponding handler or fallback handler if there is no match.
Access Points
tcp
: Stream Handler.udp
: Datagram Session Handler. Dispatching applies to Datagram Sessions only. Datagrams flowing through are not inspected.
Parameters
{
"fallback_tcp": "main-forward.tcp",
"fallback_udp": "main-forward.udp",
"rules": [
{
"is_udp": true,
"src": {
"ip_ranges": ["0.0.0.0/0"],
"port_ranges": [{ "start": 0, "end": 65535 }]
},
"dst": {
"ip_ranges": ["11.16.1.1/32"],
"port_ranges": [{ "start": 53, "end": 53 }]
},
"next": "fakeip-dns-server.udp"
}
]
}
fallback_tcp
: Descriptor of the Stream Handler to handle Streams when none of the rules matches.fallback_udp
: Descriptor of the Datagram Session Handler to handle Datagram Sessions when none of the rules matches.rules
: List of rules.rules[].is_udp
: Specify whether the rule applies to Datagram Sessions or Streams.rules[].src
: Specify the source address and port.rules[].src.ip_ranges
: List of IP ranges in CIDR format.rules[].src.port_ranges
: List of port ranges.rules[].src.port_ranges[].start
: Start port, inclusive.rules[].src.port_ranges[].end
: End port, inclusive.
rules[].dst
: Specify the destination address and port.rules[].next
: Descriptor of the handler to use when the rule matches.
Note
For large number of rules, simple-dispatcher
may not be efficient as it matches all the rules sequentially. In this case, consider using rule-dispatcher
instead.
Revision History
- 2023-04-29: Use human representation for IP CIDR.
- 2023-06-05: Fixed access points,
fallback_tcp
,fallback_udp
types. - 2023-06-05: Add recommendation for
rule-dispatcher
.
socket
Represents a system socket connection.
Access Points
- : Stream Outbound and Datagram Session Outbound. Initiate a TCP connection or UDP socket. Use the plugin name as the descriptor.
Parameters
{
"resolver": "phy.resolver",
"bind_addr_v4": "0.0.0.0:0",
"bind_addr_v6": null
}
resolver
: Descriptor of the Resolver to resolve domain names to IP addresses.bind_addr_v4
(optional): IPv4 socket address to bind to. If omitted, a default socket address will be used. If set tonull
, IPv4 will be disabled.bind_addr_v6
(optional): IPv6 socket address to bind to. If omitted, a default socket address will be used. If set tonull
, IPv6 will be disabled.
Details
When both bind_addr_v4
and bind_addr_v6
are non-null
(i.e. either specified a value or omitted), the plugin utilizes RFC 8305: Happy Eyeballs Version 2 strategy to establish a connection to both IPv4 and IPv6 networks in a concurrent manner.
Revision History
- 2023-04-29: Removed
netif
; addedbind_addr
. - 2023-04-29: Happy Eyeballs Version 2.
socket-listener
Bind a socket to a specified port and listen for connections or datagrams.
Access Points
No access point is provided. This plugin should be used as entry plugin.
Parameters
{
"tcp_listen": ["127.0.0.1:1080"],
"udp_listen": ["127.0.0.1:1080"],
"tcp_next": "geoip-dispatcher.tcp",
"udp_next": "geoip-dispatcher.udp"
}
tcp_listen
(optional): List of socket addresses to listen on TCP connections.udp_listen
(optional): List of socket addresses to receive UDP datagrams.tcp_next
: Descriptor of the Stream Handler.udp_next
: Descriptor of the Datagram Session Handler.
socks5-client
SOCKS5 client.
Access Points
tcp
: Stream Outbound.
The UDP protocol is not implemented as of YtFlowCore 0.1.
Parameters
{
"user": {
"__byte_repr": "utf8",
"data": "my_socks5_username"
},
"pass": {
"__byte_repr": "utf8",
"data": "my_socks5_password"
},
"tcp_next": "proxy-redir.tcp",
"udp_next": "proxy-redir.udp",
}
user
(optional): Specify the user name of the credential. Omitting this field disables authentication.pass
(optional): Specify the password of the credential. Omitting this field disables authentication.tcp_next
: Descriptor of the Stream Outbound to send SOCKS5 requests.udp_next
: Descriptor of the Datagram Session Outbound to send SOCKS5 UDP payloads.
socks5-server
SOCKS5 server.
Access Points
tcp
: Stream Handler.
The UDP protocol is not implemented as of YtFlowCore 0.1.
Parameters
{
"user": {
"__byte_repr": "utf8",
"data": "my_socks5_username"
},
"pass": {
"__byte_repr": "utf8",
"data": "my_socks5_password"
},
"tcp_next": "forward.tcp",
"udp_next": "forward.udp",
}
user
(optional): Specify the user name of the credential. Omitting this field disables authentication.pass
(optional): Specify the password of the credential. Omitting this field disables authentication.tcp_next
: Descriptor of the Stream Handler to use.udp_next
: Descriptor of the Datagram Session Handler to use.
switch
Handle incoming connections using runtime-selected handlers from a pre-defined list.
This plugin requires a database file to work. If a database file is not provided, it will fail to load.
Access Points
tcp
: Stream Handler selected at runtime.udp
: Datagram Session Handler selected at runtime.
Parameters
{
"choices": [
{
"name": "Proxy",
"description": "Proxy all connections unconditionally",
"tcp_next": "proxy-forward.tcp",
"udp_next": "proxy-forward.udp"
},
{
"name": "Rule",
"description": "Match connections against rules",
"tcp_next": "rule-dispatcher.tcp",
"udp_next": "rule-dispatcher.udp"
}
]
}
choices
: Specify the list of choices.choices[].name
: Name of the choice.choices[].description
: Description of the choice.choices[].tcp_next
: Descriptor of the Stream Handler to use when the choice is selected.choices[].udp_next
: Descriptor of the Datagram Session Handler to use when the choice is selected.
Details
Selection is managed by the UI application. During runtime, a user can change the selection from the UI via RPC. The selection will be stored in the database so that the plugin will automatically load the last choice on next startup.
Revision History
- 2023-06-05: Added
switch
.
system-resolver
Resolve real IP addresses by calling system functions. This is the recommended resolver for simple proxy scenarios for both client and server.
Access Points
resolver
: Resolver. Resolve domain names to real IP addresses, and map recent results back to domain names on a best-effort basis.
Parameters
null
Details
The implementation uses tokio::net::lookup_host
, which invokes getaddrinfo under the hood.
tls-client
TLS client stream.
Access Points
tcp
: Stream Outbound. Encrypt the Stream with TLS.
Parameters
{
"alpn": ["h2", "http/1.1"],
"skip_cert_check": false,
"sni": "my.trojan.proxy.server.com",
"next": "proxy-redir.tcp"
}
alpn
(optional): List of ALPN values. If omitted, the ALPN extension will be inferred from the upper layer plugins, or disabled if none of the upper layer plugins provide ALPN values.skip_cert_check
(optional): Whether to skip certificate verification, a.k.a.allowInsecure
. Enabling this option invites MITM attacks, thus you are strongly discouraged from doing so.sni
(optional): Overwrite the server name in TLS requests. If omitted, the destination hostname of Streams will be used.next
: Descriptor of the Stream Outbound to send TLS encrypted data.
Details
The following plugins are ALPN-aware:
ws-client
:h2
andhttp/1.1
auto adaptivehost-resolver
:h2
andhttp/1.1
for DNS-over-HTTPS endpoints
Revision History
- 2023-05-10: Added
alpn
. - 2023-06-05: Specify auto-inferred
alpn
. - 2024-01-16: Update
ws-client
withhttp/2
support - 2024-01-17: Remove
ws-client
fromalpn
exceptions
tls-obfs-client
simple-obfs TLS client.
Access Points
tcp
: Stream Outbound. Returns a fake-TLS handshaked Stream.
Parameters
{
"host": "dl.microsoft.com",
"next": "proxy-redir.tcp"
}
host
: Specify the value ofHost
HTTP header.next
: Descriptor of the Stream Outbound to perform HTTP obfuscation.
Details
This plugin is compatible with simple-obfs, and does not perform real TLS handshaking. It simply appends a fake ClientHello packet before the Stream, and discards the response header.
Revision History
- 2023-05-11: Added
tls-obfs-client
.
trojan-client
Trojan client.
Access Points
tcp
: Stream Outbound.
The UDP protocol is not implemented as of YtFlowCore 0.1.
Parameters
{
"password": {
"__byte_repr": "utf8",
"data": "my_trojan_password"
},
"tls_next": "trojan-client-tls.tcp"
}
password
: Password.tls_next
: Descriptor of the Stream Outbound to send plaintext Trojan requests.
Note
TLS is not included. You will likely need to connect trojan-client
to a tls-client
.
vmess-client
VMess client.
Access Points
tcp
: Stream Outbound.
The UDP protocol is not implemented as of YtFlowCore 0.3.
Parameters
{
"user_id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"security": "auto",
"alter_id": 0,
"tcp_next": "proxy-redir.tcp"
}
user_id
: Specify the user ID to generate encryption keys.security
(optional): The encryption method, or cipher for stream payload. Defaults toauto
. Supported values:none
auto
(aes-128-gcm
on x86_64 or aarch64 devices,chacha20-poly1305
otherwise)aes-128-cfb
aes-128-gcm
chacha20-poly1305
alter_id
(optional):0
to enable VMess AEAD header format (default), any value greater than0
to disable VMess AEAD header format. This value works solely as a switch for VMess AEAD header format. No alter IDs are generated.tcp_next
: Descriptor of the Stream Outbound to establish new outbound Streams.
Details
The following request options are enabled in YtFlowCore:
RequestOptionChunkStream
(0x01)RequestOptionChunkMasking
(0x04)
The following request options are not supported:
RequestOptionConnectionReuse
(0x02)RequestOptionGlobalPadding
(0x08)RequestOptionAuthenticatedLength
(0x10)
Revision History
- 2023-05-11: Added
vmess-client
. - 2023-05-18: Fixed data type for
alter_id
.
vpn-tun
An instance to be instantiated by a VPN system service, such as UWP VPN Plugin on Windows.
Access Points
tun
: TUN Interface.
Parameters
{
"ipv4": "192.168.3.1",
"ipv4_route": [
"11.17.0.0/16",
"11.16.0.0/16"
],
"ipv6": null,
"ipv6_route": [],
"dns": ["11.16.1.1"],
"web_proxy": null
}
ipv4
(optional): IPv4 address of the TUN interface.ipv4_route
: IPv4 routes CIDR format.ipv6
(optional): IPv6 address of the TUN interface.ipv6_route
: IPv6 routes CIDR format.dns
: Assigned DNS servers.web_proxy
(optional): Assigned proxy address.
Details
For a vpn-tun
to work, the YtFlowCore instance should be launched by a system VPN service, such as UWP VPN Plugin on Windows. There must be at most one vpn-tun
instance in a YtFlowCore instance.
Revision History
- 2023-04-29: Use human representation for IP CIDR.
ws-client
WebSocket client.
Access Points
tcp
: Stream Outbound. Returns a Stream on top of WebSocket.
Parameters
{
"host": "dl.microsoft.com",
"path": "/",
"headers": {},
"next": "proxy-redir.tcp"
}
host
: Specify the value ofHost
HTTP header.path
: Specify the path of the HTTP request.headers
: A key-value map of additional HTTP headers to be sent in the request.next
: Descriptor of the Stream Outbound.
Details
The WebSocket implementation is capable of initiating WebSocket requests over HTTP/2 when tls-client
is used as the lower layer transport. In case the HTTP/2 connection handshake fails for any reason during the first connection attempt, HTTP/1.1 will be used for all subsequent requests.
Since the WebSocket protocol does not support half-closed connections, issues might occur for those user applications that rely on this feature.
Revision History
- 2023-05-11: Added
ws-client
. - 2024-10-16: Document WebSockets over HTTP/2