Creation of a traffic routing service
Creation of a traffic routing service
Abstract
The work presents a solution in the field of information and communication technologies, namely, a product for automated traffic routing together with an administrator panel, a notification system, a referral invitation system, and a payment service. The article also discusses strategies for determining the importance of the characteristics of various server configurations. All developments are launched in fault-tolerant background processes that instantly restart the product in case of any cluster failure, while saving error status records. Requirements analysis was conducted to achieve maximum performance at the lowest cost (server hosting) and maintain product scalability flexibility.
1. Введение
Необходимость в маршрутизации трафика возникает в связи с растущими требованиями к приватности трафика пользователей, устойчивости сетевых соединений и гибкости распределённых систем в условиях интенсивного использования интернет-сервисов. Особенно актуально это в контексте работы с VPN-соединениями, бот-платформами и облачными инфраструктурами, где управление потоками данных требует высокой степени автоматизации и отказоустойчивости.
Данная задача является актуальной, так как с увеличением числа пользователей и усложнением сетевых топологий возрастает нагрузка на каналы связи и вычислительные ресурсы. Кроме того, пользователи всё чаще нуждаются в простой и быстрой настройке безопасного доступа к сетевым ресурсам без участия технических специалистов. Это требует создания гибких и масштабируемых решений, способных автоматически выбирать наиболее подходящие маршруты, управлять серверами и контролировать подписки.
2. Методы и принципы исследования
В продукте используется маршрутизация трафика, которая происходит посредством tun (network tunnel) интерфейса, который нагружает CPU. Также, при высокой нагрузке будет происходить рост одновременных подключений, поэтому нужна высокая широта канала сетевого подключения. Для администрирования продукта необходимо реализовать графический интерфейс, который будет работать с базой данных пользователей.
3. Основная часть
3.1. Разработка архитектуры продукта
На рисунке 1 показана общая схема архитектурного решения разрабатываемого программного продукта. Так здесь содержится сервис для обработки оплат пользователей (Payments WebHook), сервис рассылки уведомлений (Notification Service), Telegram бот в качестве продукта для распространения среди пользователей (TG bot), сервис для организации внутренней бизнес-логики (REST API), сервис для управления внутренними серверами (VPN API), зарубежные сервера (Finland VPS, Germany VPS). Для обеспечения отказоустойчивости сервера принято решение, что запуск изолированных сборок необходимо выполнять внутри автоматически перезапускаемых сервисов (systemd).

Рисунок 1 - Архитектура продукта
Необходимым требованием ко всем дочерним серверам состоит в возможности принятия запросов через VPN API. Файл конфигурации располагается на русском сервере models/countries_url.json и имеет вид:
1{
2 "finland": [
3 "finland_ip:finland_port"
4 ],
5 "germany": [
6 "germany_ip:germany_port"
7 ]
8}Таким образом, для добавления дополнительного сервера, необходимо всего лишь модифицировать конфигурационный файл, добавив в него IP адрес и номер порта.
3.3. Полный путь пользователя
Опишем формально полный набор шагов, которые необходимо выполнить пользователю:
Начало диалога с ботом -> активация пробного периода -> получение ссылки на подключение (на всех дочерних серверах для него создается персональная ссылка) -> покупка подписки.
3.4. CRUD операции над пользователями
Для работы с пользователями создан абстрагированный RESTful API сервис. Он поддерживает стандартный набор CRUD (create, read, update, delete) операций и обеспечивает взаимодействие с базой данных.
Пример конечной точки для поиска пользователя:
1@routes_bp.route('/getUser/<tg_id>', methods=['GET'])
2def getUser(tg_id):
3 try:
4 user = xray_api.getUser(tg_id)
5 if user is None:
6 logging.info(f'CAN\'T FIND USER {tg_id}')
7 abort(404)
8 return dumps(user.toDict())
9 except werkzeug.exceptions.HTTPException as e:
10 raise e
11 except Exception as e:
12 logging.error(f'FLASK ERROR {e}')
13 abort(401)
14 passПример обращения к сущности для работы с пользователями.
1def getUser(self, tg_id) -> Optional[User]:
2 try:
3 user = self.mongo_storage.findUser(tg_id)
4 return user
5 except:
6 logging.error(f'CAN’T GET USER FROM DB {tg_id}')
7 raise Exception(f'CAN’T GET USER FROM DB {tg_id}')
8 passПример поиска пользователя по базе данных:
1def findUser(self, tg_id: str) -> Optional[User]:
2 user_json = self._users.find_one({'tg_id': tg_id})
3 if user_json != None:
4 user = User('', '')
5 user.fromJson(user_json)
6 return user
7 else:
8 return None3.5. Система уведомлений
Каждый раз, при активации пользователем пробной подписки или полноценной покупки подписки, у данного пользователя обновляется срок использования. По истечению этого срока необходимо выполнить уведомление и запросить повторно произвести оплату, для продления срока использования. Решение этой задачи обеспечивает Notification Service, который работает по логике планировщика задач.
При оплате или активации пробного периода в сервис заносится задача на исполнение обратного вызова функции (отправки уведомления) в назначенное время. Также, проверяется актуальность состояния уведомления, ведь когда пользователь оплачивает 2 и более месяцев, мы не должны ему отправлять уведомления. Для отправки уведомлений сервис проверяет запланированные уведомления каждую минуту и, если таковые присутствуют, он отправляет их пользователю. Все задачи сохраняются локально, а также проверяются при перезапуске сервиса. Данный сервис абстрагирован от всего функционала отдельным RESTful API контроллером.
Поддерживаемые функции обратного вызова:
1def _checkHasSub(subdate: datetime) -> bool:
2 """Проверка на активность подписки"""
3 subdate = datetime.datetime.fromtimestamp(int(subdate) / 1000)
4 return not subdate < datetime.datetime.now()
5
6def notifySubEnded(bot, tg_id, text):
7 try:
8 user = _getUser(tg_id)
9 if _checkHasSub(user.subdate):
10 logging.info(f'CANCELLED NOTIFY USER RENEWED SUB user_subdate: {user.subdate}')
11 return
12 kb = InlineKeyboardMarkup()
13 kb.add(ButtonTypeEnum.renewSub)
14 bot.send_message(tg_id, text, reply_markup=kb)
15 except Exception as e:
16 logging.error(f'TRIED COMPLETE REMIND SUB OUT DATE FLOW exc:{repr(e)}')
17 pass
18
19def testNotify(bot, tg_id, text):
20 try:
21 kb = InlineKeyboardMarkup()
22 kb.add(ButtonTypeEnum.renewSub)
23 bot.send_message(tg_id, text, reply_markup=kb)
24 logging.info(f'TEST NOTIFY DELIVERED {tg_id}')
25 except Exception as e:
26 logging.error(f'TRIED COMPLETE TEST NOTIFY exc:{repr(e)}')
27 passРепозиторий для запросов в базу данных запланированных уведомлений:
1class NotifyDB:
2 …
3 def add_notify(self, notify: Notify):
4 self._notifications.insert_one(notify.toDict())
5 logging.info(f'ADDED NOTIFY {notify.toDict()}')
6 pass
7
8 def get_all_notifies(self) -> list[Notify]:
9 notifies_json = self._notifications.find()
10 notifies = []
11 for notify_json in notifies_json:
12 notify = Notify()
13 notify.fromDict(notify_json)
14 notifies.append(notify)
15 return notifies
16
17 def get_period_notifies(self, greater: datetime.datetime, less: datetime.datetime) -> list[Notify]:
18 """Получение уведомлений за определенный период"""
19 notifies_json = self._notifications.find({
20 'date': {
21 '$gte': greater.replace(microsecond=0, second=0),
22 '$lte': less.replace(microsecond=999999, second=59)
23 }
24 })
25 notifies = []
26 for notify_json in notifies_json:
27 notify = Notify()
28 notify.fromDict(notify_json)
29 notifies.append(notify)
30 return notifies
31
32 def get_less_notifies(self, less: datetime.datetime) -> list[Notify]:
33 """Получение прошедших уведомлений"""
34 notifies_json = self._notifications.find({
35 'date': {
36 '$lte': less.replace(microsecond=999999, second=59)
37 }
38 })
39 notifies = []
40 for notify_json in notifies_json:
41 notify = Notify()
42 notify.fromDict(notify_json)
43 notifies.append(notify)
44 return notifies
45
46 def remove_notify(self, tgid: int, date: datetime.datetime, pushName: str):
47 self._notifications.delete_one({
48 'tgid': tgid,
49 'date': {
50 '$gte': date.replace(hour=0, minute=0, second=0, microsecond=0),
51 '$lte': date.replace(hour=23, minute=59, second=59, microsecond=999999)
52 },
53 'pushName': pushName
54 })
55 pass
56
57 def remove_notify_by_id(self, id: str):
58 self._notifications.delete_one({'id': id})Реализация планировщика уведомлений:
1class NotificationsScheduler:
2 …
3 def add_task(self, notify: Notify):
4 self.notifydb.add_notify(notify)
5 logging.info(f'Added push: {notify.toDict()}')
6
7 def shutdown(self):
8 logging.info('SHUTDOWN IS RUNNING')
9 self.scheduler.shutdown(wait=True)
10 logging.info('SHUTDOWN IS COMPLETED')
11
12 def restoreTasks(self):
13 """Восстановление недоставленных уведомлений"""
14 try:
15 notifies = self.notifydb.get_less_notifies(datetime.datetime.now())
16 for notify in notifies:
17 try:
18 self.sendNotify(notify)
19 self.notifydb.remove_notify_by_id(notify.id)
20 logging.info(f'SENT AND DELETED PUSH WHILE RESTORING {notify.toDict()}')
21 except Exception as e:
22 logging.error(f'ERROR WHILE SENDING PUSH IN RESTORING FLOW push:{notify.toDict()} exc: {repr(e)}')
23 logging.info("PUSH RESTORING WAS ENDED")
24 except Exception as e:
25 logging.error(f'ERROR WHILE GETTING PUSHES IN RESTORING FLOW exc: {repr(e)}')
26
27 def sendNotify(self, notify: Notify):
28 match notify.pushName:
29 case PushName.notifySubEnded:
30 notifySubEnded(self.bot, *notify.args)
31 case PushName.testPush:
32 testNotify(self.bot, *notify.args)
33 pass
34
35 def listenPushes(self):
36 """Доставка уведомлений"""
37 try:
38 notifies = self.notifydb.get_less_notifies(datetime.datetime.now())
39 for notify in notifies:
40 try:
41 self.sendNotify(notify)
42 self.notifydb.remove_notify_by_id(notify.id)
43 logging.info(f'SENT AND DELETED PUSH {notify.toDict()}')
44 except Exception as e:
45 logging.error(f'ERROR WHILE SENDING PUSH IN CHECKING FLOW push:{notify.toDict()} exc: {repr(e)}')
46 except Exception as e:
47 logging.error(f'ERROR WHILE GETTING PUSHES IN CHECKING FLOW exc: {repr(e)}')REST API контроллер:
1@routes_bp.route('/addNotify', methods=['POST'])
2def addNotify():
3 notify = Notify()
4 try:
5 user_json = loads(request.data)['notify']
6 notify.fromDictTimestamp(user_json)
7 notifSched.add_task(notify)
8 return {"status": "ok"}
9 except Exception as e:
10 abort(500)
11 logging.error(f'FAILED ADD NOTIFY: {notify.toDict()}')
12 pass3.6. Панель администратора и рассылка
Вызвать меню администратора (рисунок 2) могут только Telegram аккаунты, идентификационные номера которых указаны в конфигурации администраторов.

Рисунок 2 - Панель администратора

Рисунок 3 - Демонстрация поиска пользователя
1@bot.callback_query_handler(func=lambda call: call.data == 'findUser')
2def findUserAction(call):
3 message = call.message
4 bot.send_message(message.chat.id, "Пришли tgid или username в формате:\n\ntgid=98472894\nИЛИ\nusername=e9r_3")
5 bot.register_next_step_handler(message, findUser)
6 pass
7
8def findUser(message):
9 data: str = message.text
10 try:
11 data = data[data.find('=') + 1:]
12 user = None
13
14 if 'tgid' in message.text:
15 user = _getUser(data, '')
16 else:
17 user = _getUserByUsername(data)
18 subdate_dt = datetime.datetime.fromtimestamp(int(user.subdate) / 1000)
19
20 bot.send_message(message.chat.id, f"Запрошенный пользователь:\n\nПодписка до [гггг.мм.дд]{str(subdate_dt)}\n\n{user.toDict()}")
21 return
22 except Exception as e:
23 logging.error(f'ADMIN FIND ERROR {data} EXC: {repr(e)}')
24 bot.send_message(message.chat.id, f'Какая-то ошибка. Проверь корректность данных')Рассылка сообщений пользователям позволяет указать текст и названия кнопок, которые существуют в дизайне для добавления к сообщению. После отправки текста сообщения оно будет разослано всем пользователям. Графический интерфейс можно увидеть на рисунке 4. Названия кнопок указываются, соответствуя названию в отдельном классе кнопок приложения

Рисунок 4 - Демонстрация рассылки
1class ButtonTextEnum:
2 connect = 'Подключиться 🔐'
3 renewSub = 'Продлить подписку 🔐'
4 profile = 'Профиль 👤'
5 …
6 pass
7
8class ButtonTypeEnum:
9 connect = InlineKeyboardButton(text=ButtonTextEnum.connect, callback_data='connect')
10 profile = InlineKeyboardButton(text=ButtonTextEnum.profile, callback_data='profile')
11 subTrial = InlineKeyboardButton(text=ButtonTextEnum.subTrial, callback_data='subTrial')
12 …
13
14 def getButtonsFromNames(self, names: list[str]) -> list[InlineKeyboardButton]:
15 all_buttons = {
16 'connect': self.connect,
17 'profile': self.profile,
18 'subTrial': self.subTrial,
19 …
20 }
21 retval = []
22 for name in names:
23 if name == '' or name == '\n':
24 continue
25 retval.append(all_buttons[name])
26
27 return retvalРеализация рассылки:
1@bot.callback_query_handler(func=lambda call: call.data == 'messageAll')
2def messageAllAction(call):
3 message = call.message
4 bot.send_message(message.chat.id, "Отправь сообщение в следующем виде:\n\'Сообщение, которое нужно отправить\'\nКнопки: \'название кнопки1\' \'название кнопки 2\' итд.\n\nПример:\n`Всем привет!\nКнопки: profile myKeys support`\n\nЕсли кнопки не нужны, тослово Кнопки писать не нужно\nДля выхода напиши EXIT", parse_mode=telegram.constants.ParseMode.MARKDOWN)
5 bot.register_next_step_handler(message, messageAll)
6pass
7
8def messageAll(message):
9 try:
10 data: str = message.text
11 if data == 'EXIT':
12 return
13 buttons = []
14 kb = None
15 mess_to_send = data[:data.find('Кнопки:')]
16 if 'Кнопки' or 'кнопки' in data:
17 btn_names = list(data[data.find('Кнопки:') + 7:].replace('\n', '').split(' ')) if ('Кнопки' or 'кнопки' in data) else data
18 buttons = ButtonTypeEnum().getButtonsFromNames(btn_names)
19 kb = genKeyboard(1, buttons)
20 try:
21 user_ids_json = requests.get(KEYLAND_API_URL + f'/getAllUserTgIds')
22 user_ids = json.loads(user_ids_json.content)
23 if user_ids_json.status_code == 404:
24 raise UserNotFoundExc(f'messageAll error 404')
25 elif user_ids_json.status_code == 401:
26 raise BadFormatDataExc(f'messageAll error response: {user_ids.content}')
27 for tg_id in user_ids['ids']:
28 try:
29 if kb is None:
30 bot.send_message(tg_id, mess_to_send)
31 else:
32 bot.send_message(tg_id, mess_to_send, reply_markup=kb)
33 except Exception as e:
34 logging.error(f'messageAll with current tgid: {tg_id} er: {repr(e)}')
35 except Exception as e:
36 logging.error(f'messageAll {repr(e)}')
37 bot.send_message(message.chat.id, 'Не получилось отправить рассылку')
38 return
39 except Exception as e:
40 logging.error(f'messageAll {repr(e)}')
41 bot.send_message(message.chat.id, 'Не получилось отправить рассылку')
42 returnТакже поддерживается изменение количества дней подписки. Это необходимо для того, чтобы в случае ошибки обработки платежа администратор, не имеющий навыков внесения правок в базу данных, мог с легкостью изменить срок подписки. Бот потребует имя пользователя (рисунок 5) или идентификационный номер пользователя, а также срок в днях и месяцах, который необходимо добавить пользователю.

Рисунок 5 - Демонстрация изменения срока
1@bot.callback_query_handler(func=lambda call: call.data == 'changeSubDays')
2def changeSubDaysAction(call):
3 message = call.message
4 bot.send_message(message.chat.id, "Пришли сообщение в следующемвиде:\n\n`tgid/username='tgid_or_username'\nmonths+='months_count'\ndays+='days_count'`\n\nПример1:\n`tgid=46537428\nmonths+=1\ndays+=3`\n\nПример 2:\n`username=e9r_3\ndays+=6`", parse_mode=telegram.constants.ParseMode.MARKDOWN)
5 bot.register_next_step_handler(message, changeSubDays)
6 pass
7
8def changeSubDays(message):
9 data: str = message.text
10 try:
11 comms: list[str] = data.split('\n')
12 months = 0
13 days = 0
14 user = None
15 for comm in comms[1:]:
16 if 'months' in comm:
17 month_pretty = comm.replace(' ', '')
18 months = int(month_pretty[month_pretty.find('=') + 1:])
19 if 'days' in comm:
20 month_pretty = comm.replace(' ', '')
21 days = int(month_pretty[month_pretty.find('=') + 1:])
22
23 if 'tgid' in comms[0]:
24 tgid = comms[0].replace(' ', '')[5:]
25 user = _getUser(id=tgid)
26 else:
27 username = comms[0].replace(' ', '')[9:]
28 user = _getUserByUsername(username=username)
29
30 user.subdate = _renewSub(userSubdate=user.subdate, days=days, months=months)
31 _updateUser(user)
32 bot.send_message(message.chat.id, f'Подписка продлена пользователю tgid:{user.tg_id} usn:{user.username}')
33
34 except LimitSubDurationExc as e:
35 logging.error(f'ADMIN CHANGE SUB DAYS FAILURE{repr(e)}')
36 bot.send_message(message.chat.id, 'Не получилось внести изменения, потому что превышается ограничение максимального кол-вадней подписки')
37 except Exception as e:
38 logging.error(f'ADMIN CHANGE SUB DAYS FAILURE{repr(e)}')
39 bot.send_message(message.chat.id, 'Не получилось внести изменения')Еще панель администрации поддерживает отправку пробного уведомления (рисунок 6). Данная функция необходима для тестирования сервиса уведомлений после перезапуска всех фоновых процессов сервиса. Бот запросит указать количество секунд, через которое необходимо прислать уведомление c тестовой кнопкой

Рисунок 6 - Отправка тестового уведомления
1@bot.callback_query_handler(func=lambda call: call.data == 'testNotify')
2def testNotifyAction(call):
3 message = call.message
4 bot.send_message(message.chat.id, "Пришли мне число секунд, через которое я должен кинуть тебе увед", parse_mode=telegram.constants.ParseMode.MARKDOWN)
5 bot.register_next_step_handler(message, testNotify)
6 pass
7
8def testNotify(message):
9 try:
10 data: str = message.text
11 secs = int(data)
12 try:
13 dtn = datetime.datetime.now()
14 _addNotify(Notify(date=dtn + relativedelta(seconds=secs), tgid=message.chat.id, pushName=PushName.testPush, args=[message.chat.id, f'Тестовый пуш пришедший через {secs} секунд']))
15 except Exception as e:
16 logging.error('While creating test notify')
17 except Exception as e:
18 logging.error(f'messageAll {repr(e)}')
19 bot.send_message(message.chat.id, 'Не получилось отправить рассылку')
20 return3.7. Стратегия предпочтения IPv6
Финансово выгодно для VPN сервера использовать IPv6 адреса, потому что он состоит из 128 бит, что в 4 раза больше IPv4. Но на данный момент есть проблема, так как не все сайты поддерживают подключение по IPv6 адресу. В качестве решения была использована стратегия подключения ForceIPv6v4, которая обеспечивает требуемую логику подключения.
3.8. Очищение ненужных пользователей
Приличное количество людей уходят после истечения пробного срока, поэтому было решено завести регулярную проверку последней подписки. С помощью crontab каждую неделю выполняется скрипт, который удаляет пользователей, у которых последняя подписка была куплена более полугода назад.
3.9. Оплата подписки (Payments Service)
В качестве решения для обработки платежей было принято использовать ЮMoney. Все события успешных и неуспешных попыток оплаты приходят в Payments Service WebHook. Решение в виде вебхука привносит преимущество для событий, которые приходят во время неактивного состояния сервиса. События оплат будут пытаться прийти в вебхук пока сервис не вернет код статуса (то есть попытки прекратятся, только когда сервис уведомит ЮMoney кодом статуса, что событие было обработано). В случае неуспешной попытки пользователю придет сообщение об ошибке, а владельцу инфраструктуры — сообщение об ошибке и логи. В случае успешного проведения платежа, сервис обновит все записи пользователя на существующих серверах (продлит подписку), заведет отложенное уведомление на окончание срока подписки и пришлет пользователю сообщение с инструкцией для подключения. В теле события об оплате приходит идентификационный номер человека.
Реализация прослушивания платежей:
1@app.route('/notification', methods=['POST'])
2def my_webhook_handler():
3 ip = request.remote_addr
4 if not SecurityHelper().is_ip_trusted(ip):
5 return Response(status=400)
6 try:
7 # Создание объекта класса уведомлений в зависимости от события
8 notification_object = WebhookNotificationFactory().create(event_json)
9 response_object = notification_object.object
10 if notification_object.event == WebhookNotificationEventType.PAYMENT_SUCCEEDED:
11 some_data = {
12 'paymentId': response_object.id,
13 'paymentStatus': response_object.status,
14 'description': response_object.description,
15 }
16 tgid = some_data['description'][some_data['description'].find('пользователь номер:')+len('пользователь номер:'):]
17 old_subdate = None
18
19 # начисляем подписку на сервере
20 try:
21 user = _getUser(tgid)
22 old_subdate = user.subdate
23 user.subdate = _renewSub(user.subdate, months=1)
24 user.has_trial = False
25 _updateUser(user)
26 dtn = max(datetime.datetime.now(), datetime.datetime.fromtimestamp(int(user.subdate) / 1000)) + relativedelta(seconds=30)
27 _addNotify(Notify(date=dtn, tgid=user.tg_id, pushName=PushName.notifySubEnded, args=[user.tg_id, 'Привет, твоя подписказакончилась 🌚\nДоступ пришлось приостановить\n\nПри продлении действующие ключи будут вновь активированы\nЗановонастраивать подключение не нужно)']))
28 except Exception as e:
29 logging.error(f'ON SUCCESS STATE tgid:{tgid} exc: {repr(e)} JSON:{event_json}')
30 bot.send_message('846559715', f'Оплата прошла, но возникла ошибка в вебхуке {str(datetime.datetime.now())} tgid: {tgid} {repr(e)}')
31 bot.send_message(tgid, 'Оплата прошла, произошла ошибка в сценарии. Обратитесь в поддержку')
32 return Response(status=200)
33
34 # Отправляем сообщение соответствуя флоу
35 if str(old_subdate) == '3000':
36 kb = InlineKeyboardMarkup()
37 kb.add(ButtonTypeEnum.finland)
38 kb.add(ButtonTypeEnum.germany)
39 bot.send_message(tgid, "Выбери регион подключения (в любое время ты сможешь его поменять в разделе \"мои ключи\") 👇", reply_markup=kb)
40 else:
41 kb = InlineKeyboardMarkup()
42 kb.add(ButtonTypeEnum.changeRegion)
43 bot.send_message(tgid, "Твои ключи снова работают!", reply_markup=kb)
44 except Exception as e:
45 return Response(status=400)
46 return Response(status=200)
47
48@app.route('/payOk', methods=['POST'])
49def payok():
50 return Response(status=200)3.10. Реферальная система
Для упрощения распространения продукта было принято решение поддержать реферальную систему. Для каждого пользователя генерируется персональная ссылка для распространения. Если человек поделится со своим другом ссылкой, а тот, в свою очередь, оплатит подписку на месяц, то рефералу начисляются бонусные 10 дней. Графический интерфейс реферального окна можно увидеть на рисунке 7.

Рисунок 7 - Реферальное окно
Для оценки производительности полученного продукта было проведено нагрузочное тестирование. В качестве конечной точки использовался запрос на создание пользователя. Данная функция порождает сущность пользователя в базе данных, а также количество новых пользователей напрямую зависит от неё. Все метрики собирались на создании 1, 10, 100 пользователей.

Рисунок 8 - График тестирования 1

Рисунок 9 - График тестирования 2

Рисунок 10 - График тестирования 3
4. Литературный обзор и обоснование актуальности
Современные тенденции цифровизации и роста требований к сетевой безопасности усиливают интерес к технологиям частных виртуальных сетей и автоматизации их управления. По данным Таненбаума и Эдвардса, они являются ключевым инструментом для защиты сетевых соединений и обеспечения конфиденциальности данных.
Использование REST API позволяет стандартизировать взаимодействие между клиентскими и серверными компонентами распределённых систем. Реализация таких решений на базе Flask обеспечивает простоту разработки, расширяемость и интеграцию с внешними сервисами.
Интерфейсы на основе чат-ботов в мессенджерах, таких как Telegram, описанные Янартханамом, повышают доступность и удобство администрирования систем. В совокупности эти подходы позволяют создавать эффективные инструменты для управления VPN-инфраструктурой через интуитивно понятный интерфейс.
Таким образом, актуальность работы обусловлена необходимостью разработки удобного и безопасного средства управления VPN-службами с использованием Telegram-бота и REST-интерфейсов.
5. Заключение
5.1. Анализ полученных результатов
В ходе работы были разработаны и протестированы следующие сервисы:
- Main API (главный REST сервис, обрабатывающий все действия пользователя и данные на внешних серверах)
- Country API (REST сервис, развернутый на каждом сервере через который происходит маршрутизация трафика, нужен для управления внутренним состоянием сервера)
- Mongo API (абстракция, созданная для сбора и сохранения данных)
- VPN API (абстракция, которая общается с группой Country API)
- Telegram bot (графический интерфейс, интегрированный в telegram бота)
Комбинация данных сервисов выполняет все поставленные задачи и предоставляет архитектуру, которая готова к дальнейшему масштабированию. Нагрузочное тестирование и метрики производительности продукта показывают ожидаемые результаты.
5.2. Вывод
Используя описанные в работе подходы, алгоритмы и идеи, удалось подготовить продукт, который предоставляет маршрутизацию трафика, способен выдержать высокую нагрузку, готов к масштабированию, также имеет настраиваемый графический интерфейс.
