dae Documentation

EBPF-based Linux high-performance transparent proxy solution.

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy dae on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name dae --restart always daeuniverse/dae:latest
Configured Services & Ports
โš™๏ธ dae
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
version: "3"
services:
  dae:
    privileged: true
    network_mode: host
    pid: host
    build:
      context: .
    volumes:
      - /sys:/sys
      - /etc/dae:/etc/dae

File: README.md

dae

dae, means goose, is a high-performance transparent proxy solution.

To enhance traffic split performance as much as possible, dae employs the transparent proxy and traffic split suite within the Linux kernel using eBPF. As a result, dae can enable direct traffic to bypass the proxy application's forwarding, facilitating genuine direct traffic passage. Through this remarkable feat, there is minimal performance loss and negligible additional resource consumption for direct traffic.

As a successor of v2rayA, dae abandoned v2ray-core to meet the needs of users more freely.

Features

  • Implement Real Direct traffic split (need ipforward on) to achieve high performance.
  • Support to split traffic by process name in local host.
  • Support to split traffic by MAC address in LAN.
  • Support to split traffic with invert match rules.
  • Support to automatically switch nodes according to policy. That is to say, support to automatically test independent TCP/UDP/IPv4/IPv6 latencies, and then use the best nodes for corresponding traffic according to user-defined policy.
  • Support advanced DNS resolution process.
  • Support full-cone NAT for shadowsocks, trojan(-go) and socks5 (no test).
  • Support various trending proxy protocols, seen in proxy-protocols.md.

Getting Started

Please refer to Quick Start Guide to start using dae right away!

Notes

  1. If you setup dae and also a shadowsocks server (or any UDP servers) on the same machine in public network, such as a VPS, don't forget to add l4proto(udp) && sport(your server ports) -> must_direct rule for your UDP server port. Because states of UDP are hard to maintain, all outgoing UDP packets will potentially be proxied (depends on your routing), including traffic to your client. This behaviour is not what we want to see. must_direct makes all traffic from this port including DNS traffic direct.
  2. If users in mainland China find that the first screen time is very long when they visit some domestic websites for the first time, please check whether you use foreign DNS to handle some domestic domain in DNS routing. Sometimes this is hard to spot. For example, ocsp.digicert.cn is included in geosite:geolocation-!cn unexpectedly, which will cause some tls handshakes to take a long time. Be careful to use such domain sets in DNS routing.

How it works

See How it works.

TODO

  • Automatically check dns upstream and source loop (whether upstream is also a client of us) and remind the user to add sip rule.
  • MACv2 extension extraction.
  • Log to userspace.
  • Protocol-oriented node features detecting (or filter), such as full-cone (especially VMess and VLESS).
  • Add quick-start guide
  • ...

Contributors

Special thanks goes to all contributors. If you would like to contribute, please see the instructions. Also, it is recommended following the commit-msg-guide.

License

AGPL-3.0 (C) daeuniverse

Stargazers over time


File: docs/en/development/commit-msg-guide.md

Semantic Commit Messages

The reasons for these conventions

  • automatic generating of the changelog
  • simple navigation through Git history (e.g. ignoring the style changes)

See how a minor change to your commit message style can make you a better developer.

Format

text
`<type>(<scope>): <subject>`

`<scope>` is optional

Example

text
feat: add hat wobble
^--^  ^------------^
|     |
|     +-> Summary in present tense.
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.

Example <type> values:

  • feat: (new feature for the user, not a new feature for build script)
  • fix: (bug fix for the user, not a fix to a build script)
  • docs: (changes to the documentation)
  • style: (formatting, missing semi colons, etc; no production code change)
  • refactor: (refactoring production code, eg. renaming a variable)
  • test: (adding missing tests, refactoring tests; no production code change)
  • chore: (updating grunt tasks etc; no production code change, e.g. dependencies upgrade)
  • perf: (perfomance improvement change, e.g. better concurrency performance)
  • ci: (updating CI configuration files and scripts e.g. .gitHub/workflows/*.yml )

Example <Scope> values:

  • init
  • runner
  • watcher
  • config
  • web-server
  • proxy

The <scope> can be empty (e.g. if the change is a global or difficult to assign to a single component), in which case the parentheses are omitted. In smaller projects such as Karma plugins, the <scope> is empty.

Message Subject (First Line)

The first line cannot be longer than 72 characters and should be followed by a blank line. The type and scope should always be lowercase as shown below

Message Body

use as in the <subject>, use the imperative, present tense: "change" not "changed" nor "changes". Message body should include motivation for the change and contrasts with previous behavior.

Referencing issues

Closed issues should be listed on a separate line in the footer prefixed with "Closes" keyword as the following:

text
Closes #234

or in the case of multiple issues:

text
Closes #123, #245, #992

References


File: docs/en/development/contribute.md

Contribute

If you want to contribute to a project and make it better, your help is very welcome. Contributing is also a great way to learn more about social coding on GitHub, new technologies and their ecosystems and how to make constructive, helpful bug reports, feature requests and the noblest of all contributions: a good, clean pull request.

Bug Reports and Feature Requests

If you have found a bug or have a feature request, please use the search first in case a similar issue already exists. If not, please create an issue in this repository

Code

If you would like to fix a bug or implement a feature, please fork the repository and create a Pull Request.

Before you start any Pull Request, it is recommended that you create an issue to discuss first if you have any doubts about requirement or implementation. That way you can be sure that the maintainer(s) agree on what to change and how, and you can hopefully get a quick merge afterwards.

Pull Requests can only be merged once all status checks are green.

Pre-commit Hook

This repo uses pre-commit hook to apply linting check prior to writing commit to local Git history. To set up pre-commit, do the followings:

bash
# install pre-commit
pip3 install pre-commit
# install pre-commit hooks
pre-commit install

How to make a clean pull request

  • Create a personal fork of the project on GitHub.
  • Clone the fork on your local machine. Your remote repo on GitHub is called origin.
  • Add the original repository as a remote called upstream.
  • If you created your fork a while ago be sure to pull upstream changes into your local repository.
  • Create a new branch to work on! Branch from main.
  • Implement/fix your feature, comment your code.
  • Follow the code style of the project, including indentation.
  • If the project has tests run them! For routine unit tests, use go test -tags dae_stub_ebpf ./.... For eBPF tests, use make ebpf-test.
  • Write or adapt tests as needed.
  • Add or change the documentation as needed.
  • Squash your commits into a single commit with Git's interactive rebase. Create a new branch if necessary.
  • Push your branch to your fork on GitHub, the remote origin.
  • From your fork open a pull request in the correct branch. Target the project's main branch.
  • Once the pull request is approved and merged you can pull the changes from upstream to your local repo and delete
    your extra branch(es).

And last but not least: Always write your commit messages in the present tense. Your commit message should describe what the commit, when applied, does to the code โ€“ not what you did to the code.

Re-requesting a review

Please do not ping your reviewer(s) by mentioning them in a new comment. Instead, use the re-request review functionality. Read more about this in the GitHub docs, Re-requesting a review.


File: docs/en/configuration/dns.md

DNS

dae will intercept all UDP traffic to port 53 and sniff DNS. Here gives some examples and templates for DNS configuration.

Schema

DoH3

text
h3://<host>:/
http3://<host>:/

default port: 443
default path: /dns-query

DoH

text
https://<host>:/

default port: 443
default path: /dns-query

DoT

text
tls://<host>:

default port: 853

DoQ

text
quic://<host>:

default port: 853

UDP

text
udp://<host>:

default port: 53

TCP

text
tcp://<host>:

default port: 53

TCP and UDP

text
tcp+udp://<host>:

default port: 53

Examples

text
/* Detailed source-code truncated for AI context efficiency. */

Templates

shell
# Use alidns for China mainland domains and googledns for others.
dns {
  upstream {
    googledns: 'tcp+udp://dns.google:53'
    alidns: 'udp://dns.alidns.com:53'
  }
  routing {
    # According to the request of dns query, decide to use which DNS upstream.
    # Match rules from top to bottom.
    request {
      # Lookup China mainland domains using alidns, otherwise googledns.
      qname(geosite:cn) -> alidns
      # fallback is also called default.
      fallback: googledns
    }
  }
}
shell
# Use alidns for all DNS queries and fallback to googledns if pollution result detected.
dns {
  upstream {
    googledns: 'tcp+udp://dns.google:53'
    alidns: 'udp://dns.alidns.com:53'
  }
  routing {
    # According to the request of dns query, decide to use which DNS upstream.
    # Match rules from top to bottom.
    request {
      # fallback is also called default.
      fallback: alidns
    }
    # According to the response of dns query, decide to accept or re-lookup using another DNS upstream.
    # Match rules from top to bottom.
    response {
      # Trusted upstream. Always accept its result.
      upstream(googledns) -> accept
      # Possibly polluted, re-lookup using googledns.
      ip(geoip:private) && !qname(geosite:cn) -> googledns
      # fallback is also called default.
      fallback: accept
    }
  }
}

File: docs/en/configuration/external-dns.md

Use External DNS

Note
DNS request should be forwarded by dae for domain based traffic split. This guide will show you how to configure dae with external DNS.

If you use a external DNS like AdguardHome, you could refer to the following guide.

External DNS on localhost

If you set up an external DNS on localhost, you may want to let the DNS queries to dns.google proxied. For example, if you have the following configuration in AdguardHome:

text
Listen on: the same machine with dae, port 53.

China mainland: udp://223.5.5.5:53
Others: https://dns.google/dns-query

You should configure dae as follows:

  1. Complete wan_interface in "global" section to proxy requests of AdguardHome.

  2. Insert following rule as the first line of "routing" section to avoid loops.

    python
    pname(AdGuardHome) && l4proto(udp) && dport(53) -> must_direct

    And make sure domain dns.google will be proxied in routing rules.

  3. Add upstream and request to section "dns".

    text
    dns {
      upstream {
        adguardhome: 'udp://127.0.0.1:53'
      }
      routing {
        request {
          fallback: adguardhome
        }
      }
    }
  4. If you bind to WAN, make sure your /etc/resolv.conf does NOT use your local external DNS directly. For example, you can set it as nameserver 119.29.29.29, and then DNS traffic will be hijacked by dae when the packets are sent through NIC. Most of the time, /etc/resolv.conf will be modified back by your DNS service like dnsmasq after rebooting, which is hard to deal with. We recommended you to uninstall them or give sudo chattr +i /etc/resolv.conf if you encounter such situation.

  5. If you bind to LAN, make sure your DHCP server will distribute dae as the DNS server (DNS request should be forwarded by dae for domain based traffic split).

  6. If there is still a DNS issue and there are no warn/error logs, you have to change your listening port of external DNS (here is AdGuardHome) from 53 to non-53 port. See #31.

  7. If you use PVE, refer to #37.

External DNS on another machine in LAN

If you set up a external DNS on another machine in LAN, you may want to let the DNS queries to dns.google proxied. For example, if you have following configuration in AdguardHome:

text
Listen on: 192.168.30.3:53 (mac address: 8c:16:45:36:1c:5a)

China mainland: udp://223.5.5.5:53
Others: https://dns.google/dns-query

You should configure dae as follows:

  1. Fill in lan_interface in "global" section to proxy requests of AdguardHome.

  2. Insert following rule as the first line of "routing" section to avoid loops.

    python
    sip(192.168.30.3) && l4proto(udp) && dport(53) -> must_direct
    # Or use MAC address if in the same link:
    # mac(8c:16:45:36:1c:5a) && l4proto(udp) && dport(53) -> must_direct

    And make sure domain dns.google will be proxied in routing rules.

  3. Add upstream and request to section "dns".

    text
    dns {
      upstream {
        adguardhome: 'udp://192.168.30.3:53'
      }
      routing {
        request {
          fallback: adguardhome
        }
      }
    }
  4. If you bind to LAN, make sure your DHCP server will distribute dae as the DNS server (DNS request should be forwarded by dae for domain based traffic split).

  5. If there is still a DNS issue and there are no warn/error logs, you have to change your listening port of external DNS (here is AdGuardHome) from 53 to non-53 port. See #31.

  6. If you use PVE, refer to #37.


File: docs/en/configuration/gaming-oriented-routing.md

DAE Config (e.g. /etc/dae/config.dae)

text
routing {            
#stop using low efficiency socks5 proxy or else
#dscp(8) -> game             

#using fw mark for ultra fast gaming experience
dscp(8) -> direct(mark:0x800)
}

OpenWRT Network Config (e.g. /etc/config/network)

Please choose the tunnel MTU carefully (CS2 Require MTU > 1300 due to UDP Ping (1300 bytes))

text
config interface 'wg100'                                                 
        option proto 'wireguard'                                         
        option private_key '[Client Private Key]'
        list addresses '10.7.0.2/24'                                    
        list addresses 'fd42:42:42::2/64'                               
        option mtu '1420'                                               
                                                                        
config wireguard_wg100                                                  
        option public_key '[Server Public Key]'
        option endpoint_host '[Your Server IP]'
        list allowed_ips '0.0.0.0/0'  
        list allowed_ips '::/0'       
                                      
config route                          
        option interface 'wg100'      
        option target '0.0.0.0/0'     
        option gateway '10.7.0.1'     
        option table '114'            
                                      
config route6                         
        option interface 'wg100'      
        option target '::/0'          
        option gateway 'fd42:42:42::1'
        option table '114'       
                                 
config rule                      
        option lookup '114'      
        option mark '0x800/0x800'
                                 
config rule6                     
        option lookup '114'      
        option mark '0x800/0x800'

OpenWRT Firewall Config (e.g. /etc/config/firewall)

text
config nat                       
        option src 'vpn'         
        option src_ip '[Gaming PC IPv4 Address]'
        option target 'SNAT'         
        option snat_ip '10.7.0.2'    
        option family 'ipv4'         
        list proto 'all'    

config nat                     
        option src 'vpn'
         option src_ip '[Gaming PC IPv6 Address]'
        option target 'SNAT'
        option snat_ip 'fd42:42:42::2'
        option family 'ipv6'
        list proto 'all'

File: docs/en/configuration/routing.md

Routing

Examples

text
/* Detailed source-code truncated for AI context efficiency. */

File: docs/en/configuration/separate-config.md

Separate Configuration Files

Sometimes you want to break your configuration file into several files. It may be useful in the following cases:

  1. You want to switch nodes via modify the config file using tools like sed.
  2. You copy other's configuration file and you want to overwrite some parts of it.

Example

Directory Structure:

sh
# tree /etc/dae
/etc/dae
โ”œโ”€โ”€ config.d
โ”‚  โ”œโ”€โ”€ dns.dae
โ”‚  โ”œโ”€โ”€ node.dae
โ”‚  โ””โ”€โ”€ route.dae
โ””โ”€โ”€ config.dae

Config files:

Notes about include paths:

  • Relative paths (for example config.d/*.dae) are resolved relative to the directory of the entry config file (the file passed to dae -c ...), not relative to the current working directory.
  • Absolute paths (for example /etc/dae/config.d/*.dae) are used as-is.
  • For security reasons, dae only allows included files under the entry config directory.
jsonc
# config.dae

# load all dae files placed in ./config.d/
include {
    # Relative path example:
    config.d/*.dae

    # Absolute path example:
    /etc/dae/config.d/*.dae
}
global {
    tproxy_port: 12345

    log_level: warn

    tcp_check_url: 'http://cp.cloudflare.com'
    udp_check_dns: 'dns.google:53'
    check_interval: 600s
    check_tolerance: 50ms

    #lan_interface: eth0
    wan_interface: eth0
    allow_insecure: false

    dial_mode: domain
    disable_waiting_network: false
    auto_config_kernel_parameter: true
    sniffing_timeout: 30ms
}
jsonc
# dns.dae
dns {
    upstream {
        alidns: 'udp://dns.alidns.com:53'
        googledns: 'tcp+udp://dns.google:53'
    }

    routing {
        request {
            qname(geosite:category-ads) -> reject
            qname(geosite:category-ads-all) -> reject
            fallback: alidns
        }
        response {
            upstream(googledns) -> accept
            !qname(geosite:cn) && ip(geoip:private) -> googledns
            fallback: accept
        }
    }
}
jsonc
# node.dae
node {
    node1: 'xxx'
    node2: 'xxx'
}

subscription {
    my_sub: 'https://www.example.com/subscription/link'
}

group {
    my_group {
        filter: subtag(my_sub) && !name(keyword: 'ExpireAt:')
        policy: min_moving_avg
    }

    local_group {
        filter: name(node1, node2)
        policy: fixed(0)
    }
}
jsonc
# route.dae
routing {
    pname(NetworkManager) -> direct
    dip(224.0.0.0/3, 'ff00::/8') -> direct
    dip(geoip:private) -> direct

    dip(1.14.5.14) -> direct

    domain(geosite:openai) -> local_group
    dip(geoip:cn) -> direct
    domain(geosite:cn) -> direct
    domain(geosite:category-scholar-cn) -> direct
    domain(geosite:geolocation-cn) -> direct


    fallback: my_group
}

Then run dae via:

sh
dae run -c /etc/dae/config.dae

File: docs/en/tutorials/dae-with-opnsense.md

dae with OPNsense: Best Practices

This tutorial shows how to use dae with OPNsense in a bypass way. dae is installed on another Linux system and is connected to OPNsense via Ethernet (physical connection, Linux bridge, or SR-IOV).

Interfaces

You should assign an address for the interface between dae and OPN that is in a different subnet from OPN's LAN. If we call this interface wan_proxy, the configuration is as follows:

text
OPN LAN: 192.168.1.1/24
OPN wan_proxy: 192.168.2.2 Gateway Auto Detect
dae enp1s0: 192.168.2.1 Gateway 192.168.2.2

Traffic Splitting

  1. Configure the GeoIP list

    Add it in Firewall: Aliases: GeoIP Settings, refer to the OPN documentation.

  2. Configure GeoIP alias

    Add an alias named proxyip in Firewall: Aliases: Aliases, select GeoIP type, and choose China in the displayed region Asia(or your own country).

  3. Add additional IP address list (optional)

    Add an alias named proxyip_ex in Firewall: Aliases: Aliases, select URL Table type, you can add a link to an IP list maintained by others, the file content is an IP address represented by CIDR per line.

  4. Configure reserved address alias

    Add an alias named __private_network in Firewall: Aliases: Aliases, select Network type, add all reserved addresses (or only add the reserved addresses used in your network), refer to Reserved IP Addresses.

  5. Aggregate the above aliases

    Add an alias named proxyroute in Firewall: Aliases: Aliases, select Network group type, select proxyip, proxyip_ex (if any), __private_network and the system built-in __lo0_network alias, and aggregate them.

  6. Add gateway

    Add a gateway named proxy in System: Gateways: Single, select the interface wan_proxy between dae, the IP is the IP of dae, according to the interface example above, fill in 192.168.2.1 here, the priority must be lower than the default gateway, such as the default gateway set to 254, set this to 255.

  7. Traffic splitting rules

    Add a rule in Firewall: Rules: Floating, configure as follows:

    Item Configuration
    Action Pass
    Quick โˆš
    Interface LAN
    Direction in
    TCP/IP Version IPv4
    Protocol TCP/UDP
    Destination/Invert โˆš
    Destination proxyroute
    Gateway proxy

    In addition, you can exclude LAN devices through Source/Invert, so their traffic will not be passed to dae.

  8. Allow dae traffic to enter OPN

    Create a new rule in Firewall: Rules: wan_proxy, keep all defaults and save.

  9. OPN's own proxy (optional)

    If you need to route some of OPN's own traffic through the proxy, such as using Google Drive to backup configurations, it is recommended to add a static route rule in System: Routes: Configuration, set the gateway for the IP segment that needs to be proxied to proxy. It is not recommended to handle WAN traffic in floating rules, which may cause loops.

This section does not involve the content of the dae configuration file, but only gives how to configure DNS requests to pass through dae, and solutions to the common problem that the proxy is normal and the direct connection is not working. For the domain and ip modes mentioned below and how to configure dae's dns and routing rules, please refer to the dae documentation.

To use dae for transparent proxy, to make the domain name based traffic splitting rules work normally, DNS requests need to pass through dae in the domain and domain+ modes (note that the DNS server is not set to the dae address, dae does not listen to port 53). If DNS requests do not pass through dae, you need to use dae's domain++ mode (match the traffic splitting rules again based on the sniffed domain name, which is not as performant as the domain mode). If you use the domain++ mode, or do not need to split traffic based on the domain name and use the ip mode, the following configuration can be ignored.

  1. DNS forwarding configuration

    Set in Services: Unbound DNS: Query Forwarding, forward DNS requests to the specified server, such as configuring OpenDNS's 208.67.222.222. The next step requires setting a static route rule, setting the gateway of this address to dae, so do not use the DNS issued by your upstream, so that you can normally use dig or nslookup to query the DNS server issued by the upstream for testing when troubleshooting DNS problems.

  2. Static route configuration

    Add a static route rule in System: Routes: Configuration, set the network to 208.67.222.222/32, and set the gateway to proxy.

After the above configuration, DNS requests can pass through dae and be hijacked by dae for processing. The DNS server set here is not the final query server. The target server of the DNS query will be rewritten by dae according to the dns rules in the dae configuration, and then the DNS query request will be sent.

It should be noted that Unbound will append EDNS related parameters when forwarding client's DNS requests, which may result in oversized (occasionally larger than 2000) DNS responses from the upstream server, causing dae's buffer for processing udp DNS to overflow (for performance considerations, dae does not use a larger buffer, tcp's DNS will not overflow), and the final result is that the client cannot get the DNS response, or even cause dae to crash. To solve this problem, you can switch to Dnsmasq or disable EDNSSEC support in Services: Unbound DNS: General and write the following Unbound configuration, which can effectively reduce the size of the returned DNS response.

yaml
# saved as /usr/local/etc/unbound.opnsense.d/disableedns.conf
server:
    disable-edns-do: yes

In addition, since dae does not perform snat, if the proxy is normal and the direct connection is not working [here refers to the direct in dae's routing, not the traffic that OPN does not split to dae and directly goes out of the WAN port. For example, according to the traffic splitting rules configured in the previous section, OPN will split the steam's traffic to pass through dae, dae has configured domain(geosite:steam@cn) -> direct in routing, steam cannot log in or download normally], configure nat in the system where dae is installed.

Performance Optimization

Changing the MTU value between OPN and dae from the default 1500 to 9000 (requires modifying both interfaces and the intermediate link) can achieve lower load.


File: docs/en/tutorials/run-on-alpine.md

Run on Alpine Linux

Note:

  1. Alpine Linux 3.18 or newer verison has full eBPF support out-of-box, older version of Alpine Linux need to build kernel by yourself.
  2. From version 3.20, Alpine Linux has officially disabled some features dae needed beacuse of Alpine Linux's cross CPU architectures compatibility, so only linux-virt can be used to run dae defaultly. For linux-lts or linux-edge, you should build the kernel by yourself.
  3. This tutorial is for Alpine Linux 3.20 and newer.

Enable Community Repo

Run setup-apkrepos command, then you'll get a menu list like this:

text
(f)    Find and use fastest mirror
 (s)    Show mirrorlist
 (r)    Use random mirror
 (e)    Edit /etc/apk/repositories with text editor
 (c)    Community repo enable
 (skip) Skip setting up apk repositories

Then input c to enable community repo.

Enable CGroups

Enable cgroups service:

sh
rc-update add cgroups boot

Mount bpf

Edit /etc/init.d/sysfs:

sh
vi /etc/init.d/sysfs

Add the following to the mount_misc section:

sh
# Setup Kernel Support for bpf file system
        if [ -d /sys/fs/bpf ] && ! mountinfo -q /sys/fs/bpf; then
                if grep -qs bpf /proc/filesystems; then
                ebegin "Mounting eBPF filesystem"
                mount -n -t bpf -o ${sysfs_opts} bpffs /sys/fs/bpf
                eend $?
                fi
        fi

Be careful that the format of the script /etc/init.d/sysfs must be correct, or sysfs service will be failed.

Install dae

Installer: https://github.com/daeuniverse/dae-installer/

This installer offered an OpenRC service script of dae, after installation, you should add a config file to /usr/local/etc/dae/config.dae, then set its permission to 600 or 640:

sh
chmod 640 /usr/local/etc/dae/config.dae

If your config file is ready to work, then you can start dae service:

sh
rc-service dae start

Start dae at boot

Use rc-update to enable dae service:

sh
rc-update add dae