diff --git a/abonapp/models.py b/abonapp/models.py index c1bdd39..656a99b 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -215,8 +215,8 @@ class Abon(BaseAccount): # make subscriber from agent structure def build_agent_struct(self): abon_addresses = tuple(ip_address(i.ip) for i in self.ip_addresses.filter(is_active=True)) - if not abon_addresses: - return + # if not abon_addresses: + # return abon_tariff = self.active_tariff() if abon_tariff is None: agent_trf = None diff --git a/abonapp/views.py b/abonapp/views.py index 8870b2c..5d692e9 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -1,3 +1,4 @@ +from ipaddress import ip_address from typing import Dict, Optional from datetime import datetime, date from django.contrib.gis.shortcuts import render_to_text @@ -16,7 +17,6 @@ from django.conf import settings from jsonview.decorators import json_view from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release -from agent.structs import IpStruct from statistics.models import StatCache from tariff_app.models import Tariff from agent import NasFailedResult, Transmitter, NasNetworkError @@ -1117,16 +1117,20 @@ def user_session_toggle(request, gid, uname, lease_id, action=None): lease = abon.ip_addresses.get(pk=lease_id) tm = Transmitter() abon_nas_obj = abon.build_agent_struct() - res_text = '#Parameter needed#' - if action == 'free': - lease.free() - tm.lease_free(abon_nas_obj, IpStruct(lease.ip)) - res_text = _('Ip lease has been freed') - elif action == 'start': - lease.start() - tm.lease_start(abon_nas_obj, IpStruct(lease.ip)) - res_text = _('Ip lease has been started') - messages.success(request, res_text) + try: + if action == 'free': + if abon.ip_addresses.filter(is_active=True).count() > 1: + tm.lease_free(abon_nas_obj, ip_address(lease.ip)) + lease.free() + messages.success(request, _('Ip lease has been freed')) + else: + messages.error(request, _('You cannot disable last session')) + elif action == 'start': + tm.lease_start(abon_nas_obj, ip_address(lease.ip)) + lease.start() + messages.success(request, _('Ip lease has been started')) + except NasFailedResult as e: + messages.error(request, e) return redirect('abonapp:abon_home', gid, uname) diff --git a/agent/core.py b/agent/core.py index 0428596..833a1d4 100644 --- a/agent/core.py +++ b/agent/core.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Iterator, Any, Tuple, Optional -from .structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff, IpStruct +from .structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff # Raised if NAS has returned failed result @@ -78,14 +78,14 @@ class BaseTransmitter(ABC): pass @abstractmethod - def lease_free(self, user: AbonStruct, lease: IpStruct): + def lease_free(self, user: AbonStruct, lease): """ Remove ip lease from allowed to network :return: """ @abstractmethod - def lease_start(self, user: AbonStruct, lease: IpStruct): + def lease_start(self, user: AbonStruct, lease): """ Starts ip lease to allowed to network :return: diff --git a/agent/mod_mikrotik.py b/agent/mod_mikrotik.py index b843234..781ae1c 100644 --- a/agent/mod_mikrotik.py +++ b/agent/mod_mikrotik.py @@ -3,13 +3,14 @@ import socket import binascii from abc import ABCMeta from hashlib import md5 -from ipaddress import ip_network, _BaseAddress +from ipaddress import _BaseAddress, ip_address from typing import Iterable, Optional, Tuple, Generator, Dict from django.conf import settings +from django.utils.translation import ugettext_lazy as _ from djing.lib.decorators import LazyInitMetaclass -from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff +from .structs import TariffStruct, AbonStruct, VectorAbon, VectorTariff from . import settings as local_settings from djing import ping from agent.core import BaseTransmitter, NasNetworkError, NasFailedResult @@ -161,18 +162,11 @@ class ApiRos(object): return ret def __del__(self): - if hasattr(self, 'sk'): + sk = getattr(self, 'sk') + if sk is not None: self.sk.close() -class IpAddressListObj(IpStruct): - __slots__ = ('__ip', 'mk_id') - - def __init__(self, ip, mk_id): - super(IpAddressListObj, self).__init__(ip) - self.mk_id = str(mk_id).replace('*', '') - - class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs', (ABCMeta, LazyInitMetaclass), {})): def __init__(self, login=None, password=None, ip=None, port=None): @@ -246,17 +240,18 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs if disabled is not None: disabled = True if disabled == 'true' else False if target is not None and name is not None: - target_ip, target_net = target.split('/') + # target may be '192.168.0.3/32,192.168.0.2/32' + ips = (ip.split('/')[0] for ip in target.split(',')) a = AbonStruct( uid=int(name[3:]), - ip=target_ip, + ips=ips, tariff=t, - is_active=disabled or False + is_access=disabled or False ) a.queue_id = info.get('=.id') return a - except ValueError: - pass + except ValueError as e: + print('ValueError:', e) ################################################# # QUEUES @@ -266,18 +261,19 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs def find_queue(self, name: str) -> Optional[AbonStruct]: r = self._exec_cmd(('/queue/simple/print', '?name=%s' % name)) if r: - return self._build_shape_obj(r) + return self._build_shape_obj(r.get('!re')) def add_queue(self, user: AbonStruct) -> None: if not isinstance(user, AbonStruct): raise TypeError if user.tariff is None or not isinstance(user.tariff, TariffStruct): return + ips = ','.join(str(i) for i in user.ips) self._exec_cmd(( '/queue/simple/add', '=name=uid%d' % user.uid, # FIXME: тут в разных микротиках или =target-addresses или =target - '=target=%s' % ','.join(str(i) for i in user.ips), + '=target=%s' % ips, '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=queue=MikroBILL_SFQ/MikroBILL_SFQ', '=burst-time=1/1' @@ -314,7 +310,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs '/queue/simple/set', '=name=uid%d' % user.uid, '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), - # FIXME: тут в разных микротиках или =target-addresses или =target + # FIXME: тут в разных версиях прошивки микротика или =target-addresses или =target '=target=%s' % ','.join(str(i) for i in user.ips), '=queue=MikroBILL_SFQ/MikroBILL_SFQ', '=burst-time=1/1' @@ -334,8 +330,8 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs # Ip->firewall->address list ################################################# - def add_ip(self, list_name: str, ip: IpStruct): - if not isinstance(ip, IpStruct): + def add_ip(self, list_name: str, ip): + if not issubclass(ip.__class__, _BaseAddress): raise TypeError commands = ( '/ip/firewall/address-list/add', @@ -373,7 +369,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs '?dynamic=no' )) for dat in ips: - yield IpAddressListObj(dat.get('=address'), dat.get('=.id')) + yield ip_address(dat.get('=address')), dat.get('=.id') ################################################# # BaseTransmitter implementation @@ -402,7 +398,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs raise TypeError self.add_queue(user) for ip in user.ips: - if not isinstance(ip, IpStruct): + if not issubclass(ip.__class__, _BaseAddress): raise TypeError self.add_ip(LIST_USERS_ALLOWED, ip) @@ -444,7 +440,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs self.add_queue(user) return if queue != user: - self.update_queue(user) + self.update_queue(user, queue) def ping(self, host, count=10) -> Optional[Tuple[int, int]]: r = self._exec_cmd(( @@ -503,20 +499,25 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs # self.remove_ip_range(diff) return queues - def lease_free(self, user: AbonStruct, lease: IpStruct): - ip = self.find_ip(lease, LIST_USERS_ALLOWED) - if ip is not None: - self.remove_ip(ip.get('=.id')) + def lease_free(self, user: AbonStruct, lease): queue = self.find_queue('uid%d' % user.uid) - if queue is not None: - user.ips = tuple(i for i in user.ips if i != lease) - self.update_queue(user, queue) + if len(queue.ips) > 1: + if queue is not None: + user.ips = tuple(i for i in user.ips if i != lease) + self.update_queue(user, queue) + ip = self.find_ip(lease, LIST_USERS_ALLOWED) + if ip is not None: + self.remove_ip(ip.get('=.id')) + else: + raise NasFailedResult(_('You cannot disable last session')) - def lease_start(self, user: AbonStruct, lease: IpStruct): + def lease_start(self, user: AbonStruct, lease): ip = self.find_ip(lease, LIST_USERS_ALLOWED) if ip is None: self.add_ip(LIST_USERS_ALLOWED, lease) queue = self.find_queue('uid%d' % user.uid) - if queue is not None: - user.ips += lease, + user.ips += lease, + if queue is None: + self.add_queue(user) + else: self.update_queue(user, queue) diff --git a/agent/structs.py b/agent/structs.py index f660d07..2bc187e 100644 --- a/agent/structs.py +++ b/agent/structs.py @@ -1,40 +1,12 @@ -# -*- coding: utf-8 -*- from abc import ABCMeta +from ipaddress import ip_address from typing import Iterable -from djing.lib import int2ip, ip2int class BaseStruct(object, metaclass=ABCMeta): __slots__ = () -class IpStruct(BaseStruct): - __slots__ = ('__ip',) - - def __init__(self, ip): - if type(ip) is int: - self.__ip = ip - else: - self.__ip = ip2int(str(ip)) - - def get_int(self): - return self.__ip - - def __eq__(self, other): - if not isinstance(other, IpStruct): - raise TypeError('Instance must be IpStruct') - return self.__ip == other.__ip - - def __int__(self): - return self.__ip - - def __str__(self): - return int2ip(self.__ip) - - def __hash__(self): - return hash(self.__ip) - - # Как обслуживается абонент class TariffStruct(BaseStruct): __slots__ = ('tid', 'speedIn', 'speedOut') @@ -71,7 +43,7 @@ class AbonStruct(BaseStruct): if ips is None: self.ips = () else: - self.ips = tuple(IpStruct(ip) for ip in ips) + self.ips = tuple(ip_address(ip) for ip in ips) self.tariff = tariff self.is_access = is_access self.queue_id = 0 diff --git a/djing/lib/__init__.py b/djing/lib/__init__.py index 2474355..0c2b4b4 100644 --- a/djing/lib/__init__.py +++ b/djing/lib/__init__.py @@ -1,25 +1,10 @@ import socket -import struct from functools import wraps from hashlib import sha256 from datetime import timedelta from collections import Iterator -def ip2int(addr): - try: - return struct.unpack("!I", socket.inet_aton(addr))[0] - except: - return 0 - - -def int2ip(addr): - try: - return socket.inet_ntoa(struct.pack("!I", addr)) - except: - return '' - - def safe_float(fl): try: return 0.0 if fl is None or fl == '' else float(fl) diff --git a/ip_pool/models.py b/ip_pool/models.py index cd9367e..bdfb55f 100644 --- a/ip_pool/models.py +++ b/ip_pool/models.py @@ -10,6 +10,7 @@ from django.db import models from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ +from djing.fields import MACAddressField from djing.lib import DuplicateEntry from ip_pool.fields import GenericIpAddressWithPrefix from group_app.models import Group @@ -162,9 +163,11 @@ class IpLeaseManager(models.Manager): class IpLeaseModel(models.Model): ip = models.GenericIPAddressField(verbose_name=_('Ip address'), unique=True) network = models.ForeignKey(NetworkModel, on_delete=models.CASCADE, verbose_name=_('Parent network')) + mac_addr = MACAddressField(verbose_name=_('Mac address'), null=True, blank=True, unique=True) lease_time = models.DateTimeField(_('Lease time'), auto_now_add=True) is_dynamic = models.BooleanField(_('Is dynamic'), default=False) is_active = models.BooleanField(_('Is active'), default=True) + device_info = models.CharField(null=True, blank=True, default=None, max_length=128) objects = IpLeaseManager()