Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions applications/luci-app-shadowsocks-rust/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include $(TOPDIR)/rules.mk

LUCI_TITLE:=LuCI support for Shadowsocks Rust
LUCI_DEPENDS:=+luci-base +shadowsocks-rust-config

PKG_MAINTAINER:=Jove Yu <yushijun110@gmail.com>
PKG_LICENSE:=GPL-3.0

include ../../luci.mk

# call BuildPackage - OpenWrt buildroot signature
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2026 Jove Yu <yushijun110@gmail.com>
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/

'use strict';
'require view';
'require poll';
'require form';
'require uci';
'require fs';
'require network';
'require rpc';

var conf = 'shadowsocks-rust';

return view.extend({
load: function() {
return uci.load(conf);
},
render: function(stats) {
var m, s, o;

m = new form.Map(conf,
_('Locals'),
_('Configure local service instances for shadowsocks-rust. \
Instances define how the client services (SOCKS5, HTTP, DNS, Tunnel, TUN, etc.) \
connect through remote servers. To enable an instance it \
is required to enable both the instance itself and the remote \
server it refers to.'));

s = m.section(form.GridSection, 'local');
s.addremove = true;

o = s.option(form.Flag, 'disabled', _('Disable'));
o.editable = true;

o = s.option(form.ListValue, 'server', _('Server'));
o.datatype = 'string';
uci.sections(conf, 'server').forEach(function(server) {
var label = server['.name'] + ' (' + server.server + ':' + server.server_port + ')';
o.value(server['.name'], label);
});

o = s.option(form.ListValue, 'protocol', _('Protocol'));
o.datatype = 'string';
o.value('socks', 'SOCKS5');
o.value('http', 'HTTP');
o.value('redir', 'REDIR');
o.value('tunnel', 'TUNNEL');
o.value('dns', 'DNS');
o.value('tun', 'TUN');
o.default = 'socks';

o = s.option(form.Value, 'local_address', _('Listen Address'));
o.datatype = 'ipaddr';
o.placeholder = '::';
o.default = '::';

o = s.option(form.Value, 'local_port', _('Listen Port'));
o.datatype = 'port';
o.placeholder = '1080';

o = s.option(form.ListValue, 'mode', _('Mode'));
o.datatype = 'string';
o.value('tcp_only', _('TCP Only'));
o.value('udp_only', _('UDP Only'));
o.value('tcp_and_udp', _('TCP and UDP'));
o.default = 'tcp_only';
o.modalonly = true;

// Tunnel specific options
o = s.option(form.Value, 'forward_address', _('Forward Address'));
o.datatype = 'host';
o.placeholder = '8.8.8.8';
o.depends('protocol', 'tunnel');
o.modalonly = true;

o = s.option(form.Value, 'forward_port', _('Forward Port'));
o.datatype = 'port';
o.placeholder = '53';
o.depends('protocol', 'tunnel');
o.modalonly = true;

// DNS specific options
o = s.option(form.Value, 'local_dns_address', _('Local DNS Address'));
o.datatype = 'host';
o.placeholder = '114.114.114.114';
o.depends('protocol', 'dns');
o.modalonly = true;

o = s.option(form.Value, 'local_dns_port', _('Local DNS Port'));
o.datatype = 'port';
o.placeholder = '53';
o.default = '53';
o.depends('protocol', 'dns');
o.modalonly = true;

o = s.option(form.Value, 'remote_dns_address', _('Remote DNS Address'));
o.datatype = 'host';
o.placeholder = '8.8.8.8';
o.depends('protocol', 'dns');
o.modalonly = true;

o = s.option(form.Value, 'remote_dns_port', _('Remote DNS Port'));
o.datatype = 'port';
o.placeholder = '53';
o.default = '53';
o.depends('protocol', 'dns');
o.modalonly = true;

o = s.option(form.Value, 'client_cache_size', _('DNS Cache Size'));
o.datatype = 'uinteger';
o.placeholder = '10';
o.default = '10';
o.depends('protocol', 'dns');
o.modalonly = true;

// TUN specific options
o = s.option(form.Value, 'tun_interface_name', _('TUN Interface Name'));
o.datatype = 'string';
o.placeholder = 'tun0';
o.default = 'tun0';
o.depends('protocol', 'tun');
o.modalonly = true;

o = s.option(form.Value, 'tun_interface_address', _('TUN Interface Address'));
o.datatype = 'cidr';
o.placeholder = '10.255.0.1/24';
o.depends('protocol', 'tun');
o.modalonly = true;

return m.render();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (C) 2026 Jove Yu <yushijun110@gmail.com>
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/

'use strict';
'require view';
'require form';
'require uci';

var conf = 'shadowsocks-rust';

function src_dst_option(s /*, ... */) {
var o = s.taboption.apply(s, L.varargs(arguments, 1));
o.datatype = 'or(ipaddr,cidr)';
}

return view.extend({
load: function() {
return uci.load(conf).then(function() {
if (!uci.get_first(conf, 'rules')) {
uci.set(conf, uci.add(conf, 'rules', 'rules'), 'disabled', '1');
}
});
},
render: function() {
var m, s, o;

m = new form.Map(conf, _('Rules'),
_('Configure traffic routing rules for transparent proxy. \
If enabled, packets will first have their source IP addresses checked \
against <em>Source IPs to Bypass</em>, <em>Source IPs to Forward</em>, \
<em>Source IPs to Check Destination</em> and if none matches \
<em>Source Default Policy</em> will give the default action to be taken. \
If the prior check results in action <em>Check Destination</em>, packets will \
continue to have their destination addresses checked.'));

s = m.section(form.NamedSection, 'rules', 'rules');
s.tab('general', _('General'));
s.tab('src', _('Source'));
s.tab('dst', _('Destination'));

s.taboption('general', form.Flag, 'disabled', _('Disable'));

o = s.taboption('general', form.ListValue, 'redir_tcp',
_('TCP Redirect'));
o.value('', _('None'));
uci.sections(conf, 'local', function(sdata) {
if (sdata.protocol === 'redir' && sdata['.name']) {
var port = sdata.local_port || 'N/A';
o.value(sdata['.name'], sdata['.name'] + ' (:' + port + ')');
}
});

o = s.taboption('general', form.ListValue, 'redir_udp',
_('UDP Redirect'));
o.value('', _('None'));
uci.sections(conf, 'local', function(sdata) {
if (sdata.protocol === 'redir' && sdata['.name']) {
var port = sdata.local_port || 'N/A';
o.value(sdata['.name'], sdata['.name'] + ' (:' + port + ')');
}
});

o = s.taboption('general', form.ListValue, 'local_default',
_('Local Default'),
_('Default action for locally generated TCP packets'));
o.value('bypass', _('Bypass'));
o.value('forward', _('Forward'));
o.value('checkdst', _('Check Destination'));
o.default = 'bypass';

o = s.taboption('general', form.Value, 'ifnames',
_('Interfaces'),
_('Only apply rules on packets from these network interfaces'));
o.placeholder = 'wan';

s.taboption('general', form.Value, 'nft_tcp_extra',
_('Extra TCP expression'),
_('Extra nftables expression for matching tcp traffics, e.g. "tcp dport { 80, 443 }"'));

s.taboption('general', form.Value, 'nft_udp_extra',
_('Extra UDP expression'),
_('Extra nftables expression for matching udp traffics, e.g. "udp dport { 53 }"'));

src_dst_option(s, 'src', form.DynamicList, 'src_ips_bypass',
_('Bypass'),
_('Bypass ss-redir for packets with source address in this list'));
src_dst_option(s, 'src', form.DynamicList, 'src_ips_forward',
_('Forward'),
_('Forward through ss-redir for packets with source address in this list'));
src_dst_option(s, 'src', form.DynamicList, 'src_ips_checkdst',
_('Check Destination'),
_('Continue to have destination address checked for packets with source address in this list'));

o = s.taboption('src', form.ListValue, 'src_default',
_('Default Policy'),
_('Default action for packets whose source address do not match any of the source ip/net list'));
o.value('bypass', _('Bypass'));
o.value('forward', _('Forward'));
o.value('checkdst', _('Check Destination'));
o.default = 'checkdst';

src_dst_option(s, 'dst', form.DynamicList, 'dst_ips_bypass',
_('Bypass'),
_('Bypass ss-redir for packets with destination address in this list'));
src_dst_option(s, 'dst', form.DynamicList, 'dst_ips_forward',
_('Forward'),
_('Forward through ss-redir for packets with destination address in this list'));

var dir = '/etc/shadowsocks-rust';
o = s.taboption('dst', form.FileUpload, 'dst_ips_bypass_file',
_('Bypass File'),
_('File containing IP/net for the purposes as with <em>Destination IPs to Bypass</em>'));
o.root_directory = dir;

o = s.taboption('dst', form.FileUpload, 'dst_ips_forward_file',
_('Forward File'),
_('File containing IP/net for the purposes as with <em>Destination IPs to Forward</em>'));
o.root_directory = dir;

o = s.taboption('dst', form.ListValue, 'dst_default',
_('Default Policy'),
_('Default action for packets whose destination address do not match any of the destination ip list'));
o.value('bypass', _('Bypass'));
o.value('forward', _('Forward'));
o.default = 'bypass';

return m.render();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2026 Jove Yu <yushijun110@gmail.com>
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/

'use strict';
'require view';
'require form';
'require uci';

var conf = 'shadowsocks-rust';

return view.extend({
load: function() {
return uci.load(conf);
},

render: function() {
var encryptionMethods = [
'none',
'plain',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305'
];

var m, s, o;

m = new form.Map(conf, _('Servers'),
_('Definition of remote shadowsocks servers. \
Disable any of them will also disable instances referring to it.'));

s = m.section(form.GridSection, 'server');
s.addremove = true;

o = s.option(form.Flag, 'disabled', _('Disable'));
o.editable = true;

o = s.option(form.Value, 'server', _('Server Address'));
o.datatype = 'host';

o = s.option(form.Value, 'server_port', _('Server Port'));
o.datatype = 'port';

o = s.option(form.ListValue, 'method', _('Encryption Method'));
o.datatype = 'string';
o.default = 'chacha20-ietf-poly1305';
encryptionMethods.forEach(function(method) {
o.value(method, method);
});

o = s.option(form.Value, 'password', _('Password'));
o.password = true;
o.datatype = 'string';
o.modalonly = true;

o = s.option(form.Value, 'plugin', _('Plugin'));
o.datatype = 'string';
o.modalonly = true;

o = s.option(form.Value, 'plugin_opts', _('Plugin Options'));
o.datatype = 'string';
o.modalonly = true;

o = s.option(form.Value, 'timeout', _('Timeout'));
o.datatype = 'uinteger';
o.placeholder = '60';
o.default = '60';
o.modalonly = true;

return m.render();
}
});
Loading
Loading