欢迎使用ruci!

本手册 是面向使用ruci 作为 代理的用户 而写的用户手册, 旨在让您快速上手。

手册的侧重点 与 代码文档不同。

代码文档(项目 doc文件夹中不在book 目录下的文件)是面向开发者的, 一般有一些 "如何编译" 的内容.

而手册专注于帮助 通过 release 下载程序包的 用户 快速学会使用ruci.

让我们开始吧! 入门

本手册基于 ruci v0.0.8 制作

ruci 入门

ruci 的可执行文件叫做 ruci-cmd, 客户端和服务端都是使用这同一个程序。

安装ruci

从这里下载 ruci-cmd 的最新发布版:Release

发布版 以 .tar.xz 为后缀,是一个压缩包,包含 ruci-cmd 以及相关的 resource 文件夹。

压缩包对于不同的平台有不同的后缀。

windows

建议下载后缀为 x86_64-pc-windows-msvc.tar.xz 的版本

下载后,可以用 7zip 来解压,先解压出一个 tar, 再解压一遍得到程序。(或双击用7zip 打开后,进入tar之后再将其内容拖拽出来)

第一次运行时,windows 可能弹出提示,询问是否允许连接到网络,同意即可。

macOS

apple silicon(m1,m2,m3,m4) 下载 aarch64-apple-darwin.tar.xz

老机型下载 x86_64-apple-darwin.tar.xz

解压:

tar xf archive.tar.xz

第一次运行时,macOS 会提示您该程序不受信任,您可以到 设置-隐私与安全 中,许可本程序的使用。

注:编译运行则不会出现此提示。

x64 linux

建议 x64的 linux 用户下载 后缀为 x86_64-unknown-linux-gnu.tar.xz 的版本

如果运行闪退,则可以下载 后缀为 x86_64-unknown-linux-musl.tar.xz 的版本

解压:

tar xf archive.tar.xz
chmod +x ruci-cmd

若报 xz: Cannot exec: No such file or directory,可运行下面命令(ubuntu)安装 xz, 安了就好了:

sudo apt install xz-utils

安卓

termux 用户可以下载 后缀为 aarch64-linux-android.tar.xz 的版本

可以先在电脑上解压好,再传到手机中

之后在 termux 中

chmod +x ruci-cmd

开始使用

在ruci-cmd 所在的文件夹中:

windows下,打开 cmd 或 powershell, 运行:

.\ruci-cmd.exe

其它平台,进入终端,输入

./ruci-cmd

为了保持本文的简洁,下面命令行示例统一使用 linux 的格式。

运行ruci-cmd后它会在相同目录下的 resource 文件夹 或 ruci_config 文件夹寻找 local.lua 文件,如果 找到了,就会运行,否则就会退出。

运行时,会同时在 ruci-cmd 的当前目录下生成一个 logs 文件夹,用于存放生成的日志文件。

如果要指定配置文件运行,可以加 -c 参数:

./ruci-cmd -c remote.lua
./ruci-cmd -c local.json

如果要了解编译等方面的细节,可参考 这里

为了不让 resource 文件夹中的示例文件影响您的自定义配置,您可以把 resource 文件夹重命名为其它名称, 然后建立一个 ruci_config 文件夹,将您的配置文件放在 ruci_config 文件夹中。

resource 文件夹中的内容有助于参考使用,建议保留。

调节日志等级为 debug:

./ruci-cmd -l debug

ruci-gui

另一种使用 ruci 的方式是使用 gui, 来自 ruci-webui 项目, 它使用 tauri 编译了 桌面和 安卓平台的 gui,内置了 ruci内核

https://github.com/e1732a364fed/ruci-webui/releases/ 下载最新的编译版本。

启动该gui后,可在 Control Panel 中 点击 “检查服务器状态”,它会显示 服务器状态: {"status":"running"} 这表示 内核已经正在运行。然后 点击 “选择配置文件”,再点击 “启动引擎”,就可以运行 您的 ruci 配置了。

ruci-gui 的 Node Editor 还提供了一种很方便的 “节点编辑器”,可以 以可视化的方式编辑您的配置文件。 而 Control Panel 中又提供了一些方便的小工具。

ruci-webui 项目的 release 分为 webui 和 gui 两种。gui为 tauri 生成的 桌面程序,而 webui 则为 一个 dist 压缩包,可用于在 ruci-cmd 中的 file-server中运行 (api-server 运行后会自动运行 file-server 服务 dist 文件夹)

接下来

命令行参数 与 程序运行

综述

我们 ruci 的可执行文件是 ruci-cmd.

所使用的配置文件的格式 可以是 lua (lua配置), 也可以是 json ).

lua配置更难写一些,但是更灵活一些。 json 配置更简单一些,但其功能则更少一些。

静态链、动态链

ruci 中引入了 “静态链、动态链” 的概念。这两个概念在ruci中至关重要,因此有必要先对其 有一个基本了解。

静态链就是传统的代理的配置方式,而 动态链 则是一种更高级的概念。

json 配置和基本的 lua配置都是 静态链配置.

而lua配置中还可以通过一些方式开启 动态链的配置。对于新手来说,先把静态链的写法学会就行了。

运行方式

本地运行:

./ruci-cmd -c local.lua

./ruci-cmd -c local.json

服务器运行:

./ruci-cmd -c remote.lua

./ruci-cmd -c remote.json

macOS 版本要在 系统App:Settings - Privacy & Security 里 allow 一下。 或者运行 xattr -c ruci-cmd

ruci-cmd 会在 下面文件夹中 找 指定的 配置文件

"./",
"ruci_config/",
"resource/",
"dev_res/"

因此如果不想使用 默认打包的 resource 文件夹,可以将其改名为 resource_default, 然后 自己创建一个 ruci_config 文件夹,将自己的配置放在 ruci_config 中,这样 就不会产生混淆

另外,配置文件名称是可以自定义的,不一定要叫 local.lua 或 remote.lua, 这只是 一种命名习惯而已。

日志

ruci-cmd 运行时产生的日志会自动创建并放在 logs 文件夹中, daily rolling

为了调整日志的等级,在运行参数中 有 两种选择:使用命令行参数 或者使用 环境变量

命令行参数法

-l, --log-level <LOG_LEVEL>

可为 ERROR, WARN, INFO , DEBUG, TRACE 小写也可以。

环境变量法

环境变量法比较高级:

在命令的开头加上

RUST_LOG=none,ruci=debug 

RUST_LOG=none,ruci=debug ./ruci-cmd -c local.lua

如果是powershell就是加上

$Env:RUST_LOG="none,ruci=debug";

$Env:RUST_LOG="none,ruci=debug"; .\ruci-cmd.exe -c local.lua

这么写 的效果是:过滤 log, 对其它依赖包的 日志通通不要,只留 ruci 包自己的日志

utils 工具包

ruci-cmd 提供了一些很方便的命令,可以执行一些辅助功能。

生成自签名根证书

./ruci-cmd utils gen-cer localhost www.mytest.com

会生成 generated_crt_and_key.crt

下载外部依赖资源

./ruci-cmd utils mmdb

./ruci-cmd utils wintun

配置文件格式转换:

ruci-cmd utils convert-format <INPUT_FILE> <OUTPUT_FORMAT> 如 ruci-cmd utils convert-format local.lua json

(lua, json) 几种格式在静态链下是可以互相转换的

转后就会生成 local.json. 如果同名文件存在,就会自动用一个新的名称,不会覆盖。

而且也可以 转换为同格式 ,相当于把 注释删掉然后 标准化一下

简易文件服务器

./ruci-cmd utils serve-folder
./ruci-cmd utils serve-folder 0.0.0.0:12345

serve-folder 命令 会将 ruci-cmd 当前工作目录下的 "static" 文件夹 作为 文件服务器的根路径。

它不会对用户打印出 static 文件夹中的任何文件,而只有当访问 static 中的用户指定的子文件夹时,才会显示其子文件夹的内容。 这样就保护了根路径的内容。

这个文件夹名不可更改,这是为了防止错误地将私密文件暴露。

如果不给出监听地址,会自动监听 "0.0.0.0:18143"。

示例链接

查看目录 http://0.0.0.0:18143/folder1

下载 http://0.0.0.0:18143/download/folder1/file1.zip

打包

./ruci-cmd utils pack folder1
./ruci-cmd utils pack-z folder1

pack 和 pack-z 命令 可以对工作目录下的指定文件夹 进行打包。

pack是打包为 tar 文件, pack-z 是在打包为 tar.zip 文件。

它会计算 打包好的 tar 文件的 md5 hash, 并将 该 md5 作为 tar 文件的文件名。

如果是 pack-z, 其依然使用 tar 的 md5 作为 文件名,而不是 zip 的 md5。

lua 命令行

./ruci-cmd utils repl

该命令可以启用一个 lua repl (read, execute, print, loop), 用户可以在里面执行一些lua代码。

高级用法

lua配置使用 infinite:

./ruci-cmd -c local.lua --infinite

运行配置的同时 开启 api-server:

./ruci-cmd -c remote.lua -a 

订阅

ruci 中使用一种非常简单的方法来实现订阅,分为三步:打包、服务文件(生成订阅url)、下载。

示例流程

./ruci-cmd utils pack-z resource
mkdir static
mv 83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip static/
./ruci-cmd utils serve-folder
./ruci-cmd -c http://0.0.0.0:18143/download/83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip --in-memory

打包

./ruci-cmd utils pack-z resource

它会把 ruci-cmd 目录下的 resource 文件夹中的全部内容打包为一个 {md5}.tar.zip 文件。 这个 zip文件里面只有一个文件,即 {md5}.tar, 而 {md5}.tar 里面则是 resource 文件夹中的所有文件内容(不包含resource 这个层级)

其中 {md5} 是 {md5}.tar 这个文件的 md5 哈希值。

服务文件(生成订阅url)

之后把 {md5}.tar.zip 文件移动到 ruci-cmd 所在目录的 static 文件夹下。若没有则创建一个。

mkdir static
mv 83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip static/

然后运行 ruci-cmd 的文件服务器

./ruci-cmd utils serve-folder

这样,订阅链接就会自动为 http://0.0.0.0:18143/download/83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip

注意,0.0.0.0 是本机地址,如果您要在公网,可以将ip换为 您的公网ip。

ruci 不提供https、用户鉴权的机制,您可以通过一些反向代理的方式来提供安全性。

在客户端使用订阅链接

正常使用配置文件时,是用的 ruci-cmd -c local.lua, 此时,只要把 -c 的参数改为对应的下载链接即可:

./ruci-cmd -c http://0.0.0.0:18143/download/83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip

该命令会下载该压缩包、保存到当前目录并使用。它会自动阅读包中的 local.lua 或 local.json 文件。

如果不想把下载的包保存,则可以加 --in-memory 选项。

如果已经保存了下载好的包,下一次使用时可以直接解压缩里面的内容使用,也可以直接用

./ruci-cmd -c 83c649c74b8a4c6ebc07a9a99ee350a0.tar.zip

或者

./ruci-cmd -c 83c649c74b8a4c6ebc07a9a99ee350a0.tar

注意,如果不解压缩,则不要修改 tar 或 tar.zip 的名字。因为名字要作为 md5 由 ruci-cmd 检查其包内容是否一致。 如果 包的实际内容的 md5 与 名字不一致,则 ruci-cmd 会拒绝运行。

接下来

lua配置

ruci 配置文件的基本命名逻辑是,

local.lua 代表 在客户端 的配置文件

remote.lua 代表在 服务端 的配置文件

入门用法如下, 使用 Config 变量:


Config = {
    inbounds = {},
    outbounds = {},

}

中级用法中,Config 中还有 fallback_route, tag_route, rule_route 这几项, 包在 routes 中:


Config = {
    inbounds = {},
    outbounds = {},
    routes = {
        fallback_route = {},
        tag_route = {},
    }
    
}

高级用法中,还有Infinite 配置

先学 简单的 Config 入门 吧。

关于 lua语法

在lua中,大括号 {} 被叫做 table, 它即可以当数组用也可以当"字典"用。

行注释以 -- 开始,块注释如下:

--[[

]]

如写 x = 1, 则 x会默认成为 全局变量,这不太好。因此一般都写成 local x = 1

字符串就是 "abc", 块级字符串为:

a = [[
    abcd
    abcd
]]

函数是 :

local function(c)
    return 1
end

接下来

Config入门

一般情况下,每个 配置文件里都要写一个 Config 块, 程序 读取解析这个 Config 后就运行。

让我们创建一个 local.lua 文件,内容如下:


Config = {
    inbounds = {},
    outbounds = {},

}

inbounds 是指 入站, 即 在这里设置 本地监听 的端口

outbounds 是指 出站, 即 在这里设置 远程服务器 的地址

因为可能有多个 入站和出站,所以 inbounds 和 outbounds 都是 列表, 也就是说,下面的示例分别有两个入站 和 三个出站


Config = {
    inbounds = { {}, {}}
    outbounds = { {}, {}, {}}
}

直接看 最简配置, 还是一步步学起?你说的算~

inbounds/outbounds

inbounds/outbounds 是 inbound/outbound 的列表:

inbounds = { inbound1_tag = {} , inbound2_tag = {}, ... }

inbound/outbound

每个 inbound/outbound 都由 一个 chain 和一个 tag 组成:

如下面 tag 为 listen1

{
    listen1 = {},
}

chain

每个 chain 都是一个 列表:

{ {}, {}, {}, ..}

它是 MapConfig 的列表.

如果在 inbound 中,则它是 InMapConfig 的列表, 如果在 outbound 中,则它是 OutMapConfig 的列表

每一种 MapConfig 都是一个 由 大括号 括起来的 table

我们先学简单的几个 Config

InMapConfig初探

先学两种 InMapConfig,Listener 和 Sock5Http

Listener

下面配置 监听 本地 tcp 端口 10800:

{
    type = "Listener", listen_addr = "0.0.0.0:10800"
}

Sock5Http

Sock5Http 可读取 socks5 协议 和 http代理 协议

{
    type = "Socks5Http"
}

合体

知道了 Listener 和 Sock5Http 这两个 InMapConfig 后,我们把它 合起来放到一个 chain 中:

{
    {
        type = "Listener", listen_addr = "0.0.0.0:10800"
    },
    {
        type = "Socks5Http"
    }
}

再把它包起来变成一个 inbound :

listen1 = {
    {
        type = "Listener", listen_addr = "0.0.0.0:10800"
    },
    {
        type = "Socks5Http"
    }
}

再把它放入 Config 的 inbounds 中, 作为 目前 inbounds 唯一的 inbound:

Config = {
    inbounds = {
        listen1 = {
            {
                type = "Listener", listen_addr = "0.0.0.0:10800"
            },
            {
                type = "Socks5Http"
            }
        }
    },

}

这样 我们第一个 inbounds 配置就做好了!

OutMapConfig初探

先学 最简单的 OutMapConfig Direct:

Direct

Direct 是最简单的 OutMap! 它就是直连:

{ type = "Direct" }

合体

它 Direct 块 放入 chain 中:

{ type = "Direct" },

再把它放入 Config 的 outbounds 中:

Config = {
    outbounds = {
        direct =  {
            { type = "Direct" },
        }
    },

}

再和 上面 inbounds 结合起来,第一个能用的 lua配置就做好了:

Config = {
    inbounds = {
        listen1 = {
            {
                type = "Listener", listen_addr = "0.0.0.0:10800"
            },
            {
                type = "Socks5Http"
            }
        }
    },
    outbounds = {
        direct =  {
            { type = "Direct" },
        }
    },
}

使用 lua 变量

上面写法是不是太长了?不要急!我们使用lua不是为了让配置变复杂,而是让它变简单,方法就是用变量替换!

我们把每个有意义的子块都给个 变量名

local direct_map = { type = "Direct" }
local listener = {  type = "Listener", listen_addr = "0.0.0.0:10800"  }
local sock5http = { type = "Socks5Http" }

(local 的用法不在此解释,照抄就行)

如此,整个 配置就变成了


local direct_map = { type = "Direct" }
local listener = {  type = "Listener", listen_addr = "0.0.0.0:10800"  }
local sock5http = { type = "Socks5Http" }

Config = {
    inbounds = {
        listen1 = { listener, sock5http  }
    },
    outbounds = {  direct =  {  direct_map,  }  },
}

最简配置

再替换一次:

local direct_map = { type = "Direct" }
local listener = {  type = "Listener", listen_addr = "0.0.0.0:10800"  }
local sock5http = { type = "Socks5Http" }

local listen_inbound = { listen1 = { listener, sock5http } }
local direct_outbound = { direct =  { direct_map, } }

Config = {
    inbounds = { listen_inbound },
    outbounds = { direct_outbound },
}

怎么样,是不是一下子就变得 清晰起来了?爽!先来个 high-five 吧!

配置您电脑的 系统代理 为 socks5或 http, 指向 127.0.0.1:10800

然后运行

./ruci_cmd -c local.lua

windows:

ruci_cmd.exe -c local.lua

测试成功!

下一步,学习 各个 MapConfig 的写法 或者直接开始学 各个 route 的写法

开发相关:参考 rucimp/src/modes/chain/config/mod.rs

开发相关:因为 代码实现方式不同,有些功能相近的 Map的Config是独立的

说明

在 Map说明的 首部标有 in, out 或 in/out 字样,表明可用于 InMapConfig 还是 OutMapConfig

配置的基本写法为 {type = "Name"},内部还可能有其它项。

没有任何示例的Map 意为着其写法仅为 {type = "Name"},如 { type = "Echo"} , {type = "Blackhole"}, {type = "Direct"}

标有 --optional 的项为可选项。

_addr 的写法

一些项,如 bind_addr,dial_addr, listen_addr, fixed_target_addr 等,其写法都是相同的。如下每一行都是合法的示例

fake.com:80
[::1]:30800
unix://file1
udp://127.0.0.1:20800
tcp://0.0.0.0:80
ip://10.0.0.1:24#utun321

{scheme}://没给出时,默认使用 tcp. unix 表示 unix domain socket

方括号括起的表示 ipv6

ip 示例中的 24 表示 子网掩码的CIDR表示, utun321 为指定要创建的 tun网卡 名称

入口、出口 Map

Blackhole

out

Direct

out

type = "Direct"

可选项为 dns_client

{
    type = "Direct",
    dns_client = {
        --...
    }--optional
}

DnsClient

OptDirect

in

OptDirect 的出现是 为了给 Direct 添加 sockopt 选项。使用 tproxy 要用该Map

{
    type = "OptDirect",
    sockopt= {
        --...
    },
    more_num_of_files= false, -- 可选
    dns_client = {}, -- 可选
}

SockOpt

DnsClient

more_num_of_files 为 true时,在 linux 上,程序将自动调整 系统设置, 防止 出现 num_of_files 不够的问题 ( 在 tproxy 等情况下尤为严重)

BindDialer

in

BindDialer 是 一个 既可以 Bind 又可以 Dial 的配置

Bind 用于 udp 和 ip, dial 则用于 udp,tcp,uds(unix domain socket)

BindDialer 中所有项都是可选的,但 bind_addr 或 dial_addr 中至少有一个要设置

对于 ip, bind_addr 须提供, 否则将报错

对于 tcp/udp, 如果 bind_addr 不提供, 将采用 随机端口

对于 uds, bind_addr 无意义

对于 ip, dial_addr 无意义

对于 tcp/uds, dial_addr 须提供,否则将报错
{
    type = "BindDialer",

    bind_addr = "",
    dial_addr = "",

    dns_client= {..} --optional

    in_auto_route= {..},  --optional

    out_auto_route = {..},  --optional

    ext= {..}, --optional
}

DnsClient

Ext

in_auto_route 用于 tun

 in_auto_route = {
    tun_dev_name = "utun321",
    tun_gateway = "10.0.0.1",
    router_ip = "192.168.0.1",
    original_dev_name = "enp0s1", -- windows/macos 可不填 original_dev_name, linux 要填 original_dev_name
    --direct_list = { "192.168.0.204" }, -- 服务端的ip要直连
    dns_list = { "114.114.114.114" }
}

out_auto_route 只用于 ip转发 (从本地的tun 转发到 服务器上的 tun)

out_auto_route = {
    tun_dev_name = "utun321",
    original_dev_name = "enp0s1", --wlp3s0
    router_ip = "192.168.0.1",
}

ip 拨号是建立一个虚拟网卡,一般为 tun. 这个一般可以用于配合 tcp/ip stack (smoltcp/lwip) 进行全局路由使用,详情 见 local.lua 中的对应示例,以及 这里

OptDialer

in

{
    type = "OptDialer",
    dial_addr= "",
    sockopt= {}, --optional
    dns_client = {}, --optional
}

DnsClient

SockOpt

Listener

in

{
    type = "Listener",
    listen_addr ="",
    ext={},--optional
}

Ext

TcpOptListener

in

{
    type = "TcpOptListener",
    listen_addr ="",
    sockopt={},
    ext={},--optional
}

SockOpt

Ext

Stdio

in/out

{
    type = "Stdio",
    write_mode = "Bytes", --optional
    ext={},--optional
}

默认的 write_mode 为 UTF8, 可以用 Bytes 模式来观察16进制数据

Ext

Fileio

in/out

{
    type = "Fileio",
    i="",
    o="",
    sleep_interval=1, --optional, 正整数
    bytes_per_turn=100, --optional, 正整数
    ext={}, --optional
}

i 表示 输入文件名,o表示输出文件名。

i为自己预先定义好的文件,o则是一个给出的文件名,其内容只有通讯后才知道。

sleep_interval 和 bytes_per_turn 两项配置用于设置发送信息的速率。

Ext

Tproxy

in

linux only

tproxy 的配置要复杂一些,要分 tcp 和 udp 两部分配置,自动路由的配置则是在 TproxyTcpResolver 中设置的。

local tproxy_tcp_listen = {
    type = "TcpOptListener",
    listen_addr = "0.0.0.0:12345",
    sockopt = {
        tproxy = true,
    }
}

local tproxy_listen_tcp_chain = {
    tproxy_tcp_listen, {
        type = "TproxyTcpResolver",
        port = 12345,
        --auto_route_tcp = true, -- only set route for tcp
        auto_route = true,         -- auto_route will set route for both tcp and udp at the appointed port

        route_ipv6 = true,         -- 如果为true, 则  也会 对 ipv6 网段执行 自动路由

        proxy_local_udp_53 = true, -- 如果为true, 则 udp 53 端口不会直连, 而是会流经 tproxy

        -- local_net4 = "192.168.0.0/16" -- 直连 ipv4 局域网段 不给出时, 默认即为 192.168.0.0/16
        
    }
}


local tproxy_udp_listen = {
    type = "TproxyUdpListener",
    listen_addr = "udp://0.0.0.0:12345",
    sockopt = {
        tproxy = true,
    }
    
}

local tproxy_listen_inbounds = { 
    listen_tproxy_tcp = tproxy_listen_tcp_chain,
    listen_tproxy_udp = { tproxy_udp_listen },
}

TproxyUdpListener

{
    type = "TproxyUdpListener",
    listen_addr="",
    sockopt={},
    ext={}, --optional
}

SockOpt

Ext

TproxyTcpResolver

rucimp/src/map/tproxy/route/mod.rs

{
    type = "TproxyTcpResolver",
     -- tproxy 监听的端口, 默认为 12345
    port=12345, --  正整数
    route_ipv6= false,
    proxy_local_udp_53=false,

    --局域网段, 默认为 192.168.0.0/16
    local_net4 = "192.168.0.0/16",
    auto_route = true,
    auto_route_tcp=false,
}

所有项都是可选的

auto_route 为 true 时, 若 auto_route_tcp 也为 true, 则 自动路由过程 只会为 tcp 设置路由, udp 将不被路由到tproxy中.

StackLwip

in

网络协议 Map

in/out

简单代理协议 Socks5,Http,Socks5Http

type = "Socks5Http"
type = "Socks5"
type = "Http"

可选用户密码组合, 内容均为可选

{
    type = "Socks5", -- Http
    userpass: "username1 password1",
    more: { "username2 password2", "username3 password3"},
}

字符串中 按whitespace 分割

Trojan

in:

{
    type = "Trojan", 
    password: "password1",
    more: { "password2", "password3"},
}

同上。password 以明文书写。

out:

 { type = "Trojan", password = "mypassword" }

TLS

in/out

in:

{
    type = "TLS", 
    cert="c.crt",
    key="k.key",
    alpn = { "h2", "h3"},--optional
}

out:

{
    type = "TLS", 
    host="www.myhost.com",
    insecure=false,
    alpn = { "h2", "h3"},--optional
}

如果任意一方的alpn 没给出, 则连接都通过;如果两方 alpn 都给出, 则只有匹配了才通过

NativeTLS

同上

NativeTLS 使用 系统自己的 tls栈,这样就没有 rust 特征了。

http2

服务端用 H2, 客户端用 H2Single 或 H2Mux,一般用 H2Mux 以使用 多路复用

grpc 也是在 http2 配置中设置

H2

目前 h2 的三种Map 的 Config 格式 是一样的

in

{
    type = "H2", 
    is_grpc=false,--optional
    http_config={},--optional
}

HttpCommonConfig

H2Single

out

{
    type = "H2Single", 
    is_grpc=false,--optional
    http_config={},--optional
},

HttpCommonConfig

H2Mux

out

{
    type = "H2Mux", 
    is_grpc=false,--optional
    http_config={},--optional
},

HttpCommonConfig

WebSocket

in/out

in:

{
    type = "WebSocket", 
    http_config = {
       --...
    } --optional
}

HttpCommonConfig

out:

{
    --... optional
}

HttpCommonConfig

Quic

in/out

in:

quic 的 监听端 是直接接管 udp 层的, listen_addr 在这里指定, 而不额外用 Listener

{
    type = "Quic", 
    key="",
    cert="",
    listen_addr="",
    alpn = { "h2", "h3"},
}

out:

 {
    type = "Quic", 
    server_addr="",
    server_name="www.mytest.com",
    cert="",--optional
    alpn = { "h2", "h3"}, --要明确指定 alpn
    insecure=true,--optional
}

须给出 server_name (域名), 且 若 insecure 为 false, 须为 证书中所写的 CN 或 Subject Alternative Name; ruci 提供的 test2.crt中的 Subject Alternative Name 为 www.mytest.com 和 localhost,

cert:可给出 服务端的 证书, 这样就算 insecure = false 也通过验证 证书须为 真证书, 或真fullchain 证书, 或自签的根证书

SPE1: Steganography Protocol Exmaple1

隐写示例协议1

{ 
    type = "SPE1", 
    qa = { { "q1", "a1" }, { "q2", "a2" } } 
}

qa 中要为 2的偶数次幂个 问答对,问答的内容任意填。但是内容越真实,隐写效果越好。

如果不给出qa,则协议会使用自己生成的问答对。

 { type = "SPE1"}

Lua: lua自定义协议

{ type = "Lua", file_name = "lua_protocol_e1.lua", handshake_function = "Handshake2" }

lua自定义协议 的写法是高级用法,见 lua自定义协议

辅助 Map

Echo

in/out

Adder

in/out

{type = "Adder", value=3}

给 输出 的信息 每字节都加 给定的数值。比如 输入abc, value=1, 则输出为 bcd

Counter

in/out

HttpFilter

in

{
    type = "HttpFilter",
    --...
}

HttpCommonConfig

子块

DnsClient

dns_client = {
    dns_server_list = { { "127.0.0.1:20800", "udp" } }, -- 8.8.8.8:53
    ip_strategy = "Ipv4Only", --optional
    static_pairs = {
        ['www.baidu.com'] = "103.235.47.188"
    } --optional
}

Direct,OptDirect,BindDialer,OptDialer 都可加此块。

ip_strategy 的可能的值:

#![allow(unused)]
fn main() {
pub enum LookupIpStrategy {
    Ipv4Only,
    Ipv6Only,
    Ipv4AndIpv6,
    Ipv6thenIpv4,
    Ipv4thenIpv6,
}
}

不给出时,默认为 Ipv4thenIpv6

SockOpt

sockopt = {
    tproxy = true,
    so_mark = 255,
    bind_to_device = "en0",
}

三项都是可选的

tproxy 为 true时表示 开启 tproxy 功能

so_mark 从0 到 255.

bind_to_device 的一些可能的值:

-- enp0s1(linux 的一般情况) -- en0 (macos 的情况) -- WLAN( windows, 用wifi联网的情况)

Ext

Listener,TcpOptListener, BindDialer, Stdio, Fileio 都能如此配置 ext

ext = {
    fixed_target_addr = "udp://8.8.8.8:53",
    pre_defined_early_data = "abcde"
}

ext 是 extension 的缩写, 是指定一些额外配置, 内部的项都是可选的, ext 本身也是可选的

fixed_target_addr 一旦设置,意味着 该Map的实际的 代理目标 是指定的值。

常见用例是转发 udp 流量,比如为自己的 出口设置不同的 dns:

listen 一个 本地的 udp 端口 (a), 指定 ext.fixed_target_addr (b), 其为实际想要的dns服务器 然后将 这个listen 所在的链 route 到一个 与远程服务器连接 的 链1上。

之后在自己的 Direct 的 dns_client 中的 dns_server_list 中添加 a, 这样就可以 将所有direct 中用到的dns解析都实际 通过 链1 发送到 服务端,服务端 再将 dns信息发到 fixed_target_addr

这就是 一些代理中的 "dokodemo" 的逻辑.

httpCommonConfig

 {
    authority = "www.myhost.com",
    path = "/ruci_jiandan",

    method = "GET",--optional
    scheme = "https",--optional
    headers= { ["header1"] = "value1", ["header2"] = "value2", },--optional
}

接下来

现在再读 dev_res/local.lua 就会轻松很多了。

学点难的? Infinite

现在我们学习 Config 中几种 route 块的写法:


routes = {
    tag_route = {},
    fallback_route = {},
    clash_rules = "the_clash_rules.yaml",
    geosite = "mygeosite_file_name.mmdb",
    geosite_gfw = {}
}

所有 route 都会用到 chain 的 tag 所以我们要求每一条inbound 都要有一个 tag, 每一个 inbound 中的 chain 都要有至少一个 map

tag_route

    tag_route = { { "l1", "d1" }, { "l2", "d2" }, { "l3", "d2" } },

tag_route 是一个 字符串对 的列表。对中前者为 inbound 的 tag, 后者为 outbound 的 tag。

这是一种固定的 路由模式,只要来自 l1 的 都会被发到 d1.

fallback_route

首先学一下什么是 fallback.

在一个 inbound的 chain 中,有序地排列着多个 InMapConfig, 即它们代表着多个 Map, 分别记为 map1, map2. 假设 map1 通过了,但 map2 的协议 逻辑检查失败,即 map2 检查数据,发现和 map2 对应的 协议所定义的 特征不一致,那么此时 整个 chain 就此中断。

如果就这样,一般的情况就是 在 log 中记录一下此次 异常情况, 然后继续 监听 其它请求。

但是,有时,map2 错了,我们依然认为 它是一个有效的 map1, 想要 将它转发到一个新的 outbound 上,此时就用到了 fallback_route. 这整个行为就叫 fallback.

没错。这个就是 trojan 协议的 精髓。

    fallback_route = { { "listen1", "fallback_dial1" } }

fallback_route 是一个 字符串对 的列表。上面示例就是表示 inbound chain "listen1" 里失败的地方将被转发到 outbound chain "fallback_dial1" 中。listen1 和 fallback_dial1 是它们的 tag.

clash_rules

0.0.8开始,ruci 支持了clash 的分流规则的使用。clash 是一个知名的app, 它的规则被用得很多

一般直接写为 clash_rules = "myfile.yaml"

然后在 myfile.yaml 中,按 clash 配置文件中的 rules 项进行书写,如写成:

rules:
  - DOMAIN-SUFFIX,ip6-localhost,Direct
  - DOMAIN-SUFFIX,ip6-loopback,Direct
  - DOMAIN-SUFFIX,lan,Direct
  - DOMAIN-SUFFIX,local,Direct
  - DOMAIN-SUFFIX,localhost,Direct
  - DOMAIN-KEYWORD,baidu,Reject

就行了

ruci 支持所有 clash 中定义的 规则,而且有算法加速支持,匹配得很快!

您还可以直接把 yaml 的内容传入 clash_rules 项,但是不太建议这么做:

clash_rules = "rules:\n  - DOMAIN-SUFFIX,ip6-localhost,Direct"

好处就是可以一个配置文件全搞定

geosite

配置 geosite 的 mmdb 是用于 clash_rules的。也就是说,配置了 geosite后,clash_rules中的 GEOSITE 规则就生效了

可以运行 ruci-cmd utils 中的 对应命令来下载 geosite 文件

geosite_gfw

https://github.com/e1732a364fed/geosite-gfw

geosite_gfw = {
    api_url = "http://127.0.0.1:5134/check",
    proxy = "127.0.0.1:10800",
    only_proxy = false,
    ok_ban_out_tag = { "Direct", "Reject"}
}

geosite_gfw 是一个 人工智能 gfw项目,它用过机器学习训练出的模型来判断一个 域名 倒底是会被墙还是 可以直连

主要用于 local 本地端进行分流。

proxy 选项若给出,则 geosite_gfw 会在访问不到目标地址时,使用 proxy 再访问一次。 而 若 only_proxy = true, 则 geosite_gfw 第一次方问目标地址就会使用 该 proxy.

注意,如果您配置 geosite_gfw 的 proxy 又指向回 我们的 ruci 的监听端口的话,要确保按上文的 tag_route 把该端口的监听强制导向某 outbound, 避免再次进竹 geosite_gfw 环节 造成 回环。

目前的geosite_gfw 的运行方式:

git clone https://github.com/e1732a364fed/geosite-gfw/
cd geosite-gfw
pip3 install transformers numpy scikit-learn flask requests
pip3 install torch

curl -LO "https://huggingface.co/e1732a364fed/geosite-gfw/resolve/main/bert_geosite_by_body.zip?download=true"
curl -LO "https://huggingface.co/e1732a364fed/geosite-gfw/resolve/main/bert_geosite_by_head.zip?download=true"

tar -xf bert_geosite_by_body.zip
tar -xf bert_geosite_by_head.zip

python3 classify.py --mode serve_api --port 5134

之后在 ruci 的 routes 中使用 geosite_gfw 就能生效啦。

接下来

学点难的? Infinite

介绍

infinite 模式下,lua配置不使用 Config 变量,而使用 Infinite 变量

基本格式:

Infinite = {
    inbounds = {
        {
            tag = "listen1",
            generator = function(cid, state_index, data)

            end
        }
    },
    outbounds = {
        {
            tag = "dial1",
            generator = function(cid, state_index, data)

            end
        }
    }
}

回顾 Config 入门 ,对比下我们发现, Infinite 的 基本结构 和 Config 类似,都有 inbounds 和 outbounds 列表。

不过,列表中的元素中的项不再是 tag 和 chain , 而是 tag 和 函数 generator!

generator 将被重复调用,以生成 chain. 区别就在于,通过函数,我们每次生成的 chain 的内容是可以不同的,也就是说,infinite 实现了 动态链 机制!

先看个能用的示例:

下面这个演示 与Config 入门 中的示例 是等价的:

local direct = { type = "Direct" }

Infinite = {
    inbounds = { {
        tag = "listen1",
        generator = function(cid, state_index, _data)
            if state_index == -1 then
                return 0, {
                    stream_generator = {
                        type = "Listener", listen_addr = "0.0.0.0:10800"
                    },
                    new_thread_fn = function(cid, state_index, _data)
                        local new_cid, newi, new_data = coroutine.yield(1, {
                            type = "Socks5"
                        })
                        return -1, {}
                    end
                }
            end
        end
    } },
    outbounds = { {
        tag = "dial1",
        generator = function(cid, state_index, _data)
            if state_index == -1 then
                return 0, direct
            else
                return -1, {}
            end
        end
    } }
}

首先是要了解,generator 的 返回值有两个,第一个值是 index, 负数表示链结束。 第二个值是 当前链节点 所生成的 Map。

如果是 Listener ,情况则复杂一些,要使用一个 {stream_generator, new_thread_fn }

stream_generator 是一个 正常的 InMapConfig, 而 new_thread_fn 则是一个函数, 它在 stream_generator 每监听到一个新请求时被调用, 因此它内部用到了 lua 的用法 coroutine.yield, 这个就照抄就行。

总之 coroutine.yield 参数中 我们又 返回 一个数和一个 InMapConfig, 而 coroutine.yield 函数的 返回的时机, 就是我们再次定义下一个 InMapConfig 的时机。 如果链中还有 Map, 我们可以继续调用 coroutine.yield, 如果已经到了 链尾,我们则 直接 return -1, {} 就行。

不难,习惯就好。

接下来

lua自定义协议

lua用户自定义协议

ruci 中提供 lua用户自定义协议方式来 大大提高使用的灵活性。

方法是,用户首先在配置文件中指定 链中的一个Map为 Lua:

local config_21_lua_example1 = {
    inbounds = {
        listen1 = listen_socks5http,
    },
    outbounds = {
        dial1 = { dial, tlsout, trojan_out, { type = "Lua" ,file_name = "lua_protocol_e1.lua", handshake_function = "Handshake2"} }
    }
}

里面指定了 具体实现协议的 lua_protocol_e1.lua 文件 以及里面的 Handshake2函数 作为 协议的握手函数

lua_protocol_e1.lua:

function Handshake(cid, behavior, addr, firstbuff, conn)
    return conn, addr, firstbuff
end

cid 为字符串, behavior 为1 表示 client, 为 2 表示 server, addr 表示 代理要连接的目标地址。 firstbuff 为数据的首包。 conn 为链中上一个Map 的连接。

上面函数的具体实现就是将内容按上一个Map的原样返回

如果要返回一个自定义的新Map,则可以返回一个 包含读、写、关、冲 四个函数的table

function Handshake(cid, behavior, addr, firstbuff, conn)
    Cid=cid
    TheConn = conn
    Behavior = behavior
     return { Read, Write, Close, Flush }, addr, firstbuff
end

上面函数把 cid, behavior, conn 保存到了全局变量中,这样就可以在 四函数中访问这些值了

其中, conn有 poll_read, poll_write, poll_close, poll_flush 四个方法。

它们都接收一个 cx 作为参数。这里不用管cx是什么,只要记住原样传递即可。

Read

Read函数除了 cx外还有一个 buf 变量。 buf 变量可以作为 conn:poll_read的参数,也可以在 Wrap_read_buf(buf) 后变为一个 lua可以访问的变量类型,其有如下方法:

put_slice
filled_len
filled_content

这种类型的变量也可以用 local b = Create_read_buf(1024) 创建。

而如果要将 lua 可以访问的buf作为 poll_read的参数,则要加一个 get_ptr:

conn:poll_read(cx, b:get_ptr())

而使用完 lua的buf后,要调用 b:drop() 来释放内存。

示例:

function Read(cx, buf)
    -- print("lua read2 called")
    local result = TheConn:poll_read(cx, buf)

    if result:is_pending() then
        return -1
    elseif result:is_err() then
        return -2
    else
        local rb = Wrap_read_buf(buf) -- 用 Wrap_read_buf 将 buf 转为 lua 可调用的 版本 (未转时仅能作 poll_read 的参数)

        local n = rb:filled_len()
        print("lua read2 got", n, Cid)

        if n > 10 then
            n = 10
        end

        local s = rb:filled_content(n)
        print("read head ", inspect(s:sub(1, 1))) --获取第一个字节的值 并打印出来

        return 0
    end
end

Write

function Write(cx, str)
    -- print("lua write2 called", str:len())
    local result = TheConn:poll_write(cx, str)

    if result:is_pending() then
        return -1
    elseif result:is_err() then
        return -2
    else
        local n = result:get_n()
        -- print("lua write2 finish", n)

        return n
    end
end

Close

function Close(cx)
    -- print("close2 called")
    local result = TheConn:poll_close(cx)

    if result:is_pending() then
        return -1
    elseif result:is_err() then
        return -2
    else
        return 0
    end
end

Flush

function Flush(cx)
    -- print("flush2 called")

    local result = TheConn:poll_flush(cx)

    if result:is_pending() then
        return -1
    elseif result:is_err() then
        return -2
    else
        return 0
    end
end

其它

ruci还在lua中注册了 Debug_print,Info_print,Warn_print 函数,可以用于向日志打印自定义输出(以debug,info,warn 级别)

还有 Load_file 函数,可以用它加载 tar 中的文件。(只在 静态链中有效)

json 格式与 lua 格式完全类似,只是以 json 语法重新写成。请查看 resource/json_examples 中的示例

json 格式只能配置静态链。

json 格式可以由 ruci-webui 导入 导出。