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.


Creative Commons License
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

  1. Build a Shadowsocks Client with a SOCKS5 inbound, akin to the classic Shadowsocks client.
  2. Focus on TCP only.

Plugins


 ┌──────────┐     ┌───────────────┐     ┌──────────────┐     ┌───────────┐
 │          │     │               │     │              │     │           │
 │ listener │ tcp │ socks5-server │ tcp │ main-forward │ tcp │ ss-client │
 │          ├────►│               ├────►│              ├────►│           │
 └──────────┘     └───────────────┘     └──────────────┘     └────┬──────┘
                                                                  │
                                      ┌──────────────┐            │
                                      │              │            │
                                      │ sys-resolver │            │ tcp
                                      │              │            │
                                      └───▲──────────┘            │
                                          │ resolver              │
                                        ┌─┴──────────┐     ┌──────▼──────┐
                                        │            │     │             │
                                        │ phy-socket │ tcp │ proxy-redir │
                                        │            │◄────┤             │
                                        └────────────┘     └─────────────┘

{
  "tcp_listen": ["127.0.0.1:1080"],
  "udp_listen": [],
  "tcp_next": "socks5-server.tcp",
  "udp_next": "reject.udp"
}
{
  "tcp_next": "main-forward.tcp",
  "udp_next": "reject.udp"
}
{
  "tcp_next": "ss-client.tcp",
  "udp_next": "null.udp"
}
{
  "method": "aes-128-gcm",
  "password": {
    "__byte_repr": "utf8",
    "data": "my_ss_password"
  },
  "tcp_next": "proxy-redir.tcp",
  "udp_next": "null.udp"
}
{
  "dest": {
    "host": "my.proxy.server.com.",
    "port": 8388
  },
  "tcp_next": "phy-socket",
  "udp_next": "null.udp"
}
{
  "resolver": "sys-resolver.resolver"
}
null
null
null

Explanation

  1. listener binds to a local host endpoint and listens for incoming connections.
  2. For each incoming connection, socks5-server performs a SOCKS5 handshake and obtains a destination address from the SOCKS5 request, say google.com:443.
  3. main-forward initiates a new connection with ss-client.
  4. 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 to google.com:443, which is not expected.
  5. phy-socket resolves the IP address of my.proxy.server.com. using a system-resolver.
  6. The Shadowsocks server at my.proxy.server.com.:8388 receives the request and starts relay between google.com:443 and your PC.

Revision History

  • 2023-04-29: Removed phy.
  • 2024-04-05: Fixed reference to resolve-dest in proxy-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 in tcp_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 in udp_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. For prefix_v4: [11, 17], the first IPv4 address to assign will be 11.17.0.1.
  • prefix_v6: Specify the first 14 bytes of the fake IPv6 addresses. For prefix_v6: [38, 12, 32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], the first IPv6 address to assign will be 260c: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 to 100): 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 endpoints
    • doh[].url: absolute URL of the endpoint. Outbound requests (in plaintext) will be sent to this URL. For https URLs, a tls-client plugin should be present in the outbound chain specified by doh[].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. A redirect 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 of Host 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 value fd00::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 in action.tcp and selected by the rules, or fallback.tcp if none matches.
  • udp: Datagram Session Handler defined in action and selected by the rules, or fallback.udp if none matches. Dispatching applies to Datagram Sessions only. Datagrams flowing through are not inspected.
  • resolver: Resolver defined in actions and the requesting domain name is selected by the rules, or fallback.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. See action 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 using outbound_resolver or the resolver of this plugin.
  • udp: Datagram Outbound. Send a UDP packet bound to the selected network interface. Domain names will be resolved using outbound_resolver or the resolver 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 when type is Manual.
  • 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

PlatformSocket bindNetwork change monitoringHost name resolution
macOS/iOSIP_BOUND_IF / IPV6_BOUND_IFnw_path_monitor_tdnssd
Windowssocket bind to interface addressWindows.Networking.Connectivity.NetworkInformation.NetworkStatusChangedhost_resolver via DNS addresses of the interface
LinuxSO_BINDTODEVICEnetlinksystemd-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 when tcp_next is set. Try to replace the destination domain name with an IP address it resolves to.
  • udp: Datagram Session Handler. Available only when udp_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. The tcp 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. The udp 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 in actions and selected by the rules, or fallback.tcp if none matches.
  • udp: Datagram Session Handler defined in actions and selected by the rules, or fallback.udp if none matches. Dispatching applies to Datagram Sessions only. Datagrams flowing through are not inspected.
  • resolver: Resolver defined in actions and the requesting domain name is selected by the rules, or fallback.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. See host-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 type geoip in quanx-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 type geoip-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. For geoip-country, the keys are ISO 3166-1 alpha-2 country codes (case-insensitive). For quanx-filter, the keys are the targets of the ruleset.
  • fallback: The action to use when no rule matches. See actions 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 component no-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 component no-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 component no-resolve exists, the rule is skipped when the destination address is a domain name. The GeoIP database is specified in the geoip parameter. The rule is skipped if the geoip parameter is not set.
  • final: Take the action specified in the second component for all unmatched connections. As opposed to other implementations, the final rule is optional. When omitted, the fallback 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 to null, 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 to null, 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; added bind_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:

Revision History

  • 2023-05-10: Added alpn.
  • 2023-06-05: Specify auto-inferred alpn.
  • 2024-01-16: Update ws-client with http/2 support
  • 2024-01-17: Remove ws-client from alpn 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 of Host 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 to auto. 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 than 0 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 of Host 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