Files
op-packages/luci-app-xkcptun/htdocs/luci-static/resources/view/xkcptun.js
T

496 lines
16 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require ui';
'require form';
'require rpc';
const callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() {
return L.resolveDefault(callServiceList('xkcptun'), {}).then(function (res) {
let status = {
client: null,
server: null
};
try {
let instances = res['xkcptun']['instances'];
if (instances['client'] && instances['client']['running']) {
status.client = instances['client']['pid'];
}
if (instances['server'] && instances['server']['running']) {
status.server = instances['server']['pid'];
}
} catch (e) {}
return status;
});
}
function renderStatus(status) {
let clientHTML = '';
let serverHTML = '';
let spanGreen = '<em><span style="color:green"><strong>%s (PID %d)</strong></span></em>';
let spanRed = '<em><span style="color:grey"><strong>%s</strong></span></em>';
if (status.client) {
clientHTML = String.format(spanGreen, _('RUNNING'), status.client);
} else {
clientHTML = String.format(spanRed, _('NOT RUNNING'));
}
if (status.server) {
serverHTML = String.format(spanGreen, _('RUNNING'), status.server);
} else {
serverHTML = String.format(spanRed, _('NOT RUNNING'));
}
return E('div', { class: 'cbi-map' },
E('fieldset', { class: 'cbi-section' }, [
E('p', {}, [
E('strong', {}, _('Client Daemon') + ': '),
E('span', {}, [clientHTML]),
E('span', { style: 'margin-left: 20px;' }, [
E('strong', {}, _('Server Daemon') + ': '),
E('span', {}, [serverHTML])
])
])
])
);
}
const modeProfiles = [
['fast3', 'fast3 (nodelay=1, interval=10ms, resend=2, nc=1)'],
['fast2', 'fast2 (nodelay=1, interval=10ms, resend=2, nc=0)'],
['fast', 'fast (nodelay=0, interval=20ms, resend=2, nc=0)'],
['normal', 'normal (nodelay=0, interval=30ms, resend=2, nc=0)'],
['manual', _('manual (custom parameters)')]
];
function addCommonAdvancedOptions(s, tabName, isServer) {
let tab = tabName || 'advanced';
let o;
o = s.taboption(tab, form.Value, 'mtu', _('MTU'),
_('Maximum Transmission Unit for UDP packets. Default is 1350.'));
o.datatype = 'uinteger';
o.placeholder = '1350';
o.optional = true;
o = s.taboption(tab, form.Value, 'sndwnd', _('Send Window (sndwnd)'),
_('Send window size (number of packets).'));
o.datatype = 'uinteger';
o.placeholder = isServer ? '4096' : '1024';
o.optional = true;
o = s.taboption(tab, form.Value, 'rcvwnd', _('Receive Window (rcvwnd)'),
_('Receive window size (number of packets).'));
o.datatype = 'uinteger';
o.placeholder = isServer ? '1024' : '4096';
o.optional = true;
o = s.taboption(tab, form.Flag, 'fec', _('Enable FEC (fec)'),
_('Frame all UDP datagrams with Reed-Solomon FEC header. Default is enabled.'));
o.default = '1';
o.rmempty = false;
o = s.taboption(tab, form.Value, 'datashard', _('Data Shards (FEC)'),
_('Reed-Solomon erasure coding data shards. Default is 10.'));
o.datatype = 'uinteger';
o.placeholder = '10';
o.optional = true;
o.depends('fec', '1');
o = s.taboption(tab, form.Value, 'parityshard', _('Parity Shards (FEC)'),
_('Reed-Solomon erasure coding parity shards. Default is 3.'));
o.datatype = 'uinteger';
o.placeholder = '3';
o.optional = true;
o.depends('fec', '1');
o = s.taboption(tab, form.Value, 'dscp', _('DSCP'),
_('DSCP IP TOS value (0-63). Default is 0.'));
o.datatype = 'range(0,63)';
o.placeholder = '0';
o.optional = true;
o = s.taboption(tab, form.Flag, 'lossctrl', _('Loss-Driven AIMD (lossctrl)'),
_('Enable loss-driven AIMD send window adaptation. Default is disabled.'));
o.default = '0';
o.rmempty = false;
o = s.taboption(tab, form.Value, 'pacing', _('Send Pacing (pacing)'),
_('Max KCP segments per flush tick to smooth send bursts (0 = off). Default is 0.'));
o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true;
o = s.taboption(tab, form.ListValue, 'nodelay', _('No Delay (nodelay)'),
_('Enable KCP nodelay mode.'));
o.value('0', '0 (' + _('Disable') + ')');
o.value('1', '1 (' + _('Enable') + ')');
o.default = '1';
o.optional = true;
o = s.taboption(tab, form.Value, 'interval', _('Internal Interval'),
_('Internal clock interval in milliseconds (10-5000). Default is 10ms.'));
o.datatype = 'range(10,5000)';
o.placeholder = '10';
o.optional = true;
o = s.taboption(tab, form.Value, 'resend', _('Fast Resend'),
_('Fast resend count. 0 = off, 2 = fast resend. Default is 2.'));
o.datatype = 'uinteger';
o.placeholder = '2';
o.optional = true;
o = s.taboption(tab, form.Flag, 'nc', _('No Congestion Control (nc)'),
_('Disable congestion window flow control. Default is 1 (disabled).'));
o.default = '1';
o.rmempty = false;
o = s.taboption(tab, form.Value, 'sockbuf', _('Socket Buffer Size'),
_('UDP socket receive/send buffer size in bytes. Default is 16777216 (16MB).'));
o.datatype = 'uinteger';
o.placeholder = '16777216';
o.optional = true;
o = s.taboption(tab, form.Value, 'keepalive', _('Keepalive Interval'),
_('KCP keepalive ping interval in seconds. Default is 10s.'));
o.datatype = 'uinteger';
o.placeholder = '10';
o.optional = true;
o = s.taboption(tab, form.Value, 'conntimeout', _('Connection Timeout'),
_('Idle connection timeout in seconds (0 = disable timeout sweep). Default is 60s.'));
o.datatype = 'uinteger';
o.placeholder = '60';
o.optional = true;
}
return view.extend({
render: function() {
let m, s, o;
m = new form.Map('xkcptun', _('xkcptun'),
_('xkcptun is a high-performance C implementation of kcptun with dynamic destination multi-tunnel unified daemon architecture.'));
// Status Section
s = m.section(form.NamedSection, '_status');
s.anonymous = true;
s.render = function (section_id) {
let container = E('div', { id: 'service_status' }, _('Collecting status ...'));
L.Poll.add(function () {
return L.resolveDefault(getServiceStatus(), {}).then(function(status) {
let view = document.getElementById('service_status');
if (view) {
dom.content(view, renderStatus(status));
}
});
});
return container;
};
// ===== Client Global Default Settings =====
s = m.section(form.NamedSection, 'client', 'global', _('Client Global Settings'),
_('Common connection parameters and KCP profile inherited by all client tunnels.'));
s.anonymous = false;
s.addremove = false;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
o = s.taboption('general', form.Value, 'remote_addr', _('Default Server Address'),
_('Remote xkcptun server IP address or domain name.'));
o.datatype = 'host';
o.placeholder = '1.2.3.4';
o.rmempty = false;
o = s.taboption('general', form.Value, 'remote_port', _('Default Server Port'),
_('Remote xkcptun server UDP listen port.'));
o.datatype = 'port';
o.placeholder = '9089';
o.rmempty = false;
o = s.taboption('general', form.Value, 'key', _('Pre-Shared Key'),
_('Pre-shared key for HMAC-SHA256 dynamic target authentication. Must match the server.'));
o.password = true;
o.placeholder = "it's a secret";
o.optional = true;
o = s.taboption('general', form.ListValue, 'mode', _('Mode Profile'),
_('KCP performance tuning profile.'));
for (let i = 0; i < modeProfiles.length; i++) {
o.value(modeProfiles[i][0], modeProfiles[i][1]);
}
o.default = 'fast3';
addCommonAdvancedOptions(s, 'advanced', false);
// ===== Client Tunnels =====
s = m.section(form.GridSection, 'client', _('Client Tunnels'),
_('Configure local client listening ports and dynamic backend target ports. All tunnels share the single unified client daemon.'));
s.anonymous = false;
s.addremove = true;
s.sortable = true;
s.addbtntitle = _('Add Client Tunnel');
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Overrides'));
// Overview Columns in table
o = s.option(form.Flag, 'disabled', _('Enabled'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o.rmempty = false;
o.editable = true;
o = s.option(form.Value, 'name', _('Tunnel Name'));
o.modalonly = false;
o.placeholder = _('(Auto)');
o = s.option(form.ListValue, 'proto', _('Protocol'));
o.value('tcp', 'TCP');
o.value('udp', 'UDP');
o.default = 'tcp';
o.modalonly = false;
o = s.option(form.Value, 'local_port', _('Local Port'));
o.modalonly = false;
o.placeholder = '9088';
o = s.option(form.Value, 'target_port', _('Target Port'));
o.modalonly = false;
o.placeholder = '22';
o = s.option(form.Value, 'target_addr', _('Target Host'));
o.modalonly = false;
o.placeholder = '127.0.0.1';
// Modal / Tab: General Settings
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'name', _('Tunnel Name / Alias'),
_('Optional descriptive name for this tunnel (e.g. ssh_tunnel, web_proxy).'));
o.placeholder = 'my_tunnel';
o.optional = true;
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'proto', _('Tunnel Protocol'),
_('Protocol to tunnel: TCP (standard KCP stream) or UDP (direct low-latency datagram forwarding, e.g. for DNS).'));
o.value('tcp', 'TCP (TCP-over-KCP)');
o.value('udp', 'UDP (Direct UDP Datagram)');
o.default = 'tcp';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_interface', _('Local Interface'),
_('Network interface to bind (default: br-lan).'));
o.placeholder = 'br-lan';
o.default = 'br-lan';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_port', _('Local Listen Port'),
_('Local port to listen for incoming application traffic (e.g. 9088).'));
o.datatype = 'port';
o.placeholder = '9088';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'target_port', _('Dynamic Target Port'),
_('Target port on the server to forward traffic to (e.g. 22, 53, 80, 443).'));
o.datatype = 'port';
o.placeholder = '22';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'target_addr', _('Dynamic Target Host'),
_('Target host on the server to connect/forward to (default: 127.0.0.1).'));
o.datatype = 'host';
o.placeholder = '127.0.0.1';
o.default = '127.0.0.1';
o.optional = true;
o.modalonly = true;
// Modal / Tab: Advanced Overrides (Optional)
o = s.taboption('advanced', form.Value, 'remote_addr', _('Override Server Address'),
_('Override remote server address for this specific tunnel (defaults to global).'));
o.datatype = 'host';
o.placeholder = _('(Inherit from Global)');
o.optional = true;
o = s.taboption('advanced', form.Value, 'remote_port', _('Override Server Port'),
_('Override remote server port for this specific tunnel (defaults to global).'));
o.datatype = 'port';
o.placeholder = _('(Inherit from Global)');
o.optional = true;
o = s.taboption('advanced', form.Value, 'key', _('Override Pre-Shared Key'),
_('Override pre-shared authentication key for this specific tunnel (defaults to global).'));
o.password = true;
o.placeholder = _('(Inherit from Global)');
o.optional = true;
// ===== Server Global Settings (Dynamic Gateway) =====
s = m.section(form.NamedSection, 'server', 'global', _('Server Global Settings (Dynamic Gateway)'),
_('Unified xkcptun server dynamic gateway. The server listens on a single UDP port and dynamically routes each client tunnel to its requested backend target.'));
s.anonymous = false;
s.addremove = false;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
o = s.taboption('general', form.Flag, 'enabled', _('Enable Server Daemon'),
_('Enable unified xkcptun server dynamic gateway daemon.'));
o.enabled = '1';
o.disabled = '0';
o.default = '0';
o.rmempty = false;
o = s.taboption('general', form.Value, 'local_interface', _('Listen Interface'),
_('Network interface to bind (e.g. eth0, br-lan, wan).'));
o.placeholder = 'eth0';
o.default = 'eth0';
o.rmempty = false;
o = s.taboption('general', form.Value, 'local_port', _('Listen Port (UDP)'),
_('UDP port to listen for incoming client KCP connections (default: 9089).'));
o.datatype = 'port';
o.placeholder = '9089';
o.default = '9089';
o.rmempty = false;
o = s.taboption('general', form.Value, 'key', _('Pre-Shared Key'),
_('Pre-shared key for dynamic gateway authentication. Clients must present a valid HMAC token generated with this key.'));
o.password = true;
o.placeholder = "it's a secret";
o.optional = true;
o = s.taboption('general', form.ListValue, 'mode', _('Mode Profile'),
_('KCP performance tuning profile.'));
for (let i = 0; i < modeProfiles.length; i++) {
o.value(modeProfiles[i][0], modeProfiles[i][1]);
}
o.default = 'fast3';
addCommonAdvancedOptions(s, 'advanced', true);
// ===== Server Fallback Tunnels (Optional) =====
s = m.section(form.GridSection, 'server', _('Server Fallback Tunnels (Optional)'),
_('Optional fallback static target rules for legacy clients that do not send dynamic destination headers. Modern dynamic clients do not require any server tunnels configured here.'));
s.anonymous = false;
s.addremove = true;
s.sortable = true;
s.addbtntitle = _('Add Fallback Tunnel');
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
// Overview Columns in table
o = s.option(form.Flag, 'disabled', _('Enabled'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o.rmempty = false;
o.editable = true;
o = s.option(form.Value, 'name', _('Tunnel Name'));
o.modalonly = false;
o.placeholder = _('(Auto)');
o = s.option(form.Value, 'local_interface', _('Interface'));
o.modalonly = false;
o.placeholder = 'eth0';
o = s.option(form.Value, 'local_port', _('Listen Port'));
o.modalonly = false;
o.placeholder = '9089';
o = s.option(form.Value, 'remote_addr', _('Fallback Target Host'));
o.modalonly = false;
o.placeholder = '127.0.0.1';
o = s.option(form.Value, 'remote_port', _('Fallback Target Port'));
o.modalonly = false;
o.placeholder = '443';
o = s.option(form.ListValue, 'mode', _('Mode'));
o.modalonly = false;
for (let i = 0; i < modeProfiles.length; i++) {
o.value(modeProfiles[i][0], modeProfiles[i][0]);
}
o = s.option(form.Flag, 'fec', _('FEC'));
o.modalonly = false;
o.default = '0';
o.editable = true;
// Modal / Tab: General Settings
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'name', _('Tunnel Name / Alias'),
_('Optional descriptive name for this tunnel (e.g. main_srv).'));
o.placeholder = 'srv_tunnel';
o.optional = true;
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_interface', _('Local Interface'),
_('Network interface to bind (e.g. eth0, br-lan, wan).'));
o.placeholder = 'eth0';
o.default = 'eth0';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_port', _('Local Listen Port (UDP)'),
_('UDP port to listen for incoming KCP connections from clients.'));
o.datatype = 'port';
o.placeholder = '9089';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'remote_addr', _('Fallback Target Host'),
_('Default target host if client did not specify a dynamic destination (default: 127.0.0.1).'));
o.datatype = 'host';
o.default = '127.0.0.1';
o.placeholder = '127.0.0.1';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'remote_port', _('Fallback Target Port'),
_('Default target port if client did not specify a dynamic destination (e.g. 443, 22).'));
o.datatype = 'port';
o.placeholder = '443';
o.rmempty = false;
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'mode', _('Mode Profile'),
_('KCP performance tuning profile.'));
for (let i = 0; i < modeProfiles.length; i++) {
o.value(modeProfiles[i][0], modeProfiles[i][1]);
}
o.default = 'fast3';
o.modalonly = true;
addCommonAdvancedOptions(s, 'advanced', true);
return m.render();
}
});