Andrevi.CH BLOG

Blog about IT, Life and stuff

Social Networks

“It is in your hands, to make a better world for all who live in it.” – Nelson Mandela

In this guide we will install the sing-box package on OpenWRT using the stable 22.03.5 and 23.05.0 as an example. A router with at least 128 MB of RAM (256 preferable) and a memory of more than 16 MB is recommended; a method for installing sing-box into RAM will also be described (suitable for devices with a small amount of ROM <16 MB)

Sing-Box is a free and open-source proxy platform that allows users to bypass internet censorship and access blocked websites. This is an alternative to v2ray-core and xray-core. It can be used with various clients on platforms such as Windows, macOS, Linux, Android and iOS.

In addition to supporting the Shadowsocks (including 2022), Trojan, Vless, Vmess and Socks protocols, it also supports ShadowTLS, Hysteria and NaiveProxy.

The guide will include:

  1. Installation from the repository
  2. Setting up sing-box for shadowsocks, vless, vmess, trojan and bypassing blocking using SagerNet GeoIP, Geosite
  3. Setting up block bypass using GeoIP, Geosite from L11R
  4. Installing sing-box in RAM and setting up autorun

1. Installation of sing-box

For OpenWRT version 22.03.5 you need to download and install the current version of sing-box:

cd /tmp
wget https://downloads.openwrt.org/releases/23.05.0/packages/$(grep "OPENWRT_ARCH" /etc/os-release | awk -F '"' '{print $2}')/packages/sing-box_1.6.0-1_$(grep "OPENWRT_ARCH" /etc/os-release | awk -F '"' '{print $2}').ipk
opkg install sing-box_*.ipk
rm sing-box_*.ipk

Since version 23.05.0 sing-box is available in the standard OpenWRT repository.

Update the list of packages:

opkg update

Next, install the kernel modules necessary for sing-box operation and the iptables compatibility package:

We are waiting for the installation to complete, the packages took up about 1MB of memory.

Next we move on to installing sing-box

opkg install kmod-inet-diag kmod-netlink-diag kmod-tun iptables-nft

We are waiting for the installation to complete, the packages took up about 1MB of memory.

Next we move on to installing sing-box

opkg install sing-box

The package takes up about 10 MB, so it will not be possible to install it on devices with 16 MB ROM without additional manipulations (more on this in paragraph 3 of this article).

If the package is successfully installed, proceed to setting up the connection; if not, proceed to paragraph 3.

2. Setting up sing-box for shadowsocks, reality, vmess, trojan and bypassing blocking using SagerNet GeoIP, Geosite

UPD 11/12/2023 Next, go to the configuration file, by default it is /etc/sing-box/config.json, but /etc/sing-box/config.json.example is available during installation. In version 1.6.0, /etc/sing-box/config.json is available by default, the file /etc/sing-box/config.json.example is no longer present during installation

Delete the default /etc/sing-box/config.json:

> /etc/sing-box/config.json

I will give an example of a config.json file for setting up both Outline VPN (the release of keys and their decryption into a password and the type of encryption from the base64 format, I reviewed here) and XTLS-Reality, VMess TLS and Trojan Websocket. The configuration uses a selector that selects only working proxies using urltest. Numerous example configurations for various protocols are available on the sing-box project website, Github user malikshi and vpnrouter.homes.

Example of config.json:

{
    "log": {
        "disabled": false,
        "level": "warn",
        "output": "/tmp/sing-box.log",
        "timestamp": true
    },
    "dns": {
        "servers": [
            {
                "tag": "google",
                "address": "tls://8.8.8.8"
            },
            {
                "tag": "block",
                "address": "rcode://success"
            }
        ],
        "final": "google",
        "strategy": "prefer_ipv4",
        "disable_cache": false,
        "disable_expire": false
    },
    "inbounds": [
        {
            "type": "mixed",
            "tag": "mixed-in",
            "listen": "127.0.0.1",
            "listen_port": 1080,
            "tcp_fast_open": false,
            "sniff": true,
            "sniff_override_destination": true,
            "set_system_proxy": false
        },
        {
            "type": "tun",
            "tag": "tun-in",
            "interface_name": "singtun0",
            "inet4_address": "172.19.16.1/30",
            "stack": "gvisor",
            "mtu": 9000,
            "auto_route": true,
            "strict_route": false,
            "endpoint_independent_nat": false,
            "sniff": true,
            "sniff_override_destination": true
        }
    ],
    "outbounds": [
        {
            "type": "selector",
            "tag": "Proxy-out",
            "outbounds": [
                "URL-Test",
                "direct",
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "default": "URL-Test"
        },
        {
            "type": "urltest",
            "tag": "URL-Test",
            "outbounds": [
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "url": "http://www.gstatic.com/generate_204",
            "interval": "1m",
            "tolerance": 50
        },
        {
            "type": "shadowsocks",
            "tag": "shadowsocks-out",
            "server": "IP",
            "server_port": 15000,
            "method": "chacha20-ietf-poly1305",
            "password": "password"
        },
        {
            "type": "vless",
            "tag": "reality-out",
            "server": "IP",
            "server_port": 8442,
            "uuid": "UUID",
            "flow": "xtls-rprx-vision",
            "network": "tcp",
            "tls": {
                "enabled": true,
                "insecure": false,
                "server_name": "SERVERNAME",
                "utls": {
                    "enabled": true,
                    "fingerprint": "chrome"
                },
                "reality": {
                    "enabled": true,
                    "public_key": "AP24JYROAB8odK5glVW_KLnsWl3UZ-voaGq_9ihQgTL"
                }
            }
        },
        {
            "type": "trojan",
            "tag": "trojan-WebSocket-out",
            "server": "IP",
            "server_port": 8443,
            "password": "PASSWORD",
            "transport": {
                "type": "ws",
                "path": "/",
                "early_data_header_name": "Sec-WebSocket-Protocol"
            },
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "d43f429a97e4ea6d.gstatic.com"
            },
            "multiplex": {
                "enabled": true,
                "max_connections": 4,
                "min_streams": 4,
                "max_streams": 0
            }
        },
        {
            "type": "vmess",
            "tag": "vmess-tls-out",
            "server": "IP",
            "server_port": 8444,
            "uuid": "UUID",
            "security": "auto",
            "alter_id": 0,
            "global_padding": false,
            "authenticated_length": true,
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "google.com",
                "insecure": false,
                "alpn": [
                    "http/1.1"
                ]
            },
            "multiplex": {
                "enabled": true,
                "protocol": "smux",
                "max_connections": 5,
                "min_streams": 4,
                "max_streams": 0
            },
            "connect_timeout": "5s"
        },
        {
            "type": "direct",
            "tag": "direct"
        },
        {
            "type": "block",
            "tag": "block"
        },
        {
            "type": "dns",
            "tag": "dns-out"
        }
    ],
    "route": {
        "geoip": {
            "path": "/tmp/geoip.db",
            "download_url": "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db",
            "download_detour": "Proxy-out"
        },
        "geosite": {
            "path": "/tmp/geosite.db",
            "download_url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db",
            "download_detour": "Proxy-out"
        },
        "rules": [
            {
                "protocol": "dns",
                "outbound": "dns-out"
            },
            {
                "protocol": [
                    "quic"
                ],
                "outbound": "block"
            },
            {
                "geosite": [
                    "private",
                    "youtube",
                    "google",
					"yandex"
                ],
                "geoip": [
                    "private",
                    "ru"
                ],
                "ip_cidr": [
                    "94.100.180.201/32",
                    "94.100.180.202/32"
                ],
                "domain_keyword": [
                    "mail.ru",
                    "vk.com"
                ],
				"domain_suffix": [
                    ".ru"
                  ],
                "outbound": "direct"
            }
        ],
        "final": "Proxy-out",
        "auto_detect_interface": true
    }
}

The configuration writes warnings and errors to the /tmp/sing-box.log log, raises socks5 proxy on port 1080, raises the singtun0 tunnel using kmod-tun and gvisor.

The outbounds section contains configurations for XTLS-Reality, VMess TLS and Trojan Websocket:

{
  "type": "shadowsocks",
  "tag": "shadowsocks-out",
....
},
{
  "type": "vless",
  "tag": "reality-out",
....
},
{
"type": "trojan", 
"tag": "trojan-WebSocket-out",
....
},
{
  "type": "vmess",
  "tag": "vmess-tls-out",
.....
},

If necessary, replace or delete unnecessary entries, not forgetting to remove or add them in the selector and urltest sections

            "type": "selector",
            "tag": "Proxy-out",
            "outbounds": [
                "URL-Test",
                "direct",
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "default": "URL-Test"
        },
        {
            "type": "urltest",
            "tag": "URL-Test",
            "outbounds": [
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"

The “route” “rules” section contains rules for direct connection to sites in the .ru zone and other unblocked resources:

“geosite”: “private”, “youtube”, “google”, “yandex” – denotes domains: private addresses (including .local), YouTube and subdomains, Google services, Yandex services.

“geoip”: “private”, “ru” – denotes IP address ranges: private addresses, IP address ranges of the .RU segment

“ip_cidr” – in this section you can specify ranges of IP addresses that should connect directly without a proxy

“domain_suffix”

The rule matches if the request domain matches the suffix. For example: “google.com” matches “www.google.com”, “mail.google.com” and “google.com”, but does not match “content-google.com”.

“domain_keyword”

The rule matches if the request domain contains the keyword.

When editing json files, you can use tools like JSON Online Validator to check formatting

In the outbounds section you need to enter the parameters of your (your) proxy: IP, port, password, uuid, public key (if applicable).

the download_detour parameter is used to specify the method of downloading databases, in the case of configuration – through a proxy

For work, SagerNet lists from the developer sing-box are used; they do not contain lists of resources blocked in the Russian Federation and therefore many unblocked sites can be opened through a proxy.

For sing-box to work, you need to configure the firewall settings, for this:

Add to the /etc/config/network file:

config interface 'proxy'
	option proto 'none'
	option device 'singtun0'

To /etc/config/firewall:

config zone
    option name 'proxy'
    list network 'tunnel'
    option forward 'REJECT'
    option output 'ACCEPT'
    option input 'REJECT'
    option masq '1'
    option mtu_fix '1'
    option device 'singtun0'
    option family 'ipv4'

config forwarding
    option name 'lan-proxy'
    option dest 'proxy'
    option src 'lan'
    option family 'ipv4'

Then restart the network:

/etc/init.d/network restart

Next, we check the functionality of the configuration:

sing-box check -c /etc/sing-box/config.json

If everything is correct, the command will not produce errors.

Next, we check the operation of the proxy:

sing-box run -c /etc/sing-box/config.json

You can check the functionality by opening the site you need.

If everything worked, we can add sing-box to autostart, to do this we enter the commands:

Add sing-box to autorun:

/etc/init.d/sing-box enable
/etc/init.d/sing-box start

This completes the sing-box setup.

If sing-box is configured for routing using firewall4, that is, classic routes to singtun0, the /etc/sing-box/config.json configuration will be simplified and look like this:

{
    "log": {
        "disabled": false,
        "level": "warn",
        "output": "/tmp/sing-box.log",
        "timestamp": true
    },
    "dns": {
        "servers": []
    },
    "inbounds": [
        {
            "type": "tun",
            "tag": "tun-in",
            "interface_name": "singtun0",
            "inet4_address": "172.19.16.1/30",
            "stack": "gvisor",
            "mtu": 9000,
            "auto_route": false,
            "strict_route": false,
            "endpoint_independent_nat": false,
            "sniff": true,
            "sniff_override_destination": true
        }
    ],
    "outbounds": [
        {
            "type": "selector",
            "tag": "Proxy-out",
            "outbounds": [
                "URL-Test",
                "direct",
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "default": "URL-Test"
        },
        {
            "type": "urltest",
            "tag": "URL-Test",
            "outbounds": [
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "url": "http://www.gstatic.com/generate_204",
            "interval": "1m",
            "tolerance": 50
        },
        {
            "type": "shadowsocks",
            "tag": "shadowsocks-out",
            "server": "IP",
            "server_port": 15000,
            "method": "chacha20-ietf-poly1305",
            "password": "password"
        },
        {
            "type": "vless",
            "tag": "reality-out",
            "server": "IP",
            "server_port": 8442,
            "uuid": "UUID",
            "flow": "xtls-rprx-vision",
            "network": "tcp",
            "tls": {
                "enabled": true,
                "insecure": false,
                "server_name": "SERVERNAME",
                "utls": {
                    "enabled": true,
                    "fingerprint": "chrome"
                },
                "reality": {
                    "enabled": true,
                    "public_key": "AP24JYROAB8odK5glVW_KLnsWl3UZ-voaGq_9ihQgTL"
                }
            }
        },
        {
            "type": "trojan",
            "tag": "trojan-WebSocket-out",
            "server": "IP",
            "server_port": 8443,
            "password": "PASSWORD",
            "transport": {
                "type": "ws",
                "path": "/",
                "early_data_header_name": "Sec-WebSocket-Protocol"
            },
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "d43f429a97e4ea6d.gstatic.com"
            },
            "multiplex": {
                "enabled": true,
                "max_connections": 4,
                "min_streams": 4,
                "max_streams": 0
            }
        },
        {
            "type": "vmess",
            "tag": "vmess-tls-out",
            "server": "IP",
            "server_port": 8444,
            "uuid": "UUID",
            "security": "auto",
            "alter_id": 0,
            "global_padding": false,
            "authenticated_length": true,
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "google.com",
                "insecure": false,
                "alpn": [
                    "http/1.1"
                ]
            },
            "multiplex": {
                "enabled": true,
                "protocol": "smux",
                "max_connections": 5,
                "min_streams": 4,
                "max_streams": 0
            },
            "connect_timeout": "5s"
        },
        {
            "type": "direct",
            "tag": "direct"
        },
        {
            "type": "block",
            "tag": "block"
        }
    ],
    "route": {
        "rules": [],
        "final": "Proxy-out",
        "auto_detect_interface": true
    }
}

3.Setting up block bypass using GeoIP, Geosite from L11R

This method with GeoIP and Geosite containing the entire dump of the RKN database (the author of these files is @L11R, many thanks to him for this). Unfortunately, this method is demanding on the amount of RAM, I started with at least 384 MB, otherwise the memory killer would work. The convenience of this method is that it is a replacement for BGP (or downloading lists), traffic to addresses from databases goes through a proxy, everything else goes directly.

An example of this config.json:

{
    "log": {
        "disabled": false,
        "level": "warn",
        "output": "/tmp/sing-box.log",
        "timestamp": true
    },
    "dns": {
        "servers": [
            {
                "tag": "google",
                "address": "tls://8.8.8.8"
            },
            {
                "tag": "block",
                "address": "rcode://success"
            }
        ],
        "final": "google",
        "strategy": "prefer_ipv4",
        "disable_cache": false,
        "disable_expire": false
    },
    "inbounds": [
        {
            "type": "mixed",
            "tag": "mixed-in",
            "listen": "127.0.0.1",
            "listen_port": 1080,
            "tcp_fast_open": false,
            "sniff": true,
            "sniff_override_destination": true,
            "set_system_proxy": false
        },
        {
            "type": "tun",
            "tag": "tun-in",
            "interface_name": "singtun0",
            "inet4_address": "172.19.16.1/30",
            "stack": "gvisor",
            "mtu": 9000,
            "auto_route": true,
            "strict_route": false,
            "endpoint_independent_nat": false,
            "sniff": true,
            "sniff_override_destination": true
        }
    ],
    "outbounds": [
        {
            "type": "selector",
            "tag": "Proxy-out",
            "outbounds": [
                "URL-Test",
                "direct",
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "default": "URL-Test"
        },
        {
            "type": "urltest",
            "tag": "URL-Test",
            "outbounds": [
                "shadowsocks-out",
                "vmess-tls-out",
                "trojan-WebSocket-out",
                "reality-out"
            ],
            "url": "http://www.gstatic.com/generate_204",
            "interval": "1m",
            "tolerance": 50
        },
        {
            "type": "shadowsocks",
            "tag": "shadowsocks-out",
            "server": "IP",
            "server_port": 15000,
            "method": "chacha20-ietf-poly1305",
            "password": "password"
        },
        {
            "type": "vless",
            "tag": "reality-out",
            "server": "IP",
            "server_port": 8442,
            "uuid": "UUID",
            "flow": "xtls-rprx-vision",
            "network": "tcp",
            "tls": {
                "enabled": true,
                "insecure": false,
                "server_name": "SERVERNAME",
                "utls": {
                    "enabled": true,
                    "fingerprint": "chrome"
                },
                "reality": {
                    "enabled": true,
                    "public_key": "AP24JYROAB8odK5glVW_KLnsWl3UZ-voaGq_9ihQgTL"
                }
            }
        },
        {
            "type": "trojan",
            "tag": "trojan-WebSocket-out",
            "server": "IP",
            "server_port": 8443,
            "password": "PASSWORD",
            "transport": {
                "type": "ws",
                "path": "/",
                "early_data_header_name": "Sec-WebSocket-Protocol"
            },
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "d43f429a97e4ea6d.gstatic.com"
            },
            "multiplex": {
                "enabled": true,
                "max_connections": 4,
                "min_streams": 4,
                "max_streams": 0
            }
        },
        {
            "type": "vmess",
            "tag": "vmess-tls-out",
            "server": "IP",
            "server_port": 8444,
            "uuid": "UUID",
            "security": "auto",
            "alter_id": 0,
            "global_padding": false,
            "authenticated_length": true,
            "tls": {
                "enabled": true,
                "disable_sni": false,
                "server_name": "google.com",
                "insecure": false,
                "alpn": [
                    "http/1.1"
                ]
            },
            "multiplex": {
                "enabled": true,
                "protocol": "smux",
                "max_connections": 5,
                "min_streams": 4,
                "max_streams": 0
            },
            "connect_timeout": "5s"
        },
        {
            "type": "direct",
            "tag": "direct"
        },
        {
            "type": "block",
            "tag": "block"
        },
        {
            "type": "dns",
            "tag": "dns-out"
        }
    ],
     "route": {
        "geoip": {
            "path": "/etc/sing-box/geoip.db",
            "download_url": "https://github.com/L11R/antizapret-sing-geosite/releases/latest/download/geoip.db",
            "download_detour": "Proxy-out"
        },
        "geosite": {
            "path": "/etc/sing-box/geosite.db",
            "download_url": "https://github.com/L11R/antizapret-sing-geosite/releases/latest/download/geosite.db",
            "download_detour": "Proxy-out"
        },
        "rules": [
            {
                "protocol": "dns",
                "outbound": "dns-out"
            },
            {
                "geoip": "antizapret",
                "geosite": "antizapret",
                "outbound": "Proxy-out"
            },
			{
                "protocol": "quic",
                "outbound": "block"
            }
        ],
        "final": "direct",
        "auto_detect_interface": true
    }
}

If you remove following lines from the configuration:

        {
            "type": "mixed",
            "tag": "mixed-in",
            "listen": "127.0.0.1",
            "listen_port": 1080,
            "tcp_fast_open": false,
            "sniff": true,
            "sniff_override_destination": true,
            "set_system_proxy": false
        },

and "path": "/etc/sing-box/geoip.db" , "path": "/etc/sing-box/geosite.db",

This configuration can also be used in the sing-box client on Android and other platforms

4. Installing sing-box in RAM

If you could not install sing-box in ROM (and this is a quite likely scenario for most mid-price routers), it is possible to install it in RAM and load the package when the device starts.

Installed and running sing-box takes up about 35 MB of RAM.

First, we need to install the kernel modules and iptables-nft into the ROM

opkg install kmod-inet-diag kmod-netlink-diag kmod-tun iptables-nft

This should take about 1MB of memory, after which we will install the sing-box in RAM:

opkg install sing-box -d ram

If everything is installed successfully, next you will need to create a configuration folder:

mkdir /etc/sing-box

Then place your config.json file from step 2 to /etc/sing-box/config.json

Let’s check the work of the proxy (more details in step 2):

 /tmp/usr/bin/sing-box run -c /etc/sing-box/config.json

If everything works, then we set up the autostart of sing-box.

To automatically install the package when the system boots, add lines to the file /etc/rc.local before exit 0

opkg update
opkg install sing-box -d ram
exit 0

using a text editor, or via Luci

We save the changes and create an autostart service for sing-box.

Create a file /etc/init.d/sing-box with the following content:

#!/bin/sh /etc/rc.common
#
# Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

START=99
USE_PROCD=1

#####  ONLY CHANGE THIS BLOCK  ######
PROG=/tmp/usr/bin/sing-box # sing-box location in RAM
RES_DIR=/etc/sing-box/ # resource dir / working dir / the dir where you store ip/domain lists
CONF=./config.json   # where is the config file, it can be a relative path to $RES_DIR
#####  ONLY CHANGE THIS BLOCK  ######

start_service() {
  sleep 10 # Waiting for sing-box package to download
  procd_open_instance
  procd_set_param command $PROG run -D $RES_DIR -c $CONF

  procd_set_param user root
  procd_set_param limits core="unlimited"
  procd_set_param limits nofile="1000000 1000000"
  procd_set_param stdout 1
  procd_set_param stderr 1
  procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
  procd_close_instance
  iptables -I FORWARD -o singtun+ -j ACCEPT #This will give error if iptables-nft is not installed
  echo "sing-box is started!"
}

stop_service() {
  service_stop $PROG
  iptables -D FORWARD -o singtun+ -j ACCEPT
  echo "sing-box is stopped!"
}

reload_service() {
  stop
  sleep 5s
  echo "sing-box is restarted!"
  start
}

Making the file executable:

chmod +x /etc/init.d/sing-box

Then add to autorun:

/etc/init.d/sing-box enable
/etc/init.d/sing-box start

This completes the setup.

P.S. 35 MB is a fairly large amount of RAM for a router with 128 MB, so the solution is on the limit.

Leave a comment