Commit 43391a3d by Őry Máté

firewall: merge from network-gui

parent a8c081dc
# -*- coding: utf8 -*-
# -*- coding: utf-8 -*-
from django.contrib import admin
from firewall.models import *
from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall,
Domain, Record, Blacklist)
from django import contrib
class RuleInline(contrib.admin.TabularInline):
model = Rule
class RecordInline(contrib.admin.TabularInline):
model = Record
class HostAdmin(admin.ModelAdmin):
list_display = ('hostname', 'vlan', 'ipv4', 'ipv6', 'pub_ipv4', 'mac',
'shared_ip', 'owner', 'description', 'reverse', 'list_groups')
'shared_ip', 'owner', 'description', 'reverse',
'list_groups')
ordering = ('hostname', )
list_filter = ('owner', 'vlan', 'groups')
search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac')
......@@ -26,20 +30,24 @@ class HostAdmin(admin.ModelAdmin):
names = [group.name for group in instance.groups.all()]
return u', '.join(names)
class HostInline(contrib.admin.TabularInline):
model = Host
fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip',
'owner', 'reverse')
class VlanAdmin(admin.ModelAdmin):
list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6',
'description', 'domain', 'snat_ip', )
ordering = ('vid', )
inlines = (RuleInline, )
class RuleAdmin(admin.ModelAdmin):
list_display = ('r_type', 'color_desc', 'owner', 'extra', 'direction',
'accept', 'proto', 'sport', 'dport', 'nat', 'nat_dport', 'used_in')
'accept', 'proto', 'sport', 'dport', 'nat',
'nat_dport', 'used_in')
list_filter = ('r_type', 'vlan', 'owner', 'direction', 'accept',
'proto', 'nat')
......@@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin):
class AliasAdmin(admin.ModelAdmin):
list_display = ('alias', 'host')
class GroupAdmin(admin.ModelAdmin):
list_display = ('name', 'owner', 'description')
inlines = (RuleInline, )
class FirewallAdmin(admin.ModelAdmin):
inlines = (RuleInline, )
class DomainAdmin(admin.ModelAdmin):
list_display = ('name', 'owner')
class RecordAdmin(admin.ModelAdmin):
list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner')
......@@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin):
a = instance.get_data()
return a['name'] if a else None
class BlacklistAdmin(admin.ModelAdmin):
list_display = ('ipv4', 'reason', 'created_at', 'modified_at')
......@@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin)
admin.site.register(Domain, DomainAdmin)
admin.site.register(Record, RecordAdmin)
admin.site.register(Blacklist, BlacklistAdmin)
......@@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules
import re
mac_re = re.compile(r'^([0-9a-fA-F]{2}([:-]?|$)){6}$')
mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')
alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$')
domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
ipv4_re = re.compile('^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
class MACAddressFormField(fields.RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid MAC address.'),
......@@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField):
def __init__(self, *args, **kwargs):
super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs)
class MACAddressField(models.Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 17
super(MACAddressField, self).__init__(*args, **kwargs)
......@@ -35,44 +39,65 @@ class MACAddressField(models.Field):
return super(MACAddressField, self).formfield(**defaults)
add_introspection_rules([], ["firewall\.fields\.MACAddressField"])
def val_alfanum(value):
"""Validate whether the parameter is a valid alphanumeric value."""
if not alfanum_re.match(value):
raise ValidationError(_(u'%s - only letters, numbers, underscores '
'and hyphens are allowed!') % value)
def is_valid_domain(value):
"""Check whether the parameter is a valid domain name."""
return domain_re.match(value) is not None
def val_domain(value):
"""Validate whether the parameter is a valid domin name."""
if not is_valid_domain(value):
raise ValidationError(_(u'%s - invalid domain name') % value)
def is_valid_reverse_domain(value):
"""Check whether the parameter is a valid reverse domain name."""
return reverse_domain_re.match(value) is not None
def val_reverse_domain(value):
"""Validate whether the parameter is a valid reverse domain name."""
if not is_valid_reverse_domain(value):
raise ValidationError(u'%s - invalid reverse domain name' % value)
def is_valid_ipv4_address(value):
"""Check whether the parameter is a valid IPv4 address."""
return ipv4_re.match(value) is not None
def val_ipv4(value):
"""Validate whether the parameter is a valid IPv4 address."""
if not is_valid_ipv4_address(value):
raise ValidationError(_(u'%s - not an IPv4 address') % value)
def val_ipv6(value):
"""Validate whether the parameter is a valid IPv6 address."""
if not is_valid_ipv6_address(value):
raise ValidationError(_(u'%s - not an IPv6 address') % value)
def val_mx(value):
"""Validate whether the parameter is a valid MX address definition.
Expected form is <priority>:<hostname>.
"""
mx = value.split(':', 1)
if not (len(mx) == 2 and mx[0].isdigit() and
domain_re.match(mx[1])):
raise ValidationError(_("Bad MX address format. "
"Should be: <priority>:<hostname>"))
def ipv4_2_ipv6(ipv4):
"""Convert IPv4 address string to IPv6 address string."""
val_ipv4(ipv4)
......
from django.contrib import auth
from firewall import models
import os
import django.conf
import subprocess
import re
import json
from datetime import datetime, timedelta
from django.db.models import Q
settings = django.conf.settings.FIREWALL_SETTINGS
class Firewall:
IPV6=False
IPV6 = False
RULES = None
RULES_NAT = []
vlans = None
dmz = None
pub = None
hosts = None
fw = None
......@@ -36,7 +34,6 @@ class Firewall:
retval = '-p %s ' % rule.proto
return retval
def iptables(self, s):
"""Append rule to filter table."""
self.RULES.append(s)
......@@ -71,12 +68,13 @@ class Firewall:
action = 'LOG_DROP'
if rule.direction == '1': # going TO host
self.iptables('-A %s_%s -d %s %s %s -g %s' % (vlan,
host.vlan, ipaddr, dport_sport, rule.extra, action))
self.iptables('-A %s_%s -d %s %s %s -g %s' %
(vlan, host.vlan, ipaddr, dport_sport,
rule.extra, action))
else:
self.iptables('-A %s_%s -s %s %s %s -g %s' % (host.vlan,
vlan, ipaddr, dport_sport, rule.extra, action))
self.iptables('-A %s_%s -s %s %s %s -g %s' %
(host.vlan, vlan, ipaddr, dport_sport,
rule.extra, action))
def fw2vlan(self, rule):
if not rule.foreign_network:
......@@ -110,12 +108,12 @@ class Firewall:
action = 'LOG_DROP'
if rule.direction == '1': # going TO host
self.iptables('-A %s_%s %s %s -g %s' % (vlan, l_vlan,
dport_sport, rule.extra, action))
self.iptables('-A %s_%s %s %s -g %s' %
(vlan, l_vlan, dport_sport, rule.extra, action))
else:
self.iptables('-A %s_%s %s %s -g %s' % (l_vlan, vlan,
dport_sport, rule.extra, action))
dport_sport,
rule.extra, action))
def prerun(self):
self.iptables('*filter')
......@@ -138,7 +136,8 @@ class Firewall:
self.iptables('-N PUB_OUT')
self.iptables('-A FORWARD -m set --match-set blacklist src,dst -j DROP')
self.iptables('-A FORWARD -m set --match-set blacklist src,dst '
'-j DROP')
self.iptables('-A FORWARD -m state --state INVALID -g LOG_DROP')
self.iptables('-A FORWARD -m state --state ESTABLISHED,RELATED '
'-j ACCEPT')
......@@ -156,7 +155,6 @@ class Firewall:
self.iptables('-A OUTPUT -m state --state ESTABLISHED,RELATED '
'-j ACCEPT')
def postrun(self):
self.iptables('-A PUB_OUT -s 152.66.243.160/27 -p tcp --dport 25 '
'-j LOG_ACC')
......@@ -172,9 +170,6 @@ class Firewall:
self.iptables('-A OUTPUT -g LOG_DROP')
self.iptables('COMMIT')
def ipt_nat(self):
self.iptablesnat('*nat')
self.iptablesnat(':PREROUTING ACCEPT [0:0]')
......@@ -188,34 +183,37 @@ class Firewall:
dport_sport = self.dportsport(rule, False)
if host.vlan.snat_ip:
self.iptablesnat('-A PREROUTING -d %s %s %s -j DNAT '
'--to-destination %s:%s' % (host.pub_ipv4,
dport_sport, rule.extra, host.ipv4,
rule.nat_dport))
'--to-destination %s:%s' %
(host.pub_ipv4, dport_sport, rule.extra,
host.ipv4, rule.nat_dport))
# rules for machines with dedicated public IP
for host in self.hosts.exclude(shared_ip=True):
if host.pub_ipv4:
self.iptablesnat('-A PREROUTING -d %s -j DNAT '
'--to-destination %s' % (host.pub_ipv4, host.ipv4))
'--to-destination %s' %
(host.pub_ipv4, host.ipv4))
self.iptablesnat('-A POSTROUTING -s %s -j SNAT '
'--to-source %s' % (host.ipv4, host.pub_ipv4))
'--to-source %s' %
(host.ipv4, host.pub_ipv4))
# default NAT rules for VLANs
for s_vlan in self.vlans:
if s_vlan.snat_ip:
for d_vlan in s_vlan.snat_to.all():
self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT '
'--to-source %s' % (s_vlan.net_ipv4(),
d_vlan.interface, s_vlan.snat_ip))
'--to-source %s' %
(s_vlan.net_ipv4(), d_vlan.interface,
s_vlan.snat_ip))
# hard-wired rules
self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0003 -j SNAT '
'--to-source 10.3.255.254') # man elerheto legyen
self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0008 -j SNAT '
self.iptablesnat('-A POSTROUTING -o vlan0008 -j SNAT '
'--to-source 10.0.0.247') # wolf network for printing
self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -o vlan0002 -j SNAT '
'--to-source %s' % self.pub.ipv4) # kulonben nemmegy a du
self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -p udp --dport 53 '
'-o vlan0002 -j SNAT ''--to-source %s' %
self.pub.ipv4) # kulonben nem megy a dns man-ban
self.iptablesnat('COMMIT')
......@@ -235,7 +233,8 @@ class Firewall:
for d_vlan in self.vlans:
self.iptables('-N %s_%s' % (s_vlan, d_vlan))
self.iptables('-A FORWARD -i %s -o %s -g %s_%s' %
(s_vlan.interface, d_vlan.interface, s_vlan, d_vlan))
(s_vlan.interface, d_vlan.interface, s_vlan,
d_vlan))
# hosts' rules
for i_vlan in self.vlans:
......@@ -264,12 +263,11 @@ class Firewall:
self.RULES = [x.replace('icmp', 'icmpv6') for x in self.RULES]
def __init__(self, IPV6=False):
self.RULES=[]
self.RULES_NAT=[]
self.RULES = []
self.RULES_NAT = []
self.IPV6 = IPV6
self.vlans = models.Vlan.objects.all()
self.hosts = models.Host.objects.all()
self.dmz = models.Vlan.objects.get(name='DMZ')
self.pub = models.Vlan.objects.get(name='PUB')
self.fw = models.Firewall.objects.all()
self.ipt_filter()
......@@ -279,21 +277,23 @@ class Firewall:
def reload(self):
if self.IPV6:
process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
'/usr/bin/sudo', '/sbin/ip6tables-restore', '-c'],
'/usr/bin/sudo',
'/sbin/ip6tables-restore', '-c'],
shell=False, stdin=subprocess.PIPE)
process.communicate('\n'.join(self.RULES) + '\n')
else:
process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
'/usr/bin/sudo', '/sbin/iptables-restore', '-c'],
'/usr/bin/sudo',
'/sbin/iptables-restore', '-c'],
shell=False, stdin=subprocess.PIPE)
process.communicate('\n'.join(self.RULES) + '\n' +
'\n'.join(self.RULES_NAT) + '\n')
def get(self):
if self.IPV6:
return { 'filter': self.RULES, }
return {'filter': self.RULES, }
else:
return { 'filter': self.RULES, 'nat': self.RULES_NAT }
return {'filter': self.RULES, 'nat': self.RULES_NAT}
def show(self):
if self.IPV6:
......@@ -302,9 +302,12 @@ class Firewall:
return ('\n'.join(self.RULES) + '\n' +
'\n'.join(self.RULES_NAT) + '\n')
def ipset():
week = datetime.now()-timedelta(days=2)
return models.Blacklist.objects.filter(Q(type='tempban', modified_at__gte=week) | Q(type='permban')).values('ipv4', 'reason')
week = datetime.now() - timedelta(days=2)
filter_ban = (Q(type='tempban', modified_at__gte=week) |
Q(type='permban')).values('ipv4', 'reason')
return models.Blacklist.objects.filter(filter_ban)
def ipv6_to_octal(ipv6):
......@@ -321,6 +324,7 @@ def ipv6_to_octal(ipv6):
octets.append(int(part[2:], 16))
return '\\' + '\\'.join(['%03o' % x for x in octets])
def ipv4_to_arpa(ipv4, cname=False):
m2 = re.search(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$', ipv4)
if cname:
......@@ -330,6 +334,7 @@ def ipv4_to_arpa(ipv4, cname=False):
return ('%s.%s.%s.%s.in-addr.arpa' %
(m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
def ipv6_to_arpa(ipv6):
while len(ipv6.split(':')) < 8:
ipv6 = ipv6.replace('::', ':::')
......@@ -357,11 +362,11 @@ def ipv6_to_arpa(ipv6):
def dns():
vlans = models.Vlan.objects.all()
regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
# regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
DNS = []
for i_vlan in vlans:
m = regex.search(i_vlan.net4)
# m = regex.search(i_vlan.net4)
rev = i_vlan.reverse_domain
for i_host in i_vlan.host_set.all():
......@@ -374,8 +379,8 @@ def dns():
# ipv4
if i_host.ipv4:
DNS.append("^%s:%s:%s" % (
(rev % { 'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
'd': int(i[3]) }),
(rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
'd': int(i[3])}),
reverse, models.settings['dns_ttl']))
# ipv6
......@@ -384,16 +389,17 @@ def dns():
reverse, models.settings['dns_ttl']))
for domain in models.Domain.objects.all():
DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" % (domain.name,
settings['dns_hostname'], models.settings['dns_ttl']))
DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" %
(domain.name, settings['dns_hostname'],
models.settings['dns_ttl']))
for r in models.Record.objects.all():
d = r.get_data()
if d['type'] == 'A':
DNS.append("+%s:%s:%s" % (d['name'], d['address'], d['ttl']))
elif d['type'] == 'AAAA':
DNS.append(":%s:28:%s:%s" % (d['name'],
ipv6_to_octal(d['address']), d['ttl']))
DNS.append(":%s:28:%s:%s" %
(d['name'], ipv6_to_octal(d['address']), d['ttl']))
elif d['type'] == 'NS':
DNS.append("&%s::%s:%s" % (d['name'], d['address'], d['ttl']))
elif d['type'] == 'CNAME':
......@@ -408,8 +414,9 @@ def dns():
return DNS
process = subprocess.Popen(['/usr/bin/ssh', 'tinydns@%s' %
settings['dns_hostname']], shell=False, stdin=subprocess.PIPE)
process.communicate("\n".join(DNS)+"\n")
settings['dns_hostname']],
shell=False, stdin=subprocess.PIPE)
process.communicate("\n".join(DNS) + "\n")
# print "\n".join(DNS)+"\n"
......@@ -422,6 +429,7 @@ def prefix_to_mask(prefix):
t[i] = 256 - (2 ** ((i + 1) * 8 - prefix))
return ".".join([str(i) for i in t])
def dhcp():
vlans = models.Vlan.objects.all()
regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
......@@ -434,7 +442,7 @@ def dhcp():
if(i_vlan.dhcp_pool):
m = regex.search(i_vlan.dhcp_pool)
if(m or i_vlan.dhcp_pool == "manual"):
DHCP.append ('''
DHCP.append('''
# %(name)s - %(interface)s
subnet %(net)s netmask %(netmask)s {
%(extra)s;
......@@ -461,7 +469,7 @@ def dhcp():
})
for i_host in i_vlan.host_set.all():
DHCP.append ('''
DHCP.append('''
host %(hostname)s {
hardware ethernet %(mac)s;
fixed-address %(ipv4)s;
......@@ -474,22 +482,7 @@ def dhcp():
return DHCP
process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
'cat > /tools/dhcp3/dhcpd.conf.generated;'
'sudo /etc/init.d/isc-dhcp-server restart'], shell=False,
stdin=subprocess.PIPE)
'sudo /etc/init.d/isc-dhcp-server restart'],
shell=False, stdin=subprocess.PIPE)
# print "\n".join(DHCP)+"\n"
process.communicate("\n".join(DHCP)+"\n")
'''
i=2
for mac, name, ipend in [("18:a9:05:64:19:aa", "mega6", 16), ("00:1e:0b:e9:79:1e", "blade1", 21), ("00:22:64:9c:fd:34", "blade2", 22), ("00:1e:0b:ec:65:46", "blade3", 23), ("b4:b5:2f:61:d2:5a", "cloud-man", 1)]:
h1 = models.Host(hostname= name, vlan=models.Vlan.objects.get(vid=3), mac=mac, ipv4="10.3.1.%d" % ipend, ipv6="2001:738:2001:4031:3:1:%d:0" % ipend, owner=auth.models.User.objects.get(username="bd"))
try:
h1.save()
h1.groups.add(models.Group.objects.get(name="netezhet manbol"))
h1.save()
# i = i + 1
except:
print "nemok %s" % name
'''
process.communicate("\n".join(DHCP) + "\n")
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-08-21 12:29+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: fields.py:19
msgid "Enter a valid MAC address."
msgstr ""
#: fields.py:46
#, python-format
msgid "%s - only letters, numbers, underscores and hyphens are allowed!"
msgstr ""
#: fields.py:58
#, python-format
msgid "%s - invalid domain name"
msgstr ""
#: fields.py:80
#, python-format
msgid "%s - not an IPv4 address"
msgstr "%s - nem egy IPv4 cím"
#: fields.py:86
#, python-format
msgid "%s - not an IPv6 address"
msgstr ""
#: fields.py:97
msgid "Bad MX address format. Should be: <priority>:<hostname>"
msgstr ""
#: models.py:33
msgid "direction"
msgstr ""
#: models.py:34
msgid "If the rule matches egress or ingress packets."
msgstr ""
#: models.py:36 models.py:222 models.py:284 models.py:307 models.py:366
#: models.py:591 models.py:615
msgid "description"
msgstr ""
#: models.py:37
msgid "Why is the rule needed, or how does it work."
msgstr ""
#: models.py:40
msgid "foreign network"
msgstr ""
#: models.py:41
msgid ""
"The group of vlans the matching packet goes to (direction out) or from (in)."
msgstr ""
#: models.py:45
msgid "dest. port"
msgstr ""
#: models.py:47
msgid "Destination port number of packets that match."
msgstr ""
#: models.py:49
msgid "source port"
msgstr ""
#: models.py:51
msgid "Source port number of packets that match."
msgstr ""
#: models.py:53
msgid "protocol"
msgstr ""
#: models.py:54
msgid "Protocol of packets that match."
msgstr ""
#: models.py:55
msgid "extra arguments"
msgstr ""
#: models.py:56
msgid "Additional arguments passed literally to the iptables-rule."
msgstr ""
#: models.py:58
msgid "accept"
msgstr ""
#: models.py:59
msgid "Accept the matching packets (or deny if not checked)."
msgstr ""
#: models.py:62 models.py:253 models.py:287 models.py:310 models.py:377
#: models.py:585 models.py:614
msgid "owner"
msgstr ""
#: models.py:63
msgid "The user responsible for this rule."
msgstr ""
#: models.py:65
msgid "Rule type"
msgstr ""
#: models.py:67
msgid "The type of entity the rule belongs to."
msgstr ""
#: models.py:69
msgid "NAT"
msgstr ""
#: models.py:70
msgid "If network address translation shoud be done."
msgstr ""
#: models.py:74
msgid "Rewrite destination port number to."
msgstr ""
#: models.py:79 models.py:251 models.py:289 models.py:312 models.py:385
msgid "created at"
msgstr ""
#: models.py:82 models.py:255 models.py:291 models.py:314 models.py:387
msgid "modified at"
msgstr ""
#: models.py:85 models.py:374
msgid "vlan"
msgstr ""
#: models.py:86
msgid "Vlan the rule applies to (if type is vlan)."
msgstr ""
#: models.py:90
msgid "vlan group"
msgstr ""
#: models.py:91
msgid "Group of vlans the rule applies to (if type is vlan)."
msgstr ""
#: models.py:94 models.py:608 models.py:730
msgid "host"
msgstr ""
#: models.py:95
msgid "Host the rule applies to (if type is host)."
msgstr ""
#: models.py:98
msgid "host group"
msgstr ""
#: models.py:99
msgid "Group of hosts the rule applies to (if type is host)."
msgstr ""
#: models.py:102
msgid "firewall"
msgstr ""
#: models.py:103
msgid "Firewall the rule applies to (if type is firewall)."
msgstr ""
#: models.py:115
msgid "Only one field can be selected."
msgstr ""
#: models.py:136
msgid "rule"
msgstr ""
#: models.py:137
msgid "rules"
msgstr ""
#: models.py:163
msgid "VID"
msgstr ""
#: models.py:164
msgid "The vlan ID of the subnet."
msgstr ""
#: models.py:169
msgid "Name"
msgstr ""
#: models.py:170
msgid "The short name of the subnet."
msgstr ""
#: models.py:173
msgid "IPv4 prefix length"
msgstr ""
#: models.py:174
msgid "The prefix length of the IPv4 subnet."
msgstr ""
#: models.py:176
msgid "IPv6 prefix length"
msgstr ""
#: models.py:177
msgid "The prefix length of the IPv6 subnet."
msgstr ""
#: models.py:179
msgid "interface"
msgstr ""
#: models.py:180
msgid ""
"The name of network interface the gateway should serve this network on. For "
"example vlan0004 or eth2."
msgstr ""
#: models.py:184