mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 18:44:03 +08:00
🌴 Sync 2026-03-05 23:51:42
This commit is contained in:
1 parent
97c049ed0a
commit
0acff03804
14253 files changed
+3893239
No files matched your search
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (C) 2022 Jaymin Patel <jem.patel@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-or-later
|
||||
PKG_MAINTAINER:=Jaymin Patel <jem.patel@gmail.com>
|
||||
|
||||
LUCI_TITLE=Luci Application for IPSec VPN (Libreswan)
|
||||
LUCI_DEPENDS:=+luci-base +libreswan
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require network';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
network.getDevices(),
|
||||
]);
|
||||
},
|
||||
|
||||
render([netDevs]) {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('libreswan', _('IPSec Global Settings'));
|
||||
|
||||
s = m.section(form.NamedSection, 'globals', 'libreswan');
|
||||
s.anonymous = false;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.ListValue, 'debug', _('Debug Logs'));
|
||||
o.default = false;
|
||||
o.rmempty = false;
|
||||
o.value('none', _('none - No Logging'));
|
||||
o.value('base', _('base - Moderate Logging'));
|
||||
o.value('cpu-usage', _('cpu-usage - Timing/Load Logging'));
|
||||
o.value('crypto', _('crypto - All crypto related Logging'));
|
||||
o.value('tmi', _('tmi - Too Much/Excessive Logging'));
|
||||
o.value('private', _('private - Sensitive private-key/password Logging'));
|
||||
o.default = 'none'
|
||||
|
||||
o = s.option(form.Flag, 'uniqueids', _('Uniquely Identify Remotes'),
|
||||
_('Whether IDs should be considered identifying remote parties uniquely'));
|
||||
o.default = false;
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(widgets.NetworkSelect, 'listen_interface', _('Listen Interface'),
|
||||
_('Interface for IPsec to use'));
|
||||
o.datatype = 'string';
|
||||
o.multiple = false;
|
||||
o.optional = true;
|
||||
|
||||
o = s.option(form.Value, 'listen', _('Listen Address'),
|
||||
_('IP address to listen on, default depends on Listen Interface'));
|
||||
o.datatype = 'ip4addr';
|
||||
for (let nd of netDevs) {
|
||||
var addrs = nd.getIPAddrs();
|
||||
for (let ad of addrs) {
|
||||
o.value(ad.split('/')[0]);
|
||||
}
|
||||
}
|
||||
o.depends({ 'listen_interface' : '' });
|
||||
|
||||
o = s.option(form.Value, 'nflog_all', _('Enable nflog on nfgroup'),
|
||||
_('NFLOG group number to log all pre-crypt and post-decrypt traffic to'));
|
||||
o.datatype = 'uinteger';
|
||||
o.default = 0;
|
||||
o.rmempty = true;
|
||||
o.optional = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'virtual_private', _('Allowed Virtual Private'),
|
||||
_('The address ranges that may live behind a NAT router through which a client connects'));
|
||||
o.datatype = 'neg(ip4addr)';
|
||||
o.multiple = true;
|
||||
o.optional = true;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require form';
|
||||
'require poll';
|
||||
|
||||
const callLibreswanStatus = rpc.declare({
|
||||
object: 'libreswan',
|
||||
method: 'status',
|
||||
expect: { },
|
||||
});
|
||||
|
||||
function secondsToString(seconds) {
|
||||
const numdays = Math.floor(seconds / 86400);
|
||||
seconds %= 86400;
|
||||
const numhours = Math.floor(seconds / 3600);
|
||||
seconds %= 3600;
|
||||
const numminutes = Math.floor(seconds / 60);
|
||||
const numseconds = seconds % 60;
|
||||
|
||||
return [
|
||||
numdays ? `${numdays}d` : '',
|
||||
numhours ? `${numhours}h` : '',
|
||||
numminutes ? `${numminutes}m` : '',
|
||||
`${numseconds}s`
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
const table =
|
||||
E('table', { 'class': 'table lases' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Name')),
|
||||
E('th', { 'class': 'th' }, _('Remote')),
|
||||
E('th', { 'class': 'th' }, _('Local Subnet')),
|
||||
E('th', { 'class': 'th' }, _('Remote Subnet')),
|
||||
E('th', { 'class': 'th' }, _('Tx')),
|
||||
E('th', { 'class': 'th' }, _('Rx')),
|
||||
E('th', { 'class': 'th' }, _('Phase1')),
|
||||
E('th', { 'class': 'th' }, _('Phase2')),
|
||||
E('th', { 'class': 'th' }, _('Status')),
|
||||
E('th', { 'class': 'th' }, _('Uptime')),
|
||||
E([])
|
||||
])
|
||||
]);
|
||||
|
||||
poll.add(function() {
|
||||
return callLibreswanStatus().then(function(tunnelsInfo) {
|
||||
const tunnels = Array.isArray(tunnelsInfo.tunnels) ? tunnelsInfo.tunnels : [];
|
||||
|
||||
cbi_update_table(table,
|
||||
tunnels.map(function(tunnel) {
|
||||
return [
|
||||
tunnel.name,
|
||||
tunnel.right,
|
||||
tunnel.leftsubnet,
|
||||
tunnel.rightsubnet,
|
||||
tunnel.tx,
|
||||
tunnel.rx,
|
||||
tunnel.phase1 ? _('Up') : _('Down'),
|
||||
tunnel.phase2 ? _('Up') : _('Down'),
|
||||
tunnel.connected ? _('Up') : _('Down'),
|
||||
secondsToString(tunnel.uptime),
|
||||
];
|
||||
}),
|
||||
E('em', _('There are no active Tunnels'))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return E([
|
||||
E('h3', _('IPSec Tunnels Summary')),
|
||||
E('br'),
|
||||
table
|
||||
]);
|
||||
},
|
||||
|
||||
handleSave: null,
|
||||
handleSaveApply:null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
render() {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('libreswan', _('IPSec Proposals'));
|
||||
|
||||
s = m.section(form.GridSection, 'crypto_proposal');
|
||||
s.anonymous = false;
|
||||
s.addremove = true;
|
||||
s.nodescriptions = true;
|
||||
s.addbtntitle = _('Add Proposal');
|
||||
|
||||
s.renderSectionAdd = function(extra_class) {
|
||||
const el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments),
|
||||
nameEl = el.querySelector('.cbi-section-create-name');
|
||||
ui.addValidator(nameEl, 'uciname', true, function(v) {
|
||||
let sections = [
|
||||
...uci.sections('libreswan', 'crypto_proposal'),
|
||||
...uci.sections('libreswan', 'tunnel'),
|
||||
];
|
||||
if (sections.find(function(s) {
|
||||
return s['.name'] == v;
|
||||
})) {
|
||||
return _('This may not share the same name as other proposals or configured tunnels.');
|
||||
}
|
||||
if (v.length > 15) return _('Name length shall not exceed 15 characters');
|
||||
return true;
|
||||
}, 'blur', 'keyup');
|
||||
return el;
|
||||
};
|
||||
|
||||
o = s.tab('general', _('General'));
|
||||
|
||||
o = s.taboption('general', form.MultiValue, 'hash_algorithm', _('Hash Algorithm'), ('* = %s').format(_('Unsafe')));
|
||||
o.default = 'md5';
|
||||
o.value('md5', _('MD5*'));
|
||||
o.value('sha1', _('SHA1*'));
|
||||
o.value('sha256', _('SHA256'));
|
||||
o.value('sha384', _('SHA384'));
|
||||
o.value('sha512', _('SHA512'));
|
||||
|
||||
o = s.taboption('general', form.MultiValue, 'encryption_algorithm', _('Encryption Method'), ('* = %s').format(_('Unsafe')));
|
||||
o.default = 'aes';
|
||||
o.value('3des', _('3DES*'))
|
||||
o.value('aes', _('AES'))
|
||||
o.value('aes_ctr', _('AES_CTR'));
|
||||
o.value('aes_cbc', _('AES_CBC'));
|
||||
o.value('aes128', _('AES128'));
|
||||
o.value('aes192', _('AES192'));
|
||||
o.value('aes256', _('AES256'));
|
||||
o.value('camellia_cbc', _('CAMELLIA_CBC'));
|
||||
|
||||
o = s.taboption('general', form.MultiValue, 'dh_group', _('DH Group'),
|
||||
('* = %s <a href="%s">RFC8247</a>.').format(_('Unsafe, See'), 'https://www.rfc-editor.org/rfc/rfc8247#section-2.4'));
|
||||
o.default = 'modp1536';
|
||||
o.value('modp1536', _('DH Group 5*'));
|
||||
o.value('modp2048', _('DH Group 14'));
|
||||
o.value('modp3072', _('DH Group 15'));
|
||||
o.value('modp4096', _('DH Group 16'));
|
||||
o.value('modp6144', _('DH Group 17'));
|
||||
o.value('modp8192', _('DH Group 18'));
|
||||
o.value('dh19', _('DH Group 19'));
|
||||
o.value('dh20', _('DH Group 20'));
|
||||
o.value('dh21', _('DH Group 21'));
|
||||
o.value('dh22', _('DH Group 22*'));
|
||||
o.value('dh31', _('DH Group 31'));
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,274 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require network';
|
||||
'require validation';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
function calculateNetwork(addr, mask) {
|
||||
const parsedAddr = validation.parseIPv4(String(addr));
|
||||
if (parsedAddr == null) return null;
|
||||
|
||||
const parsedMask = !isNaN(mask)
|
||||
? validation.parseIPv4(network.prefixToMask(+mask))
|
||||
: validation.parseIPv4(String(mask));
|
||||
if (parsedMask == null) return null;
|
||||
|
||||
const networkAddr = parsedAddr.map((byte, i) => byte & (parsedMask[i] >>> 0 & 255));
|
||||
|
||||
return `${networkAddr.join('.')}/${network.maskToPrefix(parsedMask.join('.'))}`;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
network.getDevices(),
|
||||
uci.load('libreswan'),
|
||||
]);
|
||||
},
|
||||
|
||||
render([netDevs]) {
|
||||
let m, s, o;
|
||||
|
||||
let proposals = uci.sections('libreswan', 'crypto_proposal');
|
||||
if (proposals == '') {
|
||||
ui.addNotification(null, E('p', _('Proposals must be configured for Tunnels')));
|
||||
return;
|
||||
}
|
||||
|
||||
m = new form.Map('libreswan', 'IPSec Tunnels');
|
||||
|
||||
s = m.section(form.GridSection, 'tunnel');
|
||||
s.anonymous = false;
|
||||
s.addremove = true;
|
||||
s.nodedescription = false;
|
||||
s.addbtntitle = _('Add Tunnel');
|
||||
|
||||
s.renderSectionAdd = function(extra_class) {
|
||||
const el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments);
|
||||
const nameEl = el.querySelector('.cbi-section-create-name');
|
||||
ui.addValidator(nameEl, 'uciname', true, function(v) {
|
||||
let sections = [
|
||||
...uci.sections('libreswan', 'crypto_proposal'),
|
||||
...uci.sections('libreswan', 'tunnel'),
|
||||
];
|
||||
|
||||
if (sections.find(function(s) {
|
||||
return s['.name'] == v;
|
||||
})) {
|
||||
return _('This may not share the same name as other proposals or configured tunnels.');
|
||||
}
|
||||
if (v.length > 15) return _('Name length shall not exceed 15 characters');
|
||||
return true;
|
||||
}, 'blur', 'keyup');
|
||||
return el;
|
||||
};
|
||||
|
||||
o = s.tab('general', _('General'));
|
||||
o = s.tab('authentication', _('Authentication'));
|
||||
o = s.tab('interface', _('Interface'));
|
||||
o = s.tab('advanced', _('Advanced'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'auto', _('Mode'));
|
||||
o.default = 'start';
|
||||
o.value('add', _('Listen'));
|
||||
o.value('start', _('Initiate'));
|
||||
|
||||
o = s.taboption('general', widgets.NetworkSelect, 'left_interface', _('Left Interface'));
|
||||
o.datatype = 'string';
|
||||
o.multiple = false;
|
||||
o.optional = true;
|
||||
|
||||
o = s.taboption('general', form.Value, 'left', _('Left IP/Device'));
|
||||
o.datatype = 'or(string, ipaddr)';
|
||||
netDevs.forEach(netDev => {
|
||||
netDev.getIPAddrs().forEach(addr => {
|
||||
o.value(addr.split('/')[0]);
|
||||
});
|
||||
});
|
||||
netDevs.forEach(netDev => {
|
||||
o.value(`%${netDev.device}`);
|
||||
});
|
||||
o.value('%defaultroute');
|
||||
o.optional = false;
|
||||
o.depends({ 'left_interface' : '' });
|
||||
|
||||
o = s.taboption('general', form.Value, 'leftid', _('Left ID'));
|
||||
o.datatype = 'string';
|
||||
o.value('%any');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('general', form.Value, 'right', _('Remote IP'));
|
||||
o.datatype = 'or(string, ipaddr)';
|
||||
o.value('0.0.0.0');
|
||||
o.value('%any');
|
||||
o.optional = false;
|
||||
|
||||
o = s.taboption('general', form.Value, 'rightid', _('Right ID'));
|
||||
o.datatype = 'string';
|
||||
o.value('%any');
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('general', form.Value, 'leftsourceip', _('Local Source IP'));
|
||||
o.datatype = 'ipaddr';
|
||||
netDevs.forEach(netDev => {
|
||||
netDev.getIPAddrs().forEach(addr => {
|
||||
o.value(addr.split('/')[0]);
|
||||
});
|
||||
});
|
||||
o.optional = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('general', form.Value, 'rightsourceip', _('Remote Source IP'));
|
||||
o.datatype = 'ipaddr';
|
||||
o.optional = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('general', form.DynamicList, 'leftsubnets', _('Local Subnets'));
|
||||
o.datatype = 'ipaddr';
|
||||
for (let nd of netDevs) {
|
||||
const addrs = nd.getIPAddrs();
|
||||
for (let ad of addrs) {
|
||||
const subnet = calculateNetwork(ad.split('/')[0], ad.split('/')[1]);
|
||||
if (subnet) {
|
||||
o.value(subnet);
|
||||
}
|
||||
}
|
||||
}
|
||||
o.value('0.0.0.0/0');
|
||||
|
||||
o = s.taboption('general', form.DynamicList, 'rightsubnets', _('Remote Subnets'));
|
||||
o.datatype = 'ipaddr';
|
||||
o.value('0.0.0.0/0');
|
||||
|
||||
o = s.taboption('authentication', form.ListValue, 'authby', _('Auth Method'));
|
||||
o.default = 'secret'
|
||||
o.value('secret', _('Shared Secret'));
|
||||
o.value('never', 'Never');
|
||||
o.value('null', 'Null');
|
||||
o.modalonly = true;
|
||||
o.optional = false;
|
||||
|
||||
o = s.taboption('authentication', form.Value, 'psk', _('Preshared Key'));
|
||||
o.datatype = 'and(string, minlength(8))'
|
||||
o.depends({ 'authby' : 'secret' });
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
o.optional = false;
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'ikev2', _('IKE V2'));
|
||||
o.default = 'yes';
|
||||
o.value('yes', _('IKE Version 2'));
|
||||
o.value('no', _('IKE Version 1'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.MultiValue, 'ike', _('Phase1 Proposals'));
|
||||
for (let prop of proposals) {
|
||||
o.value(prop['.name']);
|
||||
}
|
||||
o.modalonly = true;
|
||||
|
||||
function timevalidate(section_id, value) {
|
||||
if (!/^[0-9]{1,3}[smhd]$/.test(value)) {
|
||||
return _('Acceptable values are an integer followed by m, h, d');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'ikelifetime', _('IKE Life Time'), _('Acceptable values are an integer followed by m, h, d'));
|
||||
o.default = '8h';
|
||||
o.value('1h', '1h');
|
||||
o.value('2h', '2h');
|
||||
o.value('4h', '4h');
|
||||
o.value('8h', '8h');
|
||||
o.value('12h', '12h');
|
||||
o.value('16h', '16h');
|
||||
o.value('24h', '24h');
|
||||
o.modalonly = false;
|
||||
o.modalonly = true;
|
||||
o.validate = timevalidate;
|
||||
|
||||
o = s.taboption('advanced', form.Flag, 'rekey', _('Rekey'));
|
||||
o.default = false;
|
||||
o.modalonly = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'rekeymargin', _('Rekey Margin Time'), _('Acceptable values are an integer followed by m, h, d'));
|
||||
o.default = '9m';
|
||||
o.value('5m', '5m');
|
||||
o.value('9m', '9m');
|
||||
o.value('15m', '15m');
|
||||
o.value('20m', '20m');
|
||||
o.value('30m', '30m');
|
||||
o.value('60m', '60m');
|
||||
o.modalonly = false;
|
||||
o.modalonly = true;
|
||||
o.validate = timevalidate;
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'dpdaction', _('DPD Action'));
|
||||
o.default = 'restart';
|
||||
o.value('none', _('None'));
|
||||
o.value('clear', _('Clear'));
|
||||
o.value('hold', _('Hold'));
|
||||
o.value('restart', _('Restart'));
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'dpddelay', _('DPD Delay'));
|
||||
o.datatype = 'uinteger';
|
||||
o.default = 30;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'dpdtimeout', _('DPD Timeout'));
|
||||
o.datatype = 'uinteger';
|
||||
o.default = 150;
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.ListValue, 'phase2', _('Phase2 Protocol'));
|
||||
o.default = 'esp';
|
||||
o.value('esp', 'ESP');
|
||||
o.modalonly = true;
|
||||
o.optional = false;
|
||||
|
||||
o = s.taboption('advanced', form.MultiValue, 'phase2alg', _('Phase2 Proposals'));
|
||||
for (let prop of proposals) {
|
||||
o.value(prop['.name']);
|
||||
}
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'nflog', _('Enable nflog on nfgroup'));
|
||||
o.default = 0;
|
||||
o.datatype = 'uinteger';
|
||||
o.rmempty = true;
|
||||
o.optional = true;
|
||||
o.modalonly = true;
|
||||
|
||||
let interfaces = uci.sections('network', 'interface');
|
||||
o = s.taboption('advanced', form.ListValue, 'interface', _('Tunnel Interface'),
|
||||
_('Lists XFRM interfaces in format "ipsecN", N denotes ifid of xfrm interface') + '<br>' +
|
||||
_('Lists VTI interfaces configured with ikey and okey'));
|
||||
o.datatype = 'string';
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
o.value('');
|
||||
interfaces.forEach(iface => {
|
||||
const { proto, ikey, okey, ifid, ['.name']: name } = iface;
|
||||
|
||||
if (proto === "vti" && ikey && okey) {
|
||||
o.value(name, `VTI - ${name}`);
|
||||
}
|
||||
|
||||
if (proto === "xfrm" && ifid && name.match(`ipsec${ifid}`)) {
|
||||
o.value(name, `XFRM - ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
o = s.taboption('advanced', form.Flag, 'update_peeraddr', _('Update Peer Address'),
|
||||
_('Auto Update Peer Address of VTI interface'));
|
||||
o.rmempty = true;
|
||||
o.modalonly = true;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"admin/vpn/libreswan": {
|
||||
"title": "Libreswan IPSec",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/vpn/libreswan/overview": {
|
||||
"title": "Overview",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "libreswan/overview"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/vpn/libreswan/globals": {
|
||||
"title": "IPSec Globals",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "libreswan/globals"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/vpn/libreswan/proposals": {
|
||||
"title": "IPSec Proposals",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "libreswan/proposals"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/vpn/libreswan/tunnels": {
|
||||
"title": "IPSec Tunnels",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "libreswan/tunnels"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"luci-app-libreswan" : {
|
||||
"description" : "Grant access to LuCI app Libreswan IPSec",
|
||||
"read" : {
|
||||
"ubus" : {
|
||||
"libreswan" : [ "*" ]
|
||||
},
|
||||
"uci": [ "libreswan" ]
|
||||
},
|
||||
"write" : {
|
||||
"uci": [ "libreswan" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"config": "libreswan",
|
||||
"init": "ipsec"
|
||||
}
|
||||
Reference in new issue
Block a user