--[[ Copyright (c) 2012-2014 Kaarle Ritvanen See LICENSE file for license details --]] local M = require('aconf.model') local posix = require('posix') local Host = M.new() Host.address = M.net.IPAddress{required=true, addr='ipaddr'} Host.canonical = M.String{required=true, ui_name='Canonical name'} Host.alias = M.Set{ type=M.String, addr='alias/#', ui_name='Aliases', ui_member='Alias', detail=false } 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, choice={ {'dhcp', 'DHCP'}, {'loopback', enabled=false}, {'unconfigured', be_value='manual'}, 'static' }, default='unconfigured' } 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}, {'unconfigured', be_value='manual'}, 'static' }, default='unconfigured' } 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 'unconfigured' end end end function Interface:is_removable() return self.class == 'logical' end function Interface:auto_set() for _, set in 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(iface) return iface:auto_set() and true or false end, store=function(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(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(table.unpack(addr))) then return 'logical' end end return 'physical' end, choice={'loopback', 'logical', 'physical'} } Interface.type = M.String{ condition={class='logical'}, compute=function(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(iface) return not iface:auto_vlan_tag() end, required=true, choice={'bond', 'bridge', {'vlan', 'VLAN'}} } Interface.status = M.String{ visible=false, compute=function(obj) if obj.class == 'loopback' then return 'attached' end for _, iface in 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 ~= 'unconfigured' 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(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'} 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 = 'unconfigured' end end end end end