diff options
Diffstat (limited to 'acf2/modules/network.lua')
-rw-r--r-- | acf2/modules/network.lua | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/acf2/modules/network.lua b/acf2/modules/network.lua new file mode 100644 index 0000000..32b9d20 --- /dev/null +++ b/acf2/modules/network.lua @@ -0,0 +1,270 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = require('acf2.model') + +local posix = require('posix') + + +local Host = M.new() +Host.address = M.net.IPAddress{addr='ipaddr'} +Host.canonical = M.String{ui_name='Canonical name'} +Host.alias = M.Set{ + type=M.String, addr='alias/#', ui_name='Aliases', ui_member='Alias' +} + +local Resolv = M.new() +Resolv.servers = M.List{ + type=M.net.IPAddress, addr='nameserver/#', widget='inline' +} +Resolv.search_domains = M.List{ + type=M.String, addr='search/domain/#', widget='inline' +} + + +local iface_aug_addr = '/augeas/etc/network/interfaces' +local iface_sys_dir = '/sys/class/net' +local vlan_pattern = '^(.+)%.(%d+)$' + +for _, trigger in ipairs{ + {phase='pre', action='stop'}, {phase='post', action='start'} +} do + M.trigger( + trigger.phase, + iface_aug_addr, + function() os.execute('rc-service networking '..trigger.action) end + ) +end + +M.defer(iface_aug_addr) + + +-- TODO allow multiple addresses of same family + +local IPv4 = M.new() +IPv4.method = M.String{ + required=true, + default='manual', + choice={{'dhcp', 'DHCP'}, {'loopback', enabled=false}, 'manual', 'static'} +} +IPv4.address = M.net.IPv4Address{ + condition={method='static'}, required=true, cidr=true, mask_addr='netmask' +} +IPv4.gateway = M.net.IPv4Address{condition={method='static'}} + + +local IPv6 = M.new() +IPv6.method = M.String{ + required=true, + choice={{'loopback', enabled=false}, 'manual', 'static'}, + default='manual' +} +IPv6.address = M.net.IPv6Address{ + condition={method='static'}, required=true, cidr=true, mask_addr='netmask' +} +IPv6.gateway = M.net.IPv6Address{condition={method='static'}} + + + +local Interface = M.new() + +function Interface:validate() + if self.status == 'attached' then + for _, version in ipairs{4, 6} do + self['ipv'..version].method = + self.class == 'loopback' and 'loopback' or 'manual' + end + end +end + +function Interface:is_removable() return self.class == 'logical' end + +function Interface:auto_set() + for _, set in M.node.ipairs(self:fetch('../../enabled-ifaces')) do + if M.node.contains(set, self) then return set end + end +end + +function Interface:auto_vlan_tag() + local name = M.node.name(self) + local _, tag = name:match(vlan_pattern) + if tag then return tag end + return name:match('^vlan(%d+)$') +end + +Interface.enabled = M.Boolean{ + compute=function(self, iface) return iface:auto_set() and true or false end, + store=function(self, iface, value) + local set = iface:auto_set() + if value and not set then + M.node.insert(iface:fetch('../../enabled-ifaces/1'), iface) + elseif not value and set then set[iface] = nil end + end +} + +Interface.class = M.String{ + compute=function(self, iface, txn) + local name = M.node.name(iface) + if name == 'lo' then return 'loopback' end + + local saddr = M.path.rawjoin('/files', iface_sys_dir, name, '.') + if not txn:get(saddr) then return 'logical' end + + for _, addr in ipairs{ + {saddr, 'bonding'}, + {saddr, 'bridge'}, + {'/files/proc/net/vlan', name} + } do + if txn:get(M.path.join(unpack(addr))) then + return 'logical' + end + end + return 'physical' + end, + choice={'loopback', 'logical', 'physical'} +} + +Interface.type = M.String{ + condition={class='logical'}, + compute=function(self, iface) + if #iface.slaves > 0 then return 'bond' end + if #iface.ports > 0 then return 'bridge' end + if iface.vlan_tag then return 'vlan' end + end, + editable=function(self, iface) return not iface:auto_vlan_tag() end, + required=true, + choice={'bond', 'bridge', {'vlan', 'VLAN'}} +} + +Interface.status = M.String{ + visible=false, + compute=function(self, obj) + if obj.class == 'loopback' then return 'attached' end + + for _, iface in M.node.pairs(M.node.parent(obj)) do + if ( + iface.type == 'bond' and M.node.contains(iface.slaves, obj) + ) or ( + iface.type == 'bridge' and M.node.contains(iface.ports, obj) + ) then + return 'attached' + end + + if iface.type == 'vlan' and iface.trunk == obj then + return 'configured' + end + end + + for _, version in ipairs{4, 6} do + if obj['ipv'..version].method ~= 'manual' then + return 'configured' + end + end + + return 'detached' + end +} + +Interface.slaves = M.Set{ + condition={type='bond'}, + type=M.Reference{scope='../..', filter={status='detached'}}, + required=true, + addr='@family/link/@method/none/bond-slaves' +} +Interface.ports = M.Set{ + condition={type='bridge'}, + type=M.Reference{scope='../..', filter={status='detached'}}, + required=true, + addr='@family/link/@method/none/bridge-ports' +} +-- TODO do not allow VLAN creation for non-existent interfaces +Interface.trunk = M.Reference{ + condition={type='vlan'}, + compute=function(self, iface) + local trunk = M.node.name(iface):match(vlan_pattern) + if trunk and iface:fetch('..')[trunk] then return trunk end + end, + required=true, + scope='..', + filter={status={'detached', 'configured'}}, + addr='@family/link/@method/none/vlan-raw-device' +} +-- TODO ensure that (trunk, tag) is unique +Interface.vlan_tag = M.Integer{ + condition={type='vlan'}, + compute='auto_vlan_tag', + required=true, + min=0, + max=4095, + addr='@family/link/@method/none/vlan-id', + ui_name='VLAN tag' +} + +Interface.ipv4 = M.Model{ + model=IPv4, + condition={status={'detached', 'configured'}}, + create=true, + addr='@family/inet', + ui_name='IPv4 configuration', + widget='inline' +} +Interface.ipv6 = M.Model{ + model=IPv6, + condition={status={'detached', 'configured'}}, + create=true, + addr='@family/inet6', + ui_name='IPv6 configuration', + widget='inline' +} + +Interface.stats = M.Collection{ + type=M.Number{editable=false}, + editable=false, + addr=function(path) + return M.path.join( + '/files/sys/class/net', M.path.name(path), 'statistics' + ) + end, + ui_name='Statistics', + ui_member='', + widget='inline' +} + + +local Net = M.new() +Net.host_name = M.String{addr='/augeas/etc/hostname/hostname'} +Net.hosts = M.List{type=Host, addr='/augeas/etc/hosts', layout='tabular'} +Net.resolver = M.Model{ + model=Resolv, addr='/augeas/etc/resolv.conf', ui_name='DNS resolver' +} + +Net.enabled_ifaces = M.List{ + type=M.Set{type=M.Reference{scope='../../interfaces', on_delete='set-null'}}, + visible=false, + addr=iface_aug_addr..'/auto/#' +} +Net.interfaces = M.Collection{ + type=Interface, addr=iface_aug_addr..'/iface/@', widget='inline' +} + +M.register('net', Net, {ui_name='Network'}) +M.permission.defaults('/net') + +return function(txn) + local ifaces = txn:fetch('/net/interfaces') + for _, name in ipairs(posix.dir(iface_sys_dir)) do + if not ifaces[name] and posix.stat( + M.path.join(iface_sys_dir, name), 'type' + ) == 'link' then + ifaces[name] = {} + if ifaces[name].class == 'logical' then ifaces[name] = nil + else + for _, version in ipairs{4, 6} do + ifaces[name]['ipv'..version].method = 'manual' + end + end + end + end + end |