Statistics
| Branch: | Tag: | Revision:

root / flowspec / models.py @ 0492a5b5

History | View | Annotate | Download (18.9 kB)

1
# -*- coding: utf-8 -*- vim:fileencoding=utf-8:
2
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
3

    
4
# Copyright (C) 2010-2014 GRNET S.A.
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19

    
20
from django.db import models
21
from django.conf import settings
22
from django.contrib.auth.models import User
23
from django.utils.translation import ugettext_lazy as _
24
from utils import proxy as PR
25
from ipaddr import *
26
import datetime
27
import logging
28
from time import sleep
29

    
30
import beanstalkc
31
from utils.randomizer import id_generator as id_gen
32

    
33
from tasks import *
34

    
35
def user_unicode_patch(self):
36
    peer = None
37
    try:
38
        peer = self.get_profile().peer.peer_tag
39
    except:
40
        pass
41
    if peer:
42
        return '%s.::.%s' % (self.username, peer)
43
    return self.username
44

    
45
User.__unicode__ = user_unicode_patch
46

    
47

    
48
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
49
logging.basicConfig(format=FORMAT)
50
logger = logging.getLogger(__name__)
51
logger.setLevel(logging.DEBUG)
52

    
53

    
54
FRAGMENT_CODES = (
55
    ("dont-fragment", "Don't fragment"),
56
    ("first-fragment", "First fragment"),
57
    ("is-fragment", "Is fragment"),
58
    ("last-fragment", "Last fragment"),
59
    ("not-a-fragment", "Not a fragment")
60
)
61

    
62
THEN_CHOICES = (
63
    ("accept", "Accept"),
64
    ("discard", "Discard"),
65
    ("community", "Community"),
66
    ("next-term", "Next term"),
67
    ("routing-instance", "Routing Instance"),
68
    ("rate-limit", "Rate limit"),
69
    ("sample", "Sample")                
70
)
71

    
72
MATCH_PROTOCOL = (
73
    ("ah", "ah"),
74
    ("egp", "egp"),
75
    ("esp", "esp"),
76
    ("gre", "gre"),
77
    ("icmp", "icmp"),
78
    ("icmp6", "icmp6"),
79
    ("igmp", "igmp"),
80
    ("ipip", "ipip"),
81
    ("ospf", "ospf"),
82
    ("pim", "pim"),
83
    ("rsvp", "rsvp"),
84
    ("sctp", "sctp"),
85
    ("tcp", "tcp"),
86
    ("udp", "udp"),
87
)
88

    
89
ROUTE_STATES = (
90
    ("ACTIVE", "ACTIVE"),
91
    ("ERROR", "ERROR"),
92
    ("EXPIRED", "EXPIRED"),
93
    ("PENDING", "PENDING"),
94
    ("OUTOFSYNC", "OUTOFSYNC"),
95
    ("INACTIVE", "INACTIVE"),
96
    ("ADMININACTIVE", "ADMININACTIVE"),           
97
)
98

    
99

    
100
def days_offset(): return datetime.date.today() + datetime.timedelta(days = settings.EXPIRATION_DAYS_OFFSET)
101
    
102
class MatchPort(models.Model):
103
    port = models.CharField(max_length=24, unique=True)
104
    def __unicode__(self):
105
        return self.port
106
    class Meta:
107
        db_table = u'match_port'    
108

    
109
class MatchDscp(models.Model):
110
    dscp = models.CharField(max_length=24)
111
    def __unicode__(self):
112
        return self.dscp
113
    class Meta:
114
        db_table = u'match_dscp'
115

    
116
class MatchProtocol(models.Model):
117
    protocol = models.CharField(max_length=24, unique=True)
118
    def __unicode__(self):
119
        return self.protocol
120
    class Meta:
121
        db_table = u'match_protocol'
122

    
123
class FragmentType(models.Model):
124
    fragmenttype = models.CharField(max_length=20, choices=FRAGMENT_CODES, verbose_name="Fragment Type")
125
    
126
    def __unicode__(self):
127
        return "%s" %(self.fragmenttype)
128

    
129

    
130
class ThenAction(models.Model):
131
    action = models.CharField(max_length=60, choices=THEN_CHOICES, verbose_name="Action")
132
    action_value = models.CharField(max_length=255, blank=True, null=True, verbose_name="Action Value")
133
    def __unicode__(self):
134
        ret = "%s:%s" %(self.action, self.action_value)
135
        return ret.rstrip(":")
136
    class Meta:
137
        db_table = u'then_action'
138
        ordering = ['action', 'action_value']
139
        unique_together = ("action", "action_value")
140

    
141
class Route(models.Model):
142
    name = models.SlugField(max_length=128, verbose_name=_("Name"))
143
    applier = models.ForeignKey(User, blank=True, null=True)
144
    source = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Source Address"))
145
    sourceport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchSourcePort", verbose_name=_("Source Port"))
146
    destination = models.CharField(max_length=32, help_text=_("Network address. Use address/CIDR notation"), verbose_name=_("Destination Address"))
147
    destinationport = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchDestinationPort", verbose_name=_("Destination Port"))
148
    port = models.ManyToManyField(MatchPort, blank=True, null=True, related_name="matchPort", verbose_name=_("Port"))
149
    dscp = models.ManyToManyField(MatchDscp, blank=True, null=True, verbose_name="DSCP")
150
    fragmenttype = models.ManyToManyField(FragmentType, blank=True, null=True, verbose_name="Fragment Type")
151
    icmpcode = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Code")
152
    icmptype = models.CharField(max_length=32, blank=True, null=True, verbose_name="ICMP Type")
153
    packetlength = models.IntegerField(blank=True, null=True, verbose_name="Packet Length")
154
    protocol = models.ManyToManyField(MatchProtocol, blank=True, null=True, verbose_name=_("Protocol"))
155
    tcpflag = models.CharField(max_length=128, blank=True, null=True, verbose_name="TCP flag")
156
    then = models.ManyToManyField(ThenAction, verbose_name=_("Then"))
157
    filed = models.DateTimeField(auto_now_add=True)
158
    last_updated = models.DateTimeField(auto_now=True)
159
    status = models.CharField(max_length=20, choices=ROUTE_STATES, blank=True, null=True, verbose_name=_("Status"), default="PENDING")
160
#    is_online = models.BooleanField(default=False)
161
#    is_active = models.BooleanField(default=False)
162
    expires = models.DateField(default=days_offset, verbose_name=_("Expires"))
163
    response = models.CharField(max_length=512, blank=True, null=True, verbose_name=_("Response"))
164
    comments = models.TextField(null=True, blank=True, verbose_name=_("Comments"))
165

    
166
    
167
    def __unicode__(self):
168
        return self.name
169
    
170
    class Meta:
171
        db_table = u'route'
172
        verbose_name = "Rule"
173
        verbose_name_plural = "Rules"
174
    
175
    def save(self, *args, **kwargs):
176
        if not self.pk:
177
            hash = id_gen()
178
            self.name = "%s_%s" %(self.name, hash)
179
        super(Route, self).save(*args, **kwargs) # Call the "real" save() method.
180

    
181
        
182
    def clean(self, *args, **kwargs):
183
        from django.core.exceptions import ValidationError
184
        if self.destination:
185
            try:
186
                address = IPNetwork(self.destination)
187
                self.destination = address.exploded
188
            except Exception:
189
                raise ValidationError(_('Invalid network address format at Destination Field'))
190
        if self.source:
191
            try:
192
                address = IPNetwork(self.source)
193
                self.source = address.exploded
194
            except Exception:
195
                raise ValidationError(_('Invalid network address format at Source Field'))
196
   
197
    def commit_add(self, *args, **kwargs):
198
        peer = self.applier.get_profile().peer.peer_tag
199
        send_message("[%s] Adding rule %s. Please wait..." %(self.applier.username, self.name), peer)
200
        response = add.delay(self)
201
        logger.info("Got add job id: %s" %response)
202
        
203
    def commit_edit(self, *args, **kwargs):
204
        peer = self.applier.get_profile().peer.peer_tag
205
        send_message("[%s] Editing rule %s. Please wait..." %(self.applier.username, self.name), peer)
206
        response = edit.delay(self)
207
        logger.info("Got edit job id: %s" %response)
208

    
209
    def commit_delete(self, *args, **kwargs):
210
        reason_text = ''
211
        reason = ''
212
        if "reason" in kwargs:
213
            reason = kwargs['reason']
214
            reason_text = "Reason: %s. " %reason
215
        peer = self.applier.get_profile().peer.peer_tag
216
        send_message("[%s] Suspending rule %s. %sPlease wait..." %(self.applier.username, self.name, reason_text), peer)
217
        response = delete.delay(self, reason=reason)
218
        logger.info("Got delete job id: %s" %response)
219

    
220
    def has_expired(self):
221
        today = datetime.date.today()
222
        if today > self.expires:
223
            return True
224
        return False
225
    
226
    def check_sync(self):
227
        if not self.is_synced():
228
            self.status = "OUTOFSYNC"
229
            self.save()
230
    
231
    def is_synced(self):
232
        found = False
233
        get_device = PR.Retriever()
234
        device = get_device.fetch_device()
235
        try:
236
            routes = device.routing_options[0].routes
237
        except Exception as e:
238
            self.status = "EXPIRED"
239
            self.save()
240
            logger.error("No routing options on device. Exception: %s" %e)
241
            return True
242
        for route in routes:
243
            if route.name == self.name:
244
                found = True
245
                logger.info('Found a matching rule name')
246
                devicematch = route.match
247
                try:
248
                    assert(self.destination)
249
                    assert(devicematch['destination'][0])
250
                    if self.destination == devicematch['destination'][0]:
251
                        found = found and True
252
                        logger.info('Found a matching destination')
253
                    else:
254
                        found = False
255
                        logger.info('Destination fields do not match')
256
                except:
257
                    pass
258
                try:
259
                    assert(self.source)
260
                    assert(devicematch['source'][0])
261
                    if self.source == devicematch['source'][0]:
262
                        found = found and True
263
                        logger.info('Found a matching source')
264
                    else:
265
                        found = False
266
                        logger.info('Source fields do not match')
267
                except:
268
                    pass
269
                
270
                try:
271
                    assert(self.fragmenttype.all())
272
                    assert(devicematch['fragment'])
273
                    devitems = devicematch['fragment']
274
                    dbitems = ["%s"%i for i in self.fragmenttype.all()]
275
                    intersect = list(set(devitems).intersection(set(dbitems)))
276
                    if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
277
                        found = found and True
278
                        logger.info('Found a matching fragment type')
279
                    else:
280
                        found = False
281
                        logger.info('Fragment type fields do not match')
282
                except:
283
                    pass
284
                
285
                try:
286
                    assert(self.port.all())
287
                    assert(devicematch['port'])
288
                    devitems = devicematch['port']
289
                    dbitems = ["%s"%i for i in self.port.all()]
290
                    intersect = list(set(devitems).intersection(set(dbitems)))
291
                    if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
292
                        found = found and True
293
                        logger.info('Found a matching port type')
294
                    else:
295
                        found = False
296
                        logger.info('Port type fields do not match')
297
                except:
298
                    pass
299
                
300
                try:
301
                    assert(self.protocol.all())
302
                    assert(devicematch['protocol'])
303
                    devitems = devicematch['protocol']
304
                    dbitems = ["%s"%i for i in self.protocol.all()]
305
                    intersect = list(set(devitems).intersection(set(dbitems)))
306
                    if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
307
                        found = found and True
308
                        logger.info('Found a matching protocol type')
309
                    else:
310
                        found = False
311
                        logger.info('Protocol type fields do not match')
312
                except:
313
                    pass
314

    
315
                try:
316
                    assert(self.destinationport.all())
317
                    assert(devicematch['destination-port'])
318
                    devitems = devicematch['destination-port']
319
                    dbitems = ["%s"%i for i in self.destinationport.all()]
320
                    intersect = list(set(devitems).intersection(set(dbitems)))
321
                    if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
322
                        found = found and True
323
                        logger.info('Found a matching destination port type')
324
                    else:
325
                        found = False
326
                        logger.info('Destination port type fields do not match')
327
                except:
328
                    pass
329

    
330
                try:
331
                    assert(self.sourceport.all())
332
                    assert(devicematch['source-port'])
333
                    devitems = devicematch['source-port']
334
                    dbitems = ["%s"%i for i in self.sourceport.all()]
335
                    intersect = list(set(devitems).intersection(set(dbitems)))
336
                    if ((len(intersect) == len(dbitems)) and (len(intersect) == len(devitems))):
337
                        found = found and True
338
                        logger.info('Found a matching source port type')
339
                    else:
340
                        found = False
341
                        logger.info('Source port type fields do not match')
342
                except:
343
                    pass
344
                                
345
                
346
#                try:
347
#                    assert(self.fragmenttype)
348
#                    assert(devicematch['fragment'][0])
349
#                    if self.fragmenttype == devicematch['fragment'][0]:
350
#                        found = found and True
351
#                        logger.info('Found a matching fragment type')
352
#                    else:
353
#                        found = False
354
#                        logger.info('Fragment type fields do not match')
355
#                except:
356
#                    pass
357
                try:
358
                    assert(self.icmpcode)
359
                    assert(devicematch['icmp-code'][0])
360
                    if self.icmpcode == devicematch['icmp-code'][0]:
361
                        found = found and True
362
                        logger.info('Found a matching icmp code')
363
                    else:
364
                        found = False
365
                        logger.info('Icmp code fields do not match')
366
                except:
367
                    pass
368
                try:
369
                    assert(self.icmptype)
370
                    assert(devicematch['icmp-type'][0])
371
                    if self.icmptype == devicematch['icmp-type'][0]:
372
                        found = found and True
373
                        logger.info('Found a matching icmp type')
374
                    else:
375
                        found = False
376
                        logger.info('Icmp type fields do not match')
377
                except:
378
                    pass
379
                if found and self.status != "ACTIVE":
380
                    logger.error('Rule is applied on device but appears as offline')
381
                    self.status = "ACTIVE"
382
                    self.save()
383
                    found = True
384
            if self.status == "ADMININACTIVE" or self.status == "INACTIVE" or self.status == "EXPIRED":
385
                found = True
386
        return found
387

    
388
    def get_then(self):
389
        ret = ''
390
        then_statements = self.then.all()
391
        for statement in then_statements:
392
            if statement.action_value:
393
                ret = "%s %s %s" %(ret, statement.action, statement.action_value)
394
            else: 
395
                ret = "%s %s" %(ret, statement.action)
396
        return ret
397
    
398
    get_then.short_description = 'Then statement'
399
    get_then.allow_tags = True
400
#
401
    def get_match(self):
402
        ret = '<dl class="dl-horizontal">'
403
        if self.destination:
404
            ret = '%s <dt>Dst Addr</dt><dd>%s</dd>' %(ret, self.destination)
405
        if self.fragmenttype.all():
406
            ret = ret + "<dt>Fragment Types</dt><dd>%s</dd>" %(', '.join(["%s"%i for i in self.fragmenttype.all()]))
407
#            for fragment in self.fragmenttype.all():
408
#                    ret = ret + "Fragment Types:<strong>%s</dd>" %(fragment)
409
        if self.icmpcode:
410
            ret = "%s <dt>ICMP code</dt><dd>%s</dd>" %(ret, self.icmpcode)
411
        if self.icmptype:
412
            ret = "%s <dt>ICMP Type</dt><dd>%s</dd>" %(ret, self.icmptype)
413
        if self.packetlength:
414
            ret = "%s <dt>Packet Length</dt><dd>%s</dd>" %(ret, self.packetlength)
415
        if self.source:
416
            ret = "%s <dt>Src Addr</dt><dd>%s</dd>" %(ret, self.source)
417
        if self.tcpflag:
418
            ret = "%s <dt>TCP flag</dt><dd>%s</dd>" %(ret, self.tcpflag)
419
        if self.port.all():
420
            ret = ret + "<dt>Ports</dt><dd>%s</dd>" %(', '.join(["%s"%i for i in self.port.all()]))
421
#            for port in self.port.all():
422
#                    ret = ret + "Port:<strong>%s</dd>" %(port)
423
        if self.protocol.all():
424
            ret = ret + "<dt>Protocols</dt><dd>%s</dd>" %(', '.join(["%s"%i for i in self.protocol.all()]))
425
#            for protocol in self.protocol.all():
426
#                    ret = ret + "Protocol:<strong>%s</dd>" %(protocol)
427
        if self.destinationport.all():
428
            ret = ret + "<dt>DstPorts</dt><dd>%s</dd>" %(', '.join(["%s"%i for i in self.destinationport.all()]))
429
#            for port in self.destinationport.all():
430
#                    ret = ret + "Dst Port:<strong>%s</dd>" %(port)
431
        if self.sourceport.all():
432
            ret = ret + "<dt>SrcPorts</dt><dd>%s</dd>" %(', '.join(["%s"%i for i in self.sourceport.all()]))
433
#            for port in self.sourceport.all():
434
#                    ret = ret +"Src Port:<strong>%s</dd>" %(port)
435
        if self.dscp:
436
            for dscp in self.dscp.all():
437
                    ret = ret + "%s <dt>Port</dt><dd>%s</dd>" %(ret, dscp)
438
        ret = ret + "</dl>"
439
        return ret
440
        
441
    get_match.short_description = 'Match statement'
442
    get_match.allow_tags = True
443
    
444
    @property
445
    def applier_peer(self):
446
        try:
447
            applier_peer = self.applier.get_profile().peer
448
        except:
449
            applier_peer = None
450
        return applier_peer
451
    
452
    @property
453
    def days_to_expire(self):
454
        if self.status not in ['EXPIRED', 'ADMININACTIVE', 'ERROR', 'INACTIVE']:
455
            expiration_days = (self.expires - datetime.date.today()).days
456
            if expiration_days < settings.EXPIRATION_NOTIFY_DAYS:
457
                return "%s" %expiration_days
458
            else:
459
                return False
460
        else:
461
            return False
462

    
463
def send_message(msg, user):
464
#    username = user.username
465
    peer = user
466
    b = beanstalkc.Connection()
467
    b.use(settings.POLLS_TUBE)
468
    tube_message = json.dumps({'message': str(msg), 'username':peer})
469
    b.put(tube_message)
470
    b.close()