2 # -*- coding: utf-8 -*- vim:fileencoding=utf-8:
3 # vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
4 #Copyright © 2011-2013 Greek Research and Technology Network (GRNET S.A.)
6 #Developed by Leonidas Poulopoulos (leopoul-at-noc-dot-grnet-dot-gr),
9 #Permission to use, copy, modify, and/or distribute this software for any
10 #purpose with or without fee is hereby granted, provided that the above
11 #copyright notice and this permission notice appear in all copies.
13 #THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
14 #TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
15 #FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16 #CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
17 #DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18 #ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
24 from django.db import models
25 from django.conf import settings
26 from django.contrib.auth.models import User
27 from django.utils.translation import ugettext_lazy as _
28 from utils import proxy as PR
32 from time import sleep
35 from flowspy.utils.randomizer import id_generator as id_gen
37 from flowspec.tasks import *
39 FORMAT = '%(asctime)s %(levelname)s: %(message)s'
40 logging.basicConfig(format=FORMAT)
41 logger = logging.getLogger(__name__)
42 logger.setLevel(logging.DEBUG)
46 ("dont-fragment", "Don't fragment"),
47 ("first-fragment", "First fragment"),
48 ("is-fragment", "Is fragment"),
49 ("last-fragment", "Last fragment"),
50 ("not-a-fragment", "Not a fragment")
55 ("discard", "Discard"),
56 ("community", "Community"),
57 ("next-term", "Next term"),
58 ("routing-instance", "Routing Instance"),
59 ("rate-limit", "Rate limit"),
83 ("EXPIRED", "EXPIRED"),
84 ("PENDING", "PENDING"),
85 ("OUTOFSYNC", "OUTOFSYNC"),
86 ("INACTIVE", "INACTIVE"),
87 ("ADMININACTIVE", "ADMININACTIVE"),
91 def days_offset(): return datetime.date.today() + datetime.timedelta(days = settings.EXPIRATION_DAYS_OFFSET)
93 class MatchPort(models.Model):
94 port = models.CharField(max_length=24, unique=True)
95 def __unicode__(self):
98 db_table = u'match_port'
100 class MatchDscp(models.Model):
101 dscp = models.CharField(max_length=24)
102 def __unicode__(self):
105 db_table = u'match_dscp'
107 class MatchProtocol(models.Model):
108 protocol = models.CharField(max_length=24, unique=True)
109 def __unicode__(self):
112 db_table = u'match_protocol'
114 class FragmentType(models.Model):
115 fragmenttype = models.CharField(max_length=20, choices=FRAGMENT_CODES, verbose_name="Fragment Type")
117 def __unicode__(self):
118 return "%s" %(self.fragmenttype)
121 class ThenAction(models.Model):
122 action = models.CharField(max_length=60, choices=THEN_CHOICES, verbose_name="Action")
123 action_value = models.CharField(max_length=255, blank=True, null=True, verbose_name="Action Value")
124 def __unicode__(self):
125 ret = "%s:%s" %(self.action, self.action_value)
126 return ret.rstrip(":")
128 db_table = u'then_action'
129 ordering = ['action', 'action_value']
130 unique_together = ("action", "action_value")
132 class Route(models.Model):
133 name = models.SlugField(max_length=128, verbose_name=_("Name"))
134 applier = models.ForeignKey(User, blank=True, null=True)
135 source = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address"))
136 sourceport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchSourcePort", verbose_name=_("Source Port"))
137 destination = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address"))
138 destinationport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchDestinationPort", verbose_name=_("Destination Port"))
139 port = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchPort", verbose_name=_("Port"))
140 dscp = models.ManyToManyField(MatchDscp, blank=True, null=True, verbose_name="DSCP")
141 fragmenttype = models.ManyToManyField(FragmentType, blank=True, null=True, verbose_name="Fragment Type")
142 icmpcode = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Code")
143 icmptype = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Type")
144 packetlength = models.IntegerField(blank=True, null=True, verbose_name="Packet Length")
145 protocol = models.ManyToManyField(MatchProtocol, blank=True, null=True, verbose_name=_("Protocol"))
146 tcpflag = models.CharField(max_length=128, blank=True, null=True, verbose_name="TCP flag")
147 then = models.ManyToManyField(ThenAction, verbose_name=_("Then"))
148 filed = models.DateTimeField(auto_now_add=True)
149 last_updated = models.DateTimeField(auto_now=True)
150 status = models.CharField(max_length=20, choices=ROUTE_STATES, blank=True, null=True, verbose_name=_("Status"), default="PENDING")
151 # is_online = models.BooleanField(default=False)
152 # is_active = models.BooleanField(default=False)
153 expires = models.DateField(default=days_offset, verbose_name=_("Expires"))
154 response = models.CharField(max_length=512, blank=True, null=True, verbose_name=_("Response"))
155 comments = models.TextField(null=True, blank=True, verbose_name=_("Comments"))
158 def __unicode__(self):
163 verbose_name = "Rule"
164 verbose_name_plural = "Rules"
166 def save(self, *args, **kwargs):
169 self.name = "%s_%s" %(self.name, hash)
170 super(Route, self).save(*args, **kwargs) # Call the "real" save() method.
173 def clean(self, *args, **kwargs):
174 from django.core.exceptions import ValidationError
177 address = IPNetwork(self.destination)
178 self.destination = address.exploded
180 raise ValidationError(_('Invalid network address format at Destination Field'))
183 address = IPNetwork(self.source)
184 self.source = address.exploded
186 raise ValidationError(_('Invalid network address format at Source Field'))
188 def commit_add(self, *args, **kwargs):
189 peer = self.applier.get_profile().peer.domain_name
190 send_message("[%s] Adding rule %s. Please wait..." %(self.applier.username, self.name), peer)
191 response = add.delay(self)
192 logger.info("Got add job id: %s" %response)
194 def commit_edit(self, *args, **kwargs):
195 peer = self.applier.get_profile().peer.domain_name
196 send_message("[%s] Editing rule %s. Please wait..." %(self.applier.username, self.name), peer)
197 response = edit.delay(self)
198 logger.info("Got edit job id: %s" %response)
200 def commit_delete(self, *args, **kwargs):
203 if "reason" in kwargs:
204 reason = kwargs['reason']
205 reason_text = "Reason: %s. " %reason
206 peer = self.applier.get_profile().peer.domain_name
207 send_message("[%s] Suspending rule %s. %sPlease wait..." %(self.applier.username, self.name, reason_text), peer)
208 response = delete.delay(self, reason=reason)
209 logger.info("Got delete job id: %s" %response)
211 def has_expired(self):
212 today = datetime.date.today()
213 if today > self.expires:
217 def check_sync(self):
218 if not self.is_synced():
219 self.status = "OUTOFSYNC"
224 get_device = PR.Retriever()
225 device = get_device.fetch_device()
227 routes = device.routing_options[0].routes
228 except Exception as e:
229 self.status = "EXPIRED"
231 logger.error("No routing options on device. Exception: %s" %e)
234 if route.name == self.name:
236 logger.info('Found a matching rule name')
237 devicematch = route.match
239 assert(self.destination)
240 assert(devicematch['destination'][0])
241 if self.destination == devicematch['destination'][0]:
242 found = found and True
243 logger.info('Found a matching destination')
246 logger.info('Destination fields do not match')
251 assert(devicematch['source'][0])
252 if self.source == devicematch['source'][0]:
253 found = found and True
254 logger.info('Found a matching source')
257 logger.info('Source fields do not match')
262 assert(self.fragmenttype.all())
263 assert(devicematch['fragment'])
264 devitems = devicematch['fragment']
265 dbitems = ["%s"%i for i in self.fragmenttype.all()]
266 intersect = list(set(devitems).intersection(set(dbitems)))
267 if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
268 found = found and True
269 logger.info('Found a matching fragment type')
272 logger.info('Fragment type fields do not match')
277 assert(self.port.all())
278 assert(devicematch['port'])
279 devitems = devicematch['port']
280 dbitems = ["%s"%i for i in self.port.all()]
281 intersect = list(set(devitems).intersection(set(dbitems)))
282 if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
283 found = found and True
284 logger.info('Found a matching port type')
287 logger.info('Port type fields do not match')
292 assert(self.protocol.all())
293 assert(devicematch['protocol'])
294 devitems = devicematch['protocol']
295 dbitems = ["%s"%i for i in self.protocol.all()]
296 intersect = list(set(devitems).intersection(set(dbitems)))
297 if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
298 found = found and True
299 logger.info('Found a matching protocol type')
302 logger.info('Protocol type fields do not match')
307 assert(self.destinationport.all())
308 assert(devicematch['destination-port'])
309 devitems = devicematch['destination-port']
310 dbitems = ["%s"%i for i in self.destinationport.all()]
311 intersect = list(set(devitems).intersection(set(dbitems)))
312 if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
313 found = found and True
314 logger.info('Found a matching destination port type')
317 logger.info('Destination port type fields do not match')
322 assert(self.sourceport.all())
323 assert(devicematch['source-port'])
324 devitems = devicematch['source-port']
325 dbitems = ["%s"%i for i in self.sourceport.all()]
326 intersect = list(set(devitems).intersection(set(dbitems)))
327 if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
328 found = found and True
329 logger.info('Found a matching source port type')
332 logger.info('Source port type fields do not match')
338 # assert(self.fragmenttype)
339 # assert(devicematch['fragment'][0])
340 # if self.fragmenttype == devicematch['fragment'][0]:
341 # found = found and True
342 # logger.info('Found a matching fragment type')
345 # logger.info('Fragment type fields do not match')
349 assert(self.icmpcode)
350 assert(devicematch['icmp-code'][0])
351 if self.icmpcode == devicematch['icmp-code'][0]:
352 found = found and True
353 logger.info('Found a matching icmp code')
356 logger.info('Icmp code fields do not match')
360 assert(self.icmptype)
361 assert(devicematch['icmp-type'][0])
362 if self.icmptype == devicematch['icmp-type'][0]:
363 found = found and True
364 logger.info('Found a matching icmp type')
367 logger.info('Icmp type fields do not match')
370 if found and self.status != "ACTIVE":
371 logger.error('Rule is applied on device but appears as offline')
372 self.status = "ACTIVE"
375 if self.status == "ADMININACTIVE" or self.status == "INACTIVE" or self.status == "EXPIRED":
381 then_statements = self.then.all()
382 for statement in then_statements:
383 if statement.action_value:
384 ret = "%s %s:<strong>%s</strong><br/>" %(ret, statement.action, statement.action_value)
386 ret = "%s %s<br>" %(ret, statement.action)
387 return ret.rstrip(',')
389 get_then.short_description = 'Then statement'
390 get_then.allow_tags = True
395 ret = '%s Dst Addr:<strong>%s</strong> <br/>' %(ret, self.destination)
396 if self.fragmenttype.all():
397 ret = ret + "Fragment Types:<strong>%s</strong> <br/>" %(','.join(["%s"%i for i in self.fragmenttype.all()]))
398 # for fragment in self.fragmenttype.all():
399 # ret = ret + "Fragment Types:<strong>%s</strong> <br/>" %(fragment)
401 ret = "%s ICMP code:<strong>%s</strong><br/>" %(ret, self.icmpcode)
403 ret = "%s ICMP Type:<strong>%s</strong><br/>" %(ret, self.icmptype)
404 if self.packetlength:
405 ret = "%s Packet Length:<strong>%s</strong><br/>" %(ret, self.packetlength)
407 ret = "%s Src Addr:<strong>%s</strong> <br/>" %(ret, self.source)
409 ret = "%s TCP flag:<strong>%s</strong><br/>" %(ret, self.tcpflag)
411 ret = ret + "Ports:<strong>%s</strong> <br/>" %(','.join(["%s"%i for i in self.port.all()]))
412 # for port in self.port.all():
413 # ret = ret + "Port:<strong>%s</strong> <br/>" %(port)
414 if self.protocol.all():
415 ret = ret + "Protocols:<strong>%s</strong> <br/>" %(','.join(["%s"%i for i in self.protocol.all()]))
416 # for protocol in self.protocol.all():
417 # ret = ret + "Protocol:<strong>%s</strong> <br/>" %(protocol)
418 if self.destinationport.all():
419 ret = ret + "DstPorts:<strong>%s</strong> <br/>" %(','.join(["%s"%i for i in self.destinationport.all()]))
420 # for port in self.destinationport.all():
421 # ret = ret + "Dst Port:<strong>%s</strong> <br/>" %(port)
422 if self.sourceport.all():
423 ret = ret + "SrcPorts:<strong>%s</strong> <br/>" %(','.join(["%s"%i for i in self.sourceport.all()]))
424 # for port in self.sourceport.all():
425 # ret = ret +"Src Port:<strong>%s</strong> <br/>" %(port)
427 for dscp in self.dscp.all():
428 ret = ret + "%s Port:<strong>%s</strong> <br/>" %(ret, dscp)
429 return ret.rstrip('<br/>')
431 get_match.short_description = 'Match statement'
432 get_match.allow_tags = True
435 def applier_peer(self):
437 applier_peer = self.applier.get_profile().peer
443 def days_to_expire(self):
444 if self.status not in ['EXPIRED', 'ADMININACTIVE', 'ERROR', 'INACTIVE']:
445 expiration_days = (self.expires - datetime.date.today()).days
446 if expiration_days < settings.EXPIRATION_NOTIFY_DAYS:
447 return "%s" %expiration_days
453 def send_message(msg, user):
454 # username = user.username
456 b = beanstalkc.Connection()
457 b.use(settings.POLLS_TUBE)
458 tube_message = json.dumps({'message': str(msg), 'username':peer})