2019-08-20 13:31:35 +00:00
'use strict' ;
2020-08-18 20:51:07 +00:00
'require ui' ;
2020-03-03 20:14:04 +00:00
'require uci' ;
2020-08-18 20:51:07 +00:00
'require rpc' ;
2019-08-20 13:31:35 +00:00
'require form' ;
'require network' ;
2020-08-18 20:51:07 +00:00
var generateKey = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'generateKeyPair' ,
expect : { keys : { } }
} ) ;
2021-11-11 00:31:43 +00:00
var getPublicAndPrivateKeyFromPrivate = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'getPublicAndPrivateKeyFromPrivate' ,
params : [ 'privkey' ] ,
expect : { keys : { } }
} ) ;
2021-09-08 10:56:10 +00:00
var generateQrCode = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'generateQrCode' ,
2021-09-10 12:01:53 +00:00
params : [ 'privkey' , 'psk' , 'allowed_ips' ] ,
2021-09-08 10:56:10 +00:00
expect : { qr _code : '' }
} ) ;
2019-08-20 13:31:35 +00:00
function validateBase64 ( section _id , value ) {
2019-09-17 06:28:45 +00:00
if ( value . length == 0 )
return true ;
2020-01-21 17:38:04 +00:00
if ( value . length != 44 || ! value . match ( /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ ) )
2019-08-20 13:31:35 +00:00
return _ ( 'Invalid Base64 key string' ) ;
2020-05-12 12:45:47 +00:00
if ( value [ 43 ] != "=" )
return _ ( 'Invalid Base64 key string' ) ;
2019-08-20 13:31:35 +00:00
return true ;
}
2021-09-08 10:56:10 +00:00
function findSection ( sections , name ) {
for ( var i = 0 ; i < sections . length ; i ++ ) {
var section = sections [ i ] ;
if ( section [ '.name' ] == name ) return section ;
}
return null ;
}
2021-09-10 12:01:53 +00:00
function generateDescription ( name , texts ) {
return E ( 'li' , { 'style' : 'color: inherit;' } , [
E ( 'span' , name ) ,
E ( 'ul' , texts . map ( function ( text ) {
return E ( 'li' , { 'style' : 'color: inherit;' } , text ) ;
} ) )
] ) ;
}
2019-08-20 13:31:35 +00:00
return network . registerProtocol ( 'wireguard' , {
getI18n : function ( ) {
return _ ( 'WireGuard VPN' ) ;
} ,
getIfname : function ( ) {
return this . _ubus ( 'l3_device' ) || this . sid ;
} ,
getOpkgPackage : function ( ) {
return 'wireguard-tools' ;
} ,
isFloating : function ( ) {
return true ;
} ,
isVirtual : function ( ) {
return true ;
} ,
getDevices : function ( ) {
return null ;
} ,
containsDevice : function ( ifname ) {
return ( network . getIfnameOf ( ifname ) == this . getIfname ( ) ) ;
} ,
renderFormOptions : function ( s ) {
var o , ss ;
// -- general ---------------------------------------------------------------------
o = s . taboption ( 'general' , form . Value , 'private_key' , _ ( 'Private Key' ) , _ ( 'Required. Base64-encoded private key for this interface.' ) ) ;
o . password = true ;
o . validate = validateBase64 ;
o . rmempty = false ;
2021-11-11 00:31:43 +00:00
var sections = uci . sections ( 'network' ) ;
var serverName = this . getIfname ( ) ;
var server = findSection ( sections , serverName ) ;
o = s . taboption ( 'general' , form . Value , 'public_key' , _ ( 'Public Key' ) , _ ( 'Base64-encoded public key of this interface for sharing.' ) ) ;
o . rmempty = false ;
o . write = function ( ) { /* write nothing */ } ;
o . load = function ( s ) {
return getPublicAndPrivateKeyFromPrivate ( server . private _key ) . then (
function ( keypair ) {
return keypair . pub || '' ;
} ,
function ( error ) {
return _ ( 'Error getting PublicKey' ) ;
} , this )
} ;
2020-08-18 20:51:07 +00:00
o = s . taboption ( 'general' , form . Button , 'generate_key' , _ ( 'Generate Key' ) ) ;
o . inputstyle = 'apply' ;
o . onclick = ui . createHandlerFn ( this , function ( section _id , ev ) {
return generateKey ( ) . then ( function ( keypair ) {
var keyInput = document . getElementById ( 'widget.cbid.network.%s.private_key' . format ( section _id ) ) ,
2021-11-11 00:31:43 +00:00
changeEvent = new Event ( 'change' ) ,
pubKeyInput = document . getElementById ( 'widget.cbid.network.%s.public_key' . format ( section _id ) ) ;
2020-08-18 20:51:07 +00:00
keyInput . value = keypair . priv || '' ;
2021-11-11 00:31:43 +00:00
pubKeyInput . value = keypair . pub || '' ;
2020-08-18 20:51:07 +00:00
keyInput . dispatchEvent ( changeEvent ) ;
} ) ;
} , s . section ) ;
2019-08-20 13:31:35 +00:00
o = s . taboption ( 'general' , form . Value , 'listen_port' , _ ( 'Listen Port' ) , _ ( 'Optional. UDP port used for outgoing and incoming packets.' ) ) ;
o . datatype = 'port' ;
o . placeholder = _ ( 'random' ) ;
o . optional = true ;
o = s . taboption ( 'general' , form . DynamicList , 'addresses' , _ ( 'IP Addresses' ) , _ ( 'Recommended. IP addresses of the WireGuard interface.' ) ) ;
o . datatype = 'ipaddr' ;
o . optional = true ;
2020-02-08 18:05:50 +00:00
o = s . taboption ( 'general' , form . Flag , 'nohostroute' , _ ( 'No Host Routes' ) , _ ( 'Optional. Do not create host routes to peers.' ) ) ;
o . optional = true ;
2019-08-20 13:31:35 +00:00
// -- advanced --------------------------------------------------------------------
o = s . taboption ( 'advanced' , form . Value , 'mtu' , _ ( 'MTU' ) , _ ( 'Optional. Maximum Transmission Unit of tunnel interface.' ) ) ;
o . datatype = 'range(1280,1420)' ;
o . placeholder = '1420' ;
o . optional = true ;
o = s . taboption ( 'advanced' , form . Value , 'fwmark' , _ ( 'Firewall Mark' ) , _ ( 'Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with <code>0x</code>.' ) ) ;
o . optional = true ;
o . validate = function ( section _id , value ) {
2021-06-02 18:35:32 +00:00
if ( value . length > 0 && ! value . match ( /^0x[a-fA-F0-9]{1,8}$/ ) )
2019-08-20 13:31:35 +00:00
return _ ( 'Invalid hexadecimal value' ) ;
return true ;
} ;
// -- peers -----------------------------------------------------------------------
try {
s . tab ( 'peers' , _ ( 'Peers' ) , _ ( 'Further information about WireGuard interfaces and peers at <a href=\'http://wireguard.com\'>wireguard.com</a>.' ) ) ;
}
catch ( e ) { }
o = s . taboption ( 'peers' , form . SectionValue , '_peers' , form . TypedSection , 'wireguard_%s' . format ( s . section ) ) ;
o . depends ( 'proto' , 'wireguard' ) ;
ss = o . subsection ;
ss . anonymous = true ;
ss . addremove = true ;
ss . addbtntitle = _ ( 'Add peer' ) ;
ss . renderSectionPlaceholder = function ( ) {
return E ( [ ] , [
E ( 'br' ) ,
E ( 'em' , _ ( 'No peers defined yet' ) )
] ) ;
} ;
2021-11-12 22:20:10 +00:00
o = ss . option ( form . Flag , 'disabled' , _ ( 'Peer disabled' ) , _ ( 'Enable / Disable peer. Restart wireguard interface to apply changes.' ) ) ;
o . optional = true ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Value , 'description' , _ ( 'Description' ) , _ ( 'Optional. Description of peer.' ) ) ;
o . placeholder = 'My Peer' ;
o . datatype = 'string' ;
o . optional = true ;
2021-09-08 10:56:10 +00:00
o = ss . option ( form . Value , 'description' , _ ( 'QR-Code' ) ) ;
o . render = L . bind ( function ( view , section _id ) {
var sections = uci . sections ( 'network' ) ;
2021-09-10 12:01:53 +00:00
var client = findSection ( sections , section _id ) ;
2021-09-08 10:56:10 +00:00
var serverName = this . getIfname ( ) ;
var server = findSection ( sections , serverName ) ;
2021-09-10 12:01:53 +00:00
var interfaceTexts = [
'PrivateKey: ' + _ ( 'A random, on the fly generated "PrivateKey", the key will not be saved on the router' )
] ;
var peerTexts = [
'PublicKey: ' + _ ( 'The "PublicKey" of that wg interface' ) ,
'AllowedIPs: ' + _ ( 'The list of this client\'s "AllowedIPs" or "0.0.0.0/0, ::/0" if not configured' ) ,
'PresharedKey: ' + _ ( 'If available, the client\'s "PresharedKey"' )
] ;
var description = [
2021-10-07 14:19:30 +00:00
E ( 'span' , [
_ ( 'If there are any unsaved changes for this client, please save the configuration before generating a QR-Code' ) ,
E ( 'br' ) ,
_ ( 'The QR-Code works per wg interface, it will be refreshed with every button click and transfers the following information:' )
] ) ,
2021-09-10 12:01:53 +00:00
E ( 'ul' , [
generateDescription ( '[Interface]' , interfaceTexts ) ,
generateDescription ( '[Peer]' , peerTexts )
] )
] ;
2021-09-08 10:56:10 +00:00
return E ( 'div' , { 'class' : 'cbi-value' } , [
E ( 'label' , { 'class' : 'cbi-value-title' } , _ ( 'QR-Code' ) ) ,
E ( 'div' , {
2021-11-21 22:40:21 +00:00
'class' : 'cbi-value-field' ,
2021-09-08 10:56:10 +00:00
'style' : 'display: flex; flex-direction: column; align-items: baseline;' ,
'id' : 'qr-' + section _id
} , [
E ( 'button' , {
'class' : 'btn cbi-button cbi-button-apply' ,
2021-09-10 12:01:53 +00:00
'click' : ui . createHandlerFn ( this , function ( server , client , section _id ) {
2021-09-08 10:56:10 +00:00
var qrDiv = document . getElementById ( 'qr-' + section _id ) ;
var qrEl = qrDiv . querySelector ( 'value' ) ;
var qrBtn = qrDiv . querySelector ( 'button' ) ;
var qrencodeErr = '<b>%q</b>' . format (
_ ( 'For QR-Code support please install the qrencode package!' ) ) ;
if ( qrEl . innerHTML != '' && qrEl . innerHTML != qrencodeErr ) {
qrEl . innerHTML = '' ;
qrBtn . innerHTML = _ ( 'Generate New QR-Code' )
} else {
qrEl . innerHTML = _ ( 'Loading QR-Code...' ) ;
2021-09-10 12:01:53 +00:00
generateQrCode ( server . private _key , client . preshared _key ,
client . allowed _ips ) . then ( function ( qrCode ) {
if ( qrCode == '' ) {
2021-09-08 10:56:10 +00:00
qrEl . innerHTML = qrencodeErr ;
2021-09-10 12:01:53 +00:00
} else {
qrEl . innerHTML = qrCode ;
qrBtn . innerHTML = _ ( 'Hide QR-Code' ) ;
}
} ) ;
2021-09-08 10:56:10 +00:00
}
2021-09-10 12:01:53 +00:00
} , server , client , section _id )
2021-09-08 10:56:10 +00:00
} , _ ( 'Generate new QR-Code' ) ) ,
E ( 'value' , {
'class' : 'cbi-section' ,
'style' : 'margin: 0;'
} ) ,
E ( 'div' , { 'class' : 'cbi-value-description' } , description )
] )
] ) ;
} , this ) ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Value , 'public_key' , _ ( 'Public Key' ) , _ ( 'Required. Base64-encoded public key of peer.' ) ) ;
o . validate = validateBase64 ;
o . rmempty = false ;
o = ss . option ( form . Value , 'preshared_key' , _ ( 'Preshared Key' ) , _ ( 'Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.' ) ) ;
o . password = true ;
o . validate = validateBase64 ;
o . optional = true ;
2021-08-29 04:44:38 +00:00
o = ss . option ( form . DynamicList , 'allowed_ips' , _ ( 'Allowed IPs' ) , _ ( "Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel." ) ) ;
2019-08-20 13:31:35 +00:00
o . datatype = 'ipaddr' ;
2021-08-29 04:44:38 +00:00
o . optional = true ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Flag , 'route_allowed_ips' , _ ( 'Route Allowed IPs' ) , _ ( 'Optional. Create routes for Allowed IPs for this peer.' ) ) ;
o = ss . option ( form . Value , 'endpoint_host' , _ ( 'Endpoint Host' ) , _ ( 'Optional. Host of peer. Names are resolved prior to bringing up the interface.' ) ) ;
o . placeholder = 'vpn.example.com' ;
o . datatype = 'host' ;
o = ss . option ( form . Value , 'endpoint_port' , _ ( 'Endpoint Port' ) , _ ( 'Optional. Port of peer.' ) ) ;
o . placeholder = '51820' ;
o . datatype = 'port' ;
o = ss . option ( form . Value , 'persistent_keepalive' , _ ( 'Persistent Keep Alive' ) , _ ( 'Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.' ) ) ;
o . datatype = 'range(0,65535)' ;
o . placeholder = '0' ;
2020-03-03 20:14:04 +00:00
} ,
deleteConfiguration : function ( ) {
uci . sections ( 'network' , 'wireguard_%s' . format ( this . sid ) , function ( s ) {
uci . remove ( 'network' , s [ '.name' ] ) ;
} ) ;
2019-08-20 13:31:35 +00:00
}
} ) ;