luci-app-cjdns: import package from SeattleMeshnet/meshbox

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2015-04-20 12:45:07 +02:00
parent 94ee37fe6c
commit 89914e47de
10 changed files with 519 additions and 0 deletions

38
luci-app-cjdns/Makefile Normal file
View File

@ -0,0 +1,38 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-cjdns
PKG_VERSION:=1.3
PKG_RELEASE:=4
PKG_LICENSE:=GPL-3.0
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-cjdns
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=Encrypted near-zero-conf mesh routing protocol
URL:=https://github.com/hyperboria/cjdns
MAINTAINER:=Lars Gierth <larsg@systemli.org>
DEPENDS:=+cjdns +luci-base
endef
define Package/luci-app-cjdns/description
This package allows you to configure and inspect cjdns networking using LuCI.
Cjdns implements an encrypted IPv6 network using public-key cryptography
for address allocation and a distributed hash table for routing.
This provides near-zero-configuration networking, and prevents many
of the security and scalability issues that plague existing networks.
endef
define Build/Compile
endef
define Package/luci-app-cjdns/install
$(INSTALL_DIR) $(1)/usr/lib/lua/luci
$(CP) ./luasrc/* $(1)/usr/lib/lua/luci
endef
$(eval $(call BuildPackage,luci-app-cjdns))

View File

@ -0,0 +1,105 @@
module("luci.controller.cjdns", package.seeall)
cjdns = require "cjdns/init"
dkjson = require "dkjson"
function index()
if not nixio.fs.access("/etc/config/cjdns") then
return
end
entry({"admin", "services", "cjdns"},
cbi("cjdns/overview"), _("cjdns")).dependent = true
entry({"admin", "services", "cjdns", "overview"},
cbi("cjdns/overview"), _("Overview"), 1).leaf = false
entry({"admin", "services", "cjdns", "peering"},
cbi("cjdns/peering"), _("Peers"), 2).leaf = false
entry({"admin", "services", "cjdns", "iptunnel"},
cbi("cjdns/iptunnel"), _("IP Tunnel"), 3).leaf = false
entry({"admin", "services", "cjdns", "settings"},
cbi("cjdns/settings"), _("Settings"), 4).leaf = false
entry({"admin", "services", "cjdns", "cjdrouteconf"},
cbi("cjdns/cjdrouteconf"), _("cjdroute.conf"), 5).leaf = false
entry({"admin", "services", "cjdns", "peers"}, call("act_peers")).leaf = true
entry({"admin", "services", "cjdns", "ping"}, call("act_ping")).leaf = true
end
function act_peers()
require("cjdns/uci")
admin = cjdns.uci.makeInterface()
local page = 0
local peers = {}
while page do
local response, err = admin:auth({
q = "InterfaceController_peerStats",
page = page
})
if err or response.error then
luci.http.status(502, "Bad Gateway")
luci.http.prepare_content("application/json")
luci.http.write_json({ err = err, response = response })
return
end
for i,peer in pairs(response.peers) do
peer.ipv6 = publictoip6(peer.publicKey)
if peer.user == nil then
peer.user = ''
uci.cursor():foreach("cjdns", "udp_peer", function(udp_peer)
if peer.publicKey == udp_peer.public_key then
peer.user = udp_peer.user
end
end)
end
peers[#peers + 1] = peer
end
if response.more then
page = page + 1
else
page = nil
end
end
luci.http.status(200, "OK")
luci.http.prepare_content("application/json")
luci.http.write_json(peers)
end
function act_ping()
require("cjdns/uci")
admin = cjdns.uci.makeInterface()
local response, err = admin:auth({
q = "SwitchPinger_ping",
path = luci.http.formvalue("label"),
timeout = tonumber(luci.http.formvalue("timeout"))
})
if err or response.error then
luci.http.status(502, "Bad Gateway")
luci.http.prepare_content("application/json")
luci.http.write_json({ err = err, response = response })
return
end
luci.http.status(200, "OK")
luci.http.prepare_content("application/json")
luci.http.write_json(response)
end
function publictoip6(publicKey)
local process = io.popen("/usr/bin/publictoip6 " .. publicKey, "r")
local ipv6 = process:read()
process:close()
return ipv6
end

View File

@ -0,0 +1,32 @@
m = Map("cjdns", translate("cjdns"),
translate("Implements an encrypted IPv6 network using public-key \
cryptography for address allocation and a distributed hash table for \
routing. This provides near-zero-configuration networking, and prevents \
many of the security and scalability issues that plague existing \
networks."))
dkjson = require("dkjson")
cjdns = require("cjdns")
require("cjdns/uci")
local f = SimpleForm("cjdrouteconf", translate("Edit cjdroute.conf"),
translate("JSON interface to what's /etc/cjdroute.conf on other systems. \
Will be parsed and written to UCI by <code>cjdrouteconf set</code>."))
local o = f:field(Value, "_cjdrouteconf")
o.template = "cbi/tvalue"
o.rows = 25
function o.cfgvalue(self, section)
return dkjson.encode(cjdns.uci.get(), { indent = true })
end
function o.write(self, section, value)
local obj, pos, err = dkjson.decode(value, 1, nil)
if obj then
cjdns.uci.set(obj)
end
end
return f

View File

@ -0,0 +1,46 @@
uci = require "luci.model.uci"
cursor = uci:cursor_state()
m = Map("cjdns", translate("cjdns"),
translate("Implements an encrypted IPv6 network using public-key \
cryptography for address allocation and a distributed hash table for \
routing. This provides near-zero-configuration networking, and prevents \
many of the security and scalability issues that plague existing \
networks."))
m.on_after_commit = function(self)
os.execute("/etc/init.d/cjdns restart")
end
-- Outgoing
outgoing = m:section(TypedSection, "iptunnel_outgoing", translate("Outgoing IP Tunnel Connections"),
translate("Enter the public keys of the nodes that will provide Internet access."))
outgoing.anonymous = true
outgoing.addremove = true
outgoing.template = "cbi/tblsection"
outgoing:option(Value, "public_key", translate("Public Key")).size = 55
-- Allowed
allowed = m:section(TypedSection, "iptunnel_allowed", translate("Allowed IP Tunnel Connections"),
translate("Enter the public key of the node you will provide Internet access to, along with the \
IPv4 and/or IPv6 address you will assign them."))
allowed.anonymous = true
allowed.addremove = true
public_key = allowed:option(Value, "public_key", translate("Public Key"))
public_key.template = "cjdns/value"
public_key.size = 55
ipv4 = allowed:option(Value, "ipv4", translate("IPv4"))
ipv4.template = "cjdns/value"
ipv4.datatype = 'ipaddr'
ipv4.size = 55
ipv6 = allowed:option(Value, "ipv6", translate("IPv6"),
translate("IPv6 addresses should be entered <em>without</em> brackets here, e.g. <code>2001:123:ab::10</code>."))
ipv6.template = "cjdns/value"
ipv6.datatype = 'ip6addr'
ipv6.size = 55
return m

View File

@ -0,0 +1,10 @@
m = Map("cjdns", translate("cjdns"),
translate("Implements an encrypted IPv6 network using public-key \
cryptography for address allocation and a distributed hash table for \
routing. This provides near-zero-configuration networking, and prevents \
many of the security and scalability issues that plague existing \
networks."))
m:section(SimpleSection).template = "cjdns/status"
return m

View File

@ -0,0 +1,73 @@
uci = require "luci.model.uci"
cursor = uci:cursor_state()
cjdns = require("cjdns")
require("cjdns/uci")
m = Map("cjdns", translate("cjdns"),
translate("Implements an encrypted IPv6 network using public-key \
cryptography for address allocation and a distributed hash table for \
routing. This provides near-zero-configuration networking, and prevents \
many of the security and scalability issues that plague existing \
networks."))
m.on_after_commit = function(self)
os.execute("/etc/init.d/cjdns restart")
end
-- Authorized Passwords
passwords = m:section(TypedSection, "password", translate("Authorized Passwords"),
translate("Anyone offering one of the these passwords will be allowed to peer with you on the existing UDP and Ethernet interfaces."))
passwords.anonymous = true
passwords.addremove = true
passwords.template = "cbi/tblsection"
passwords:option(Value, "user", translate("User/Name"),
translate("Must be unique.")
).default = "user-" .. cjdns.uci.random_string(6)
passwords:option(Value, "contact", translate("Contact"), translate("Optional, for out-of-band communication."))
passwords:option(Value, "password", translate("Password"),
translate("Hand out to your peer, in accordance with the peering best practices of the network.")
).default = cjdns.uci.random_string(32)
-- UDP Peers
udp_peers = m:section(TypedSection, "udp_peer", translate("Outgoing UDP Peers"),
translate("For peering via public IP networks, the peer handed you their Public Key and IP address/port along with a password. IPv6 addresses should be entered with square brackets, like so: <code>[2001::1]</code>."))
udp_peers.anonymous = true
udp_peers.addremove = true
udp_peers.template = "cbi/tblsection"
udp_peers:option(Value, "user", translate("User/Name")).datatype = "string"
udp_interface = udp_peers:option(Value, "interface", translate("UDP interface"))
local index = 1
for i,section in pairs(cursor:get_all("cjdns")) do
if section[".type"] == "udp_interface" then
udp_interface:value(index, section.address .. ":" .. section.port)
end
end
udp_interface.default = 1
udp_peers:option(Value, "address", translate("IP address"))
udp_peers:option(Value, "port", translate("Port")).datatype = "portrange"
udp_peers:option(Value, "public_key", translate("Public key"))
udp_peers:option(Value, "password", translate("Password"))
-- Ethernet Peers
eth_peers = m:section(TypedSection, "eth_peer", translate("Outgoing Ethernet Peers"),
translate("For peering via local Ethernet networks, the peer handed you their Public Key and MAC address along with a password."))
eth_peers.anonymous = true
eth_peers.addremove = true
eth_peers.template = "cbi/tblsection"
eth_interface = eth_peers:option(Value, "interface", translate("Ethernet interface"))
local index = 1
for i,section in pairs(cursor:get_all("cjdns")) do
if section[".type"] == "eth_interface" then
eth_interface:value(index, section.bind)
end
end
eth_interface.default = 1
eth_peers:option(Value, "address", translate("MAC address")).datatype = "macaddr"
eth_peers:option(Value, "public_key", translate("Public key"))
eth_peers:option(Value, "password", translate("Password"))
return m

View File

@ -0,0 +1,63 @@
m = Map("cjdns", translate("cjdns"),
translate("Implements an encrypted IPv6 network using public-key \
cryptography for address allocation and a distributed hash table for \
routing. This provides near-zero-configuration networking, and prevents \
many of the security and scalability issues that plague existing \
networks."))
m.on_after_commit = function(self)
os.execute("/etc/init.d/cjdns restart")
end
s = m:section(NamedSection, "cjdns", nil, translate("Settings"))
s.addremove = false
-- Identity
s:tab("identity", translate("Identity"))
node6 = s:taboption("identity", Value, "ipv6", translate("IPv6 address"),
translate("This node's IPv6 address within the cjdns network."))
node6.datatype = "ip6addr"
pbkey = s:taboption("identity", Value, "public_key", translate("Public key"),
translate("Used for packet encryption and authentication."))
pbkey.datatype = "string"
prkey = s:taboption("identity", Value, "private_key", translate("Private key"),
translate("Keep this private. When compromised, generate a new keypair and IPv6."))
prkey.datatype = "string"
-- Admin Interface
s:tab("admin", translate("Admin API"), translate("The Admin API can be used by other applications or services to configure and inspect cjdns' routing and peering.<br/><br/>Documentation: <a href=\"https://github.com/cjdelisle/cjdns/tree/master/admin#cjdns-admin-api\">admin/README.md</a>"))
aip = s:taboption("admin", Value, "admin_address", translate("IP Address"),
translate("IPv6 addresses should be entered like so: <code>[2001::1]</code>."))
apt = s:taboption("admin", Value, "admin_port", translate("Port"))
apt.datatype = "port"
apw = s:taboption("admin", Value, "admin_password", translate("Password"))
apw.datatype = "string"
-- UDP Interfaces
udp_interfaces = m:section(TypedSection, "udp_interface", translate("UDP Interfaces"),
translate("These interfaces allow peering via public IP networks, such as the Internet, or many community-operated wireless networks. IPv6 addresses should be entered with square brackets, like so: <code>[2001::1]</code>."))
udp_interfaces.anonymous = true
udp_interfaces.addremove = true
udp_interfaces.template = "cbi/tblsection"
udp_address = udp_interfaces:option(Value, "address", translate("IP Address"))
udp_address.placeholder = "0.0.0.0"
udp_interfaces:option(Value, "port", translate("Port")).datatype = "portrange"
-- Ethernet Interfaces
eth_interfaces = m:section(TypedSection, "eth_interface", translate("Ethernet Interfaces"),
translate("These interfaces allow peering via local Ethernet networks, such as home or office networks, or phone tethering. If an interface name is set to \"all\" each available device will be used."))
eth_interfaces.anonymous = true
eth_interfaces.addremove = true
eth_interfaces.template = "cbi/tblsection"
eth_bind = eth_interfaces:option(Value, "bind", translate("Network Interface"))
eth_bind.placeholder = "br-lan"
eth_beacon = eth_interfaces:option(Value, "beacon", translate("Beacon Mode"))
eth_beacon:value(0, translate("0 -- Disabled"))
eth_beacon:value(1, translate("1 -- Accept beacons"))
eth_beacon:value(2, translate("2 -- Accept and send beacons"))
eth_beacon.default = 2
eth_beacon.datatype = "integer(range(0,2))"
return m

View File

@ -0,0 +1 @@
<%+cjdns/status%>

View File

@ -0,0 +1,116 @@
<script type="text/javascript">//<![CDATA[
var peersURI = '<%=luci.dispatcher.build_url("admin", "services", "cjdns", "peers")%>';
var updatePeers = function(x, peers) {
var table = document.getElementById('cjdns-peerings');
while (table.rows.length > 1) {
table.deleteRow(1);
}
if ((peers) && ((peers.err) || (typeof peers.length === 'undefined'))) {
var errpeer = (peers.err)
? 'Socket Error: unable to connect to Admin API'
: 'No active peers';
var row = table.insertRow(-1);
row.className = 'cbi-section-table-row';
var cell = row.insertCell(-1);
cell.colSpan = 7;
cell.textContent = errpeer;
return;
};
peers.forEach(function(peer, i) {
if (peer.user == null) {
var user = '';
} else if (peer.user == 'Local Peers') {
var user = 'beacon';
} else {
var user = peer.user;
}
if (peer.isIncoming === 0) {
var interface = 'outgoing';
} else {
var interface = 'incoming';
}
var status = interface + ', ' + peer.state.toLowerCase();
if (peer.version === 0) {
var version = '-';
} else {
var version = 'v' + peer.version;
}
var rxtx = lbbytes(peer.bytesIn) + ' / ' + lbbytes(peer.bytesOut);
var row = table.insertRow(-1);
row.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
row.insertCell(-1).textContent = user;
row.insertCell(-1).textContent = peer.ipv6;
row.insertCell(-1).textContent = status;
row.insertCell(-1).textContent = version;
row.insertCell(-1).textContent = rxtx;
var latencyCell = row.insertCell(-1);
latencyCell.textContent = 'waiting';
var pingURI = '<%=luci.dispatcher.build_url("admin", "services", "cjdns", "ping")%>';
var timeout = 2000;
XHR.get(pingURI, { label: peer.switchLabel, timeout: timeout }, function(x, pong) {
var pongrsp = ((pong.err == "ai:recv > timeout") || (pong == "undefined") || (pong.ms >= timeout))
? '> ' + timeout + ' ms'
: pong.ms + ' ms';
latencyCell.textContent = pongrsp;
})
});
};
XHR.get(peersURI, null, updatePeers);
XHR.poll(5, peersURI, null, updatePeers);
//]]></script>
<script type="text/javascript">
<%# Author: [GitHub/75lb] -%>
//<![CDATA[
function lbbytes (bytes){
var kilobyte = 1024,
megabyte = kilobyte * 1024,
gigabyte = megabyte * 1024,
terabyte = gigabyte * 1024;
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + " B";
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(2) + " KB";
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(2) + " MB";
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(2) + " GB";
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(2) + " TB";
} else {
return bytes + " B";
}
};
//]]>
</script>
<fieldset class="cbi-section">
<legend>Active cjdns peers</legend>
<table class="cbi-section-table" id="cjdns-peerings">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell">User/Name</th>
<th class="cbi-section-table-cell">IPv6</th>
<th class="cbi-section-table-cell">Status</th>
<th class="cbi-section-table-cell">Version</th>
<th class="cbi-section-table-cell">Rx / Tx</th>
<th class="cbi-section-table-cell">Latency</th>
</tr>
<tr class="cbi-section-table-row">
<td colspan="7">Querying Admin API</td>
</tr>
</table>
</fieldset>

View File

@ -0,0 +1,35 @@
<%+cbi/valueheader%>
<input type="<%=self.password and 'password" class="cbi-input-password' or 'text" class="cbi-input-text' %>" onchange="cbi_d_update(this.id)"<%=
attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) ..
ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder")
%> style="width: auto" />
<% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %>
<% if #self.keylist > 0 or self.datatype then -%>
<script type="text/javascript">//<![CDATA[
<% if #self.keylist > 0 then -%>
cbi_combobox_init('<%=cbid%>', {
<%-
for i, k in ipairs(self.keylist) do
-%>
<%-=string.format("%q", k) .. ":" .. string.format("%q", self.vallist[i])-%>
<%-if i<#self.keylist then-%>,<%-end-%>
<%-
end
-%>
}, '<%- if not self.rmempty and not self.optional then -%>
<%-: -- Please choose -- -%>
<%- elseif self.placeholder then -%>
<%-= pcdata(self.placeholder) -%>
<%- end -%>', '
<%- if self.combobox_manual then -%>
<%-=self.combobox_manual-%>
<%- else -%>
<%-: -- custom -- -%>
<%- end -%>');
<%- end %>
<% if self.datatype then -%>
cbi_validate_field('<%=cbid%>', <%=tostring((self.optional or self.rmempty) == true)%>, '<%=self.datatype:gsub("'", "\\'")%>');
<%- end %>
//]]></script>
<% end -%>
<%+cbi/valuefooter%>