Дорогие пользователи! У нас появился новый форум на платформе tp-link.community (Сообщество)
Форум доступен по ссылке https://community.tp-link.com/ru
Просим Вас отнестись с пониманием к новому форуму, он находится в стадии доработки и в скором времени будет полностью завершен.
Если при регистрации в Сообществе Вы укажете адрес электронный почты, который используете на данном форуме, то Ваши данные будут перенесены на форум Сообщества автоматически.
Также, если на форуме Сообщества Ваш никнейм будет занят, то Вам предложат сменить его или оставить, но с приставкой “_RU”.
Подробнее Вы можете прочитать тут: https://community.tp-link.com/ru/home/f … pic/501542
Убедительная просьба не дублировать темы на старом/новом форуме.
-
schetilin
- Сообщения: 2
- Зарегистрирован: 28 июл 2014, 03:56
- Страна: Украина
Проблема с проброской 80 порта в прошивке от 21. 2014
Аппаратная версия устройства: Ver.4
Версия прошивки: build 140521_RU
После обновления TL-WR741ND v4 прошивкой от 21.05.2014 не получается пробросить 80 порт.
“Код ошибки: 14007 Ошибка: Порт удалённого управления через веб-интерфейс конфликтует с портом виртуального сервера.”
Пришлось вернуться на прошивку от 18.06.2013
-
Ame
- Модератор
- Сообщения: 686
- Зарегистрирован: 07 апр 2014, 11:41
- Страна: Москва
Re: Проблема с проброской 80 порта в прошивке от 21. 2014
Сообщение
Ame » 28 июл 2014, 12:05
Вы изменяли порт удаленного управления роутера с стандартного 80го? Скорее всего нет, тогда вам нужно зайти в настройки роутера, далее Безопасность, там удаленное управление. В этом разделе меняете 80й порт на 8080 например. После перезагружаете роутер и все заработает.
-
schetilin
- Сообщения: 2
- Зарегистрирован: 28 июл 2014, 03:56
- Страна: Украина
Re: Проблема с проброской 80 порта в прошивке от 21. 2014
Сообщение
schetilin » 28 июл 2014, 18:31
Спасибо, попробую. Просто в предыдущей прошивке все работало без изменения порта web-интерфейса.
-
Rolis
- Сообщения: 544
- Зарегистрирован: 25 фев 2014, 20:02
- Страна: Россия
- Откуда: Moscow
- Контактная информация:
Re: Проблема с проброской 80 порта в прошивке от 21. 2014
Сообщение
Rolis » 28 июл 2014, 23:48
schetilin писал(а):Спасибо, попробую. Просто в предыдущей прошивке все работало без изменения порта web-интерфейса.
Скорее всего потому, что удаленное управление роутером через 80-й порт не было разрешено.
TL-ER6120 | TL-ER6020 | TL-R600VPN | TL-ER604W | TL-WA5210G | TL-WA5110G | TL-WR941ND | TL-WR842ND | TL-WA750RE | TL-WA901ND | TL-MR3420 | TL-MR3020 | TL-SG3210 | TL-SG1024DE | TL-SL5428E | TL-SL3428 | TL-WA860RE | TL-WA855RE | Archer C2300 | TL-ER7206
05.06.2010, 20:53
|
#1 |
||
Участник
|
Запись уже существует!?
Из одной компании выгрузил файл (dat) с клиентами. В другой компании пытаюсь загрузить, выдает ошибку: Невозможно создать запись в Клиенты (CustTable). Счет клиента: АБРИКОС, ООО “АБРИКОС”. НО такого клиента нет в этой компании со счетом клиента “АБРИКОС”. Проверял всеми способами и в аксапте запросы писал, и фильтром, и на SQL посмотрел. Такое ощущение что проблема в RecId, буд-то он его из файла грузит. Как исправить эту ошибку?
|
||
05.06.2010, 21:14
|
#2 |
||
Участник
|
Очевидный вопрос, но тем не менее: все варианты написания “АБРИКОС” проверили?
|
||
05.06.2010, 22:10
|
#3 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Что если в параметрах загрузки установить флаг “Обновлять существующие записи” (или как он там называется в вашей версии – кстати, неплохо бы привести версию).
|
05.06.2010, 22:54
|
#4 |
||
Участник
|
АБРИКОС искал во всех вариантах. “абрикос” это условный пример. на самом деле там несколько записей, и на всех ошибка. Так же код на трансляцию справочников выдает туже ошибку X++: changecompany("k3") { _newrecord = new DictTable(_record.tableId).makeRecord() .... if (!_newrecord) { buf2buf(_record,_newrecord); _newrecord.doinsert(); } else { buf2buf(_record,_newrecord); _newrecord.doupdate(); } } Причем doinsert() или doupdate() – ошибка все таже
Последний раз редактировалось propeller; 05.06.2010 в 23:01.
|
||
05.06.2010, 23:00
|
#5 |
Участник Регистрация: 29.09.2005 Адрес: Санкт-Петербург |
Проверьте счетчик RecID. Вполне возможно, что из-за прямых вмешательств в БД он не актуален. Ага, БД из 4.0 – все больше шансов что это именно счетчик RecId. Он хранится в таблице, на память не скажу, но название достаточно интуитивное. Таблицу надо смотреть в компании DAT.
__________________
Последний раз редактировалось Ivanhoe; 05.06.2010 в 23:03.
|
05.06.2010, 23:08
|
#6 |
Участник Регистрация: 29.09.2005 Адрес: Санкт-Петербург |
Таблица – SystemSequences, посмотреть можно через AOT – System Documentation.
__________________
|
05.06.2010, 23:12
|
#7 |
||
Участник
|
Цитата: Сообщение от Ivanhoe Ага, БД из 4.0 – все больше шансов что это именно счетчик RecId. Он хранится в таблице, на память не скажу, но название достаточно интуитивное. Таблицу надо смотреть в компании DAT. Уже пробовал – В таблице SYSTEMSEQUENCES на скле нашел запись для этой таблицы (tableId = 77) для компании дат. и в поле NEXTVAL подставил единичку в начало. Перестартанул AOS все равно не помогло. Вот запись об этой таблице Причем, если я добавляю новую запись в справочник то она нормально транслируется в другую компанию. Если же делаю update() существующих записей то такая ошибка.
Последний раз редактировалось propeller; 05.06.2010 в 23:30.
|
||
06.06.2010, 11:38
|
#8 |
Участник |
propeller, поставьте для начала брейкпойнт в инфологе и посмотрите в этой точке, какая конкретно запись находится: с каким рекидом, в какой компании, и т.д. По-моему, это первое, что надо было сделать, чтобы начать разбирательство.
|
06.06.2010, 12:35
|
#9 |
||
Участник
|
Цитата: Сообщение от Bober propeller, поставьте для начала брейкпойнт в инфологе и посмотрите в этой точке, какая конкретно запись находится: с каким рекидом, в какой компании, и т.д. По-моему, это первое, что надо было сделать, чтобы начать разбирательство. Это я и делал в первую очередь. Еcли _newrecord.doupdate() ([s]\Classes\xRecord\doupdate) ошибка: Не понимаю причем тут update() и запись уже существует… Существует и надо ее обновить..
Последний раз редактировалось propeller; 06.06.2010 в 13:06.
|
||
06.06.2010, 13:55
|
#10 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Цитата: Сообщение от propeller Запись эта находится в консолидирующей компании. Ну вот и ответ. В консолидирующей компании можно создавать очень ограниченный набор записей. Клиенты к ним не относятся.
|
06.06.2010, 14:02
|
#11 |
||
Участник
|
Цитата: Сообщение от Raven Melancholic Ну вот и ответ. В консолидирующей компании можно создавать очень ограниченный набор записей. Клиенты к ним не относятся. Что вы имеете ввиду?
|
||
06.06.2010, 14:55
|
#12 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Я имею ввиду, что если в компании в параметрах главной книги стоит флаг “Консолидирующая компания”, то создать там запись клиента не особенно получится.
|
06.06.2010, 17:54
|
#13 |
||
Участник
|
Цитата: Сообщение от Raven Melancholic Я имею ввиду, что если в компании в параметрах главной книги стоит флаг “Консолидирующая компания”, то создать там запись клиента не особенно получится. Нет, флаг не стоит.
|
||
06.06.2010, 18:51
|
#14 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Цитата: Сообщение от propeller Нет, флаг не стоит Цитата: Сообщение от propeller Запись эта находится в консолидирующей компании. В Аксе есть четкое определение консолидирующей компании, если флаг не стоит, то это с точки зрения Аксы не является консолидирующей компанией, поэтому расскажи ,что значит консолидирующая компания с вашей точки зрения – стандарт, судя по всему, к вам не относится.
|
06.06.2010, 19:10
|
#15 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Кстати. Для Ax30 была рекомендация при смене компании обнулять переменную, то есть, делать что-то подобное: X++: changeCompany('XXX') { { custTable = null; } В DAX4 часто этого недостаточно. Если только что искали запись по коду и после этого переключились в другую компанию и ищем по тому же коду, то обнуление переменной может не сработать!!! Как-то странно работает кэш. Не знаю как в 2009, но в DAX4 кроме обнуления переменной нужно еще запретить кеширование, то есть делать что-то подобное: X++: changeCompany('XXX') { { custTable = null; custTable.disabeCash(true); } Причем, обнаружил эту особенность на таблице InventTrans, которая вообще не кэшируется! Получилось так, что в результате работы механизма Интеркомпани – получлось так, что лот в одной компании (по которому только что был произведен поиск, совпал с лотом в другой компании, для которой ищем лот). Если в одной компании нашли лот, то после переключения на другую компанию этот лот не ищется, а возвращается запись из первой компании!!!
|
06.06.2010, 19:25
|
#16 |
Участник Регистрация: 21.03.2005 Адрес: Москва-Петушки |
Для примера, работал примерно такой код: X++: InventTrans inventTrans;
InvenTrans inventTransNew;
InventTransId transId = 'Скл000001';
// Работеем в компании AAA X++: select firstOnly inventTrans where inventTrans.InventTransId == transId; if (inventTrans ) { changeCompany('BBB') { inventTransNew = null; select firstOnly inventTransNew where inventTransNew.InventTransId == transId; } } Лот с номером ”Скл000001′ есть как в компании AAA, так и в компании BBB. В вышеприведеном коде в inventTransNew попадала та же запись, что и в inventTrans!!! X++: select firstOnly inventTrans where inventTrans.InventTransId == transId; if (inventTrans ) { changeCompany('BBB') { inventTransNew = null; inventTransNew .disableCach(true); select firstOnly inventTransNew where inventTransNew rans.InventTransId == transId; } } то в компании BBB находилась запись именно этой компании, а не AAA.
Последний раз редактировалось Raven Melancholic; 06.06.2010 в 19:28.
|
07.06.2010, 09:36
|
#17 |
||
Участник
Регистрация: 12.10.2004 Адрес: Москва Записей в блоге: 2 |
Цитата: Сообщение от Raven Melancholic Причем, обнаружил эту особенность на таблице InventTrans, которая вообще не кэшируется! Получилось так, что в результате работы механизма Интеркомпани – получлось так, что лот в одной компании (по которому только что был произведен поиск, совпал с лотом в другой компании, для которой ищем лот). Если в одной компании нашли лот, то после переключения на другую компанию этот лот не ищется, а возвращается запись из первой компании!!!
Судя по описанным симптомам, могло повлиять вот это : Правда у меня все это воспроизводилось в Ax 3.0
|
||
Интернет уже видел множество сканеров портов разного функционала, скорости и информативности. Не обошел и я данную тему стороной. Но, попытался дополнить ее небольшим, хоть и не секретным, но, почему-то очень редко встречающимися функционалом, а именно, захватом баннеров. Конечно же, делать так не нужно, и вся информация в статье только лишь для ознакомления. А поможет нам в создании сканера портов, конечно же, Python.
Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.
В широком смысле, захват баннеров, это метод, который нередко используется для того, чтобы получить информацию о запущенных службах на целевой системе. В данном контексте, баннер, это сообщение, которое размещается на хосте и как правило выполняет роль приветствия или предоставления минимальной информации о версиях используемых служб и программных продуктов.
Из посторонних библиотек – ничего. Из тех, что идут в комплекте с python, импортируем socket и threading. Давайте выполним импорт библиотек в скрипт.
import socket
import threading
Тут нужно сказать, что я решил сделать из данного сканера класс. Уж не знаю, зачем, может быть для того, чтобы можно было одновременно попытаться просканировать несколько сайтов. Тут еще надо подумать. Но, хотелось бы сказать, что с созданием класса использование функционала в коде стало намного легче и проще. Достаточно создать экземпляр класса, передать параметры и запустить функции.
Создание класса для сканирования портов
def __init__(self, target: str, port_counts: int):
"""
При инициализации класса требуется передать ему адрес для сканирования и кол-во портов,
которые требуется просканировать.
:ip - обработанный адрес для сканирования, в котором,
в случае невозможности получения адреса содержится None.
:banners_port - словарь, в котором содержаться все открытые ip-порты и баннеры
полученные от сервисов запущенных на них.
:open_port - словарь, содержащий все открытые порты без баннеров.
:param target: Полученный от пользователя адрес для сканирования. Тип: строка.
:param port_counts: Полученное от пользователя кол-во портов для сканирования. Тип: целое число.
"""
self.target = target
self.ip = self.check_target()
self.port_counts = int(port_counts)
self.banners_port = dict()
self.open_port = dict()
def check_target(self):
"""
Производится обработка поступившего от пользователя адреса.
Удаление http(https) для дальнейшего получения ip домена.
Запрашивается ip-адрес, который возвращается из функции,
если его удалось получить. Если нет, возвращается None.
:return: Если не возникает исключения, возвращается ip-адрес,
иначе возвращается None
"""
if self.target.startswith("http"):
self.target = self.target.split("/")[2]
try:
ip_domain = socket.gethostbyname(self.target)
return ip_domain
except socket.gaierror:
return
Создадим функцию для сканирования порта, scan_port(self, port: int). На вход она принимает порт. Адрес цели у нас глобален в пределах класса. Создаем объект сокета с соединением по TCP.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Выставляем таймаут соединения в 5 секунд. Этого будет достаточно, чтобы получить ответ от большинства сервисов. Проверяем, если ip не получен, выходим из функции.
s.settimeout(5)
if self.ip is None:
return
Создаем соединение на полученный ранее ip и полученный на входе порт. Получаем баннер. Обернем функцию получения баннера в блок try – except. Для начала проверяем, не является ли ответ полученный от сервиса пустым. Если да, обновляем словарь открытых портов без баннеров, пытаемся получить с помощью вспомогательной функции название сервиса работающего на данном порту.
Если баннер получен, добавляем информацию в словарь открытых портов с баннерами. В случае ошибки получения баннера и возникновения исключения, также добавляем информацию об открытом порте в словарь отрытых портов без баннеров. Если возникает ошибка декодирования, добавляем в словарь портов с баннерами не декодированную информацию.
Ну и если возникают ошибки соединения, просто выходим из функции.
def scan_port(self, port: int):
"""
В функции выполняется подключение к ip-адресу на полученный
порт. Если подключение удалось, значит порт открыт. Производится
попытка получить баннер. Если баннер не получен, обрабатывается
исключение, в котором идет запрос службы на порту у функции за
пределами класса. Если получить службу не удалось, возвращается
unassigned. Полученные значения добавляются в словари для
последующей обработки. В словарь с баннерами добавляются значения,
если баннер получен. В остальных случаях, в словарь открытых портов.
:param port: Номер порта для сканирования. Целое число
:return: Возвращается None. Служит для выхода из функции
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
if self.ip is None:
return
try:
s.connect((self.ip, port))
try:
banner = s.recv(1024).decode().strip()
if banner == '':
self.open_port.update({port: get_serv(port).upper()})
else:
self.banners_port.update({port: banner})
except OSError:
self.open_port.update({port: get_serv(port).upper()})
except UnicodeDecodeError:
banner = s.recv(1024).strip()
self.banners_port.update({port: banner})
except (socket.timeout, ConnectionRefusedError, OSError):
return
Запуск потоков для сканирования портов
Создадим функцию для запуска потоков сканирования портов port_rotate(self). Создадим список, в который будем добавлять запущенные потоки. Запускаем цикл по диапазону портов от 1 до полученного от пользователя. Создаем экземпляр потока с целевой функцией и параметрами для передачи, в данном случае, передать нужно порт. Указываем, что поток запустится как демон, стартуем поток и добавляем в список.
def port_rotate(self):
"""
Служит для перебора в цикле полученных от пользователя
портов и передачи их в функцию сканирования, где производится
их обработка.
:return: Ничего не возвращает.
"""
threads = []
for port in range(1, self.port_counts+1):
t = threading.Thread(target=self.scan_port, kwargs={'port': port})
t.daemon = True
t.start()
threads.append(t)
time.sleep(0.02)
for thread in threads:
thread.join()
Вспомогательная функция для получения названия сервиса на порту
Создадим вспомогательную функцию get_serv(port). На вход она принимает порт, информацию о котором нужно получить. С помощью функции сокетов getservbyport выполняется попытка получения названия сервиса. Если она не удается, возвращается ‘unassigned’, если же название сервиса получено, возвращается название сервиса.
def get_serv(port):
"""
Запрашивается название сервиса по порту. Если название получено,
оно возвращается в функцию сканирования портов. Если нет, обрабатывается
исключение и возвращается 'unassigned'.
:param port: Порт для запроса имени сервиса. Целое число.
:return: Возвращается сервис, если получен. В противном случае
возвращается 'unassigned'.
"""
try:
return socket.getservbyport(port)
except OSError:
return 'unassigned'
import socket
import threading
import time
def get_serv(port):
"""
Запрашивается название сервиса по порту. Если название получено,
оно возвращается в функцию сканирования портов. Если нет, обрабатывается
исключение и возвращается 'unassigned'.
:param port: Порт для запроса имени сервиса. Целое число.
:return: Возвращается сервис, если получен. В противном случае
возвращается 'unassigned'.
"""
try:
return socket.getservbyport(port)
except OSError:
return 'unassigned'
class PortScan:
"""
Класс для сканирования портов локального или удаленного компьютера.
Из зависимостей требует библиотеку socket, а также наличие интернета,
так как некоторые запросы обрабатываются с его помощью.
"""
def __init__(self, target: str, port_counts: int):
"""
При инициализации класса требуется передать ему адрес для сканирования и кол-во портов,
которые требуется просканировать.
:ip - обработанный адрес для сканирования, в котором,
в случае невозможности получения адреса содержится None.
:banners_port - словарь, в котором содержатся все открытые ip-порты и баннеры
полученные от сервисов запущенных на них.
:open_port - словарь, содержащий все открытые порты без баннеров.
:param target: Полученный от пользователя адрес для сканирования. Тип: строка.
:param port_counts: Полученное от пользователя кол-во портов для сканирования. Тип: целое число.
"""
self.target = target
self.ip = self.check_target()
self.port_counts = int(port_counts)
self.banners_port = dict()
self.open_port = dict()
def check_target(self):
"""
Производится обработка поступившего от пользователя адреса.
Удаление http(https) для дальнейшего получения ip домена.
Запрашивается ip-адрес, который возвращается из функции,
если его удалось получить. Если нет, возвращается None.
:return: Если не возникает исключения, возвращается ip-адрес,
иначе возвращается None
"""
if self.target.startswith("http"):
self.target = self.target.split("/")[2]
try:
ip_domain = socket.gethostbyname(self.target)
return ip_domain
except socket.gaierror:
return
def scan_port(self, port: int):
"""
В функции выполняется подключение к ip-адресу на полученный
порт. Если подключение удалось, значит порт открыт. Производится
попытка получить баннер. Если баннер не получен, обрабатывается
исключение, в котором идет запрос службы на порту у функции за
пределами класса. Если получить службу не удалось, возвращается
unassigned. Полученные значения добавляются в словари для
последующей обработки. В словарь с баннерами добавляются значения,
если баннер получен. В остальных случаях, в словарь открытых портов.
:param port: Номер порта для сканирования. Целое число
:return: Возвращается None. Служит для выхода из функции
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
if self.ip is None:
return
try:
s.connect((self.ip, port))
try:
banner = s.recv(1024).decode().strip()
if banner == '':
self.open_port.update({port: get_serv(port).upper()})
else:
self.banners_port.update({port: banner})
except OSError:
self.open_port.update({port: get_serv(port).upper()})
except UnicodeDecodeError:
banner = s.recv(1024).strip()
self.banners_port.update({port: banner})
except (socket.timeout, ConnectionRefusedError, OSError):
return
def port_rotate(self):
"""
Служит для перебора в цикле полученных от пользователя
портов и передачи их в функцию сканирования, где производится
их обработка.
:return: Ничего не возвращает.
"""
threads = []
for port in range(1, self.port_counts+1):
t = threading.Thread(target=self.scan_port, kwargs={'port': port})
t.daemon = True
t.start()
threads.append(t)
time.sleep(0.02)
for thread in threads:
thread.join()
Использование созданного класса
Функция для сканирования портов
Теперь давайте используем созданный класс в деле. Создадим новый файл main.py. В него импортируем из модуля с созданным нами классом, собственно класс.
from portscan_v import PortScan
target = input('\n[*] Введите домен или IP-адрес для сканирования >>> ')
port_count = int(input('[*] Введите количество портов для сканирования (1000 по умолчанию) >>> ') or 1000)
print('\n- Сканирую...', end='')
scan = PortScan(target, port_count)
scan.port_rotate()
print('\r- Выполнено', end='')
Дальше идут проверки. Если функция сканирования вернула нам оба пустых словаря, пишем, что ничего не найдено и выходим из функции. Если же не пустой хоть один словарь, перебираем и выводим информацию в терминал.
if len(banners) == 0 and len(o_port) == 0:
print('Открытых портов не найдено.')
return
else:
if target.startswith("http"):
target_print = target.split("/")[2]
print(f'\n\nСВОДНАЯ ИНФОРМАЦИЯ ПО ДОМЕНУ (IP): {target_print}\n{"*"*50}')
else:
print(f'\n\nСВОДНАЯ ИНФОРМАЦИЯ ПО ДОМЕНУ (IP): {target}\n{"*"*50}')
for bann in banners:
print(f' Порт: {bann:5} Баннер: {banners[bann]}')
for o in o_port:
print(f' Порт: {o:5} Сервис: {o_port[o]}')
from portscan_v import PortScan
def main():
target = input('\n[*] Введите домен или IP-адрес для сканирования >>> ')
port_count = int(input('[*] Введите количество портов для сканирования (1000 по умолчанию) >>> ') or 1000)
print('\n- Сканирую...', end='')
scan = PortScan(target, port_count)
scan.port_rotate()
print('\r- Выполнено', end='')
banners = scan.banners_port
o_port = scan.open_port
if len(banners) == 0 and len(o_port) == 0:
print('Открытых портов не найдено.')
return
else:
if target.startswith("http"):
target_print = target.split("/")[2]
print(f'\n\nСВОДНАЯ ИНФОРМАЦИЯ ПО ДОМЕНУ (IP): {target_print}\n{"*"*50}')
else:
print(f'\n\nСВОДНАЯ ИНФОРМАЦИЯ ПО ДОМЕНУ (IP): {target}\n{"*"*50}')
for bann in banners:
print(f' Порт: {bann:5} Баннер: {banners[bann]}')
for o in o_port:
print(f' Порт: {o:5} Сервис: {o_port[o]}')
if __name__ == "__main__":
main()
Для примера, ниже показан результат работы функции сканирования портов на тестовой машине Metasploitable2.
Таким образом, из полученной информации можно сделать вывод как об операционной системе, на которой работает данная машина, так и о сервисах, которые на ней запущены. Это, скажем так, простое сканирование с захватом баннеров. Надо сказать, что далеко не на всех доменах вам удастся получить столь подробную информацию, но, если данная информация будет получена, вы уже можете ее использовать по назначению. К примеру, по полученному баннеру сервиса можно попытаться узнать, существует ли уязвимость для данной версии, а также эксплоит к ней.
А на этом, пожалуй, все.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна