Compare commits

..

3 Commits

Author SHA1 Message Date
alan 551e8db291 Обновить README.md 2025-03-17 18:47:20 +10:00
alan 9b727a93b2 Обновить .gitignore
правильное игнорирование
2025-03-17 18:46:36 +10:00
alan 7b0ee88293 Initial commit 2025-03-17 18:42:14 +10:00
6 changed files with 3 additions and 976 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
__pycache__/
unloading/
**/__pycache__/
/unloading/
+1 -134
View File
@@ -1,136 +1,3 @@
# bird_list_ip
Это скрипт написанный на Python3 для выгрузки пулов ip адресов, для bird2.
# Установка (Debian)
-- устанавливаем пакеты
> sudo apt install -y git bird2 m4
-- добавляем поддержку brotli и requests в python3
> apt -y install python3-pip\
> pip install brotli requests 2> /dev/null || pip install brotli requests --break-system-packages
-- клонируем репозиторий
> git clone https://git.alanbox.ru/alan/bird_list_ip.git /opt/bird_list_ip && chmod +x /opt/bird_list_ip/download.py
-- правим Unit Systemd bird2 для работы с m4
> nano /usr/lib/systemd/system/bird.service
> [Unit]\
> Description=BIRD Internet Routing Daemon\
> After=network.target\
>\
> [Service]\
> EnvironmentFile=/etc/bird/envvars\
> ExecStartPre=/bin/sh -c "/usr/bin/m4 /opt/bird > /etc/bird/bird.conf"\
> ExecStartPre=-/usr/lib/bird/prepare-environment\
> ExecStartPre=-/usr/sbin/bird -p\
> ExecReload=/bin/sh -c "/usr/bin/m4 /opt/bird > /etc/bird/bird.conf"\
> ExecReload=-/usr/sbin/birdc configure\
> ExecStart=/usr/sbin/bird -f -u $BIRD_RUN_USER -g $BIRD_RUN_GROUP $BIRD_ARGS\
> Restart=on-abort\
>\
> [Install]\
> WantedBy=multi-user.target\
-- пример конфигурации bird2 на m4, для работы со списками
> nano /opt/bird
> log syslog {error, fatal};\
> router id ~~**1.1.1.1**~~;\
>\
> protocol device {\
> }\
>\
> protocol direct {\
>     ipv4; # Connect to default IPv4 table\
>     #ipv6; # ... and to default IPv6 table\
> }\
>\
> protocol kernel {\
>     learn;\
>     merge paths on;\
>     ipv4 { # Connect protocol to IPv4 table by channel\
>         import none; # Import to table, default is import all\
>         export filter { if (net.len > 0 && source=RTS_BGP) then { accept; } reject; }; # Export to protocol. default is export none\
>     };\
> }\
>\
> \# SUB m4\
> define(\`LOCATION', \`~~**741**~~')\
> include(\`/opt/bird_list_ip/unloading/bird2_v4.m4')\
>\
> filter border_in {\
>     if (net ~ 0.0.0.0/32) then { reject; }\
>     if (net ~ 127.0.0.0/8) then { reject; }\
>     if (net ~ 169.254.0.0/16) then { reject; }\
>     if (net ~ 224.0.0.0/4) then { reject; }\
>     if (net ~ 240.0.0.0/4) then { reject; }\
>     if (net.len > 0) then { accept; }\
>     reject;\
> }\
>\
> filter border_out {\
>     if (net ~ 0.0.0.0/32) then { reject; }\
>     if (net ~ 127.0.0.0/8) then { reject; }\
>     if (net ~ 10.0.0.0/8) then { reject; }\
>     if (net ~ 172.16.0.0/12) then { reject; }\
>     if (net ~ 192.168.0.0/16) then { reject; }\
>     if (net ~ 169.254.0.0/16) then { reject; }\
>     if (net ~ 224.0.0.0/4) then { reject; }\
>     if (net ~ 240.0.0.0/4) then { reject; }\
>     accept;\
> }\
>\
> define(\`BGP_BORDER', \`\
> protocol bgp $1 {\
>     ipv4 {\
>         import filter border_in;\
>         export filter border_out;\
>         next hop self;\
>     };\
>     router id $2;\
>     source address $2;\
>     local $2 as ~~**65431**~~;\
>     neighbor $3 as ~~**65949**~~;\
>     hold time 90;\
>     keepalive time 60;\
>     passive off;\
>     multihop;\
>     bfd no;\
> }\
> ')\
>\
> BGP_BORDER(\`CLIENT1', \`~~**10.8.1.1**~~', \`~~**10.8.1.2**~~')\
> BGP_BORDER(\`CLIENT1', \`~~**10.8.2.1**~~', \`~~**10.8.2.2**~~')\
> ...\
> BGP_BORDER(\`CLIENT99', \`~~**10.8.99.1**~~', \`~~**10.8.99.2**~~')
~~**Тык**~~ - это требует вашего внимания, для вашей конфигурации bird2
-- добавляем задачу в cron, обновление раз в неделю в среду в 5 утра
> crontab -e
> 0 5 * * 3 /usr/bin/python3 /opt/bird_list_ip/download.py > /dev/null 2>&1 # обновление списков ip адресов
-- моя рекомендация cron, если у вас идет выгрузка по спискам RKN
> crontab -e
> 0 5 * * * /usr/bin/python3 /opt/bird_list_ip/download.py RKN > /dev/null 2>&1 # обновление списков ip адресов RKN\
> 5 5 10 * * /usr/bin/python3 /opt/bird_list_ip/download.py -RKN > /dev/null 2>&1 # обновление списков ip адресов
-- запуск загрузки в ручную
> python3 /opt/bird_list_ip/download.py # выгружаем все списки\
> python3 /opt/bird_list_ip/download.py RU # выгружаем конкретный список\
> python3 /opt/bird_list_ip/download.py RU JAPAN KOREA # выгружаем перечисленные списки\
> python3 /opt/bird_list_ip/download.py -RU # выгружаем все списки, кроме RU\
> python3 /opt/bird_list_ip/download.py -RU JAPAN KOREA # выгрузит списки JAPAN KOREA, -RU будет проигнорирован\
> python3 /opt/bird_list_ip/download.py -RU jaPAn korea # допускается указывать ключ в любом регистре
-- добавляем bird2 в автозагрузку и запускаем
> systemctl daemon-reload && systemctl enable bird.service && systemctl start bird.service
**#####################################################################**\
    Пример конфигурация списка выгрузки можно посмотреть в файле list в репозитории,\
    так же допускается указывать url ссылку на конфигурацию первой строкой в файле list
Это скрипт написанный на Python3 для выгрузки пулов ip адресов по номерам AS.
-398
View File
@@ -1,398 +0,0 @@
#!/usr/bin/python3
import re
import os
import sys
import ast
import requests
import ipaddress
from include import net_tree
from collections import defaultdict
from time import sleep as time_sleep
from shutil import get_terminal_size
from include.http_header import get_headers
# компилируем регулярку поиска ipv4 адреса
ipv4_find_str=re.compile(r"(?<![0-9.])(?!10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)((?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]|[0-9])\.(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]|[0-9])\.(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]|[0-9]))(?:/(3[0-2]|[12][0-9]|[1-9]))?(?![0-9.])")
# компилируем регулярку поиска ipv6 адреса
ipv6_find_str=re.compile(r'(?<![0-9A-Fa-f:])([23][0-9A-Fa-f]{3}(?:(?::[0-9A-Fa-f]{1,4}){0,6}|(?:::[0-9A-Fa-f]{0,4})?)(?::[0-9A-Fa-f]{0,4})*)(?:/([1-9][0-9]?|1[01][0-9]|12[0-8]))?(?![0-9A-Fa-f:])')
# метод вывода прогресса на экран
def progres_print(lstr:list, mtype:int=0, ss:float=0.3):
"""
Метод вывода на экран прогресса выполнения
"""
term_width = get_terminal_size().columns
mtype: str = '33' if mtype == 1 else '31' if mtype == 2 else '32'
cstr: str = f"\033[{mtype}m | \033[0m".join(lstr)[:term_width]
print(f"\r{' ' * term_width}\r\033[{mtype}m>>>\033[0m {cstr}", end='', flush=True)
time_sleep(ss)
# метод сбора set адресов ipv4 из текста
def ipv4_find(strip:str, size:int):
"""
Метод сбора set адресов ipv4 из текста
возвращает set ip
где:
значение - [адрес в int формате, размер сети]
"""
ips=set()
for c in ipv4_find_str.finditer(strip):
ip_str = c.group(1)
prefix_str = c.group(2)
# определяем префикс
if (prefix:=int(prefix_str) if prefix_str else 32) > size: continue
# проверка корректности IPv4
try:
ips.add((int(ipaddress.IPv4Address(ip_str)), prefix))
except (ValueError, ipaddress.AddressValueError):
continue
return ips
# метод сбора set адресов ipv6 из текста
def ipv6_find(strip:str, size:int):
"""
Метод сбора set адресов ipv6 из текста
возвращает set ip
где:
значение - [адрес в int формате, размер сети]
"""
ips=set()
for c in ipv6_find_str.finditer(strip):
ip_str = c.group(1)
prefix_str = c.group(2)
# определяем префикс
if (prefix:=int(prefix_str) if prefix_str else 128) > size: continue
# проверка корректности IPv6
try:
ips.add((int(ipaddress.IPv6Address(ip_str)), prefix))
except (ValueError, ipaddress.AddressValueError):
continue
return ips
# метод группировки словаря
def get_dict_groups(input:dict):
"""
Метод получения сгрупированных данных
community и ip адресов
возвращает словарь сгрупированных данных
данные встречающиеся один раз, не попадают в вывод
"""
# если словарь короче 2х
if len(input) < 2: return {}
# строим битовые маски
name_to_bit = {name: 1 << i for i, name in enumerate(input.keys())}
line_communities = defaultdict(set)
line_mask = defaultdict(int)
# походим по строкам и назначаем маску
for name, (communities, lines) in input.items():
bit = name_to_bit[name]
for line in lines:
line_mask[line] |= bit
# добавляем комьюнити этого списка к конкретной строке
line_communities[line].update(communities)
# группируем строки по маске
groups = defaultdict(list)
groups_communities = defaultdict(set)
for line, mask in line_mask.items():
groups[mask].append(line)
groups_communities[mask].update(line_communities[line])
# конвертируем маску в имена
bit_to_names = {}
for mask in groups.keys():
names = [name for name, bit in name_to_bit.items() if mask & bit]
list_name = "__".join(names)
bit_to_names[mask] = list_name
# возвращаем словарь сгрупированных данных
return { bit_to_names[mask]: [ groups_communities[mask], set(groups[mask]) ] for mask in groups if "__" in bit_to_names[mask] and groups_communities[mask] and groups[mask] }
# метод получения списка ip адресов
def list_ip(c_list: list = []):
"""
Метод получения списка ip адресов
возвращает кортеж из 2-х списков: ipv4 и ipv6
"""
try:
ipv4_list=set()
ipv6_list=set()
# определяем, будем сжимать или нет
compress=c_list[0].get('compress', True)
# какие типы обрабытываем, от какого размера
# по умолчанию:
# ipv4 обрабатываем (<=24)
# ipv6 игнорируем (<=64)
# какие типы обрабытываем, от какого размера
ipv4 = False if not (ipv4:=c_list[0].get('ipv4', True)) else (ipv4 if type(ipv4) is int else 24)
ipv6 = False if not (ipv6:=c_list[0].get('ipv6', False)) else (ipv6 if type(ipv6) is int else 64)
c_list_len=len(c_list)
# пробегаем словарь выгрузки
for i, c_dict in enumerate(c_list, start=1):
# прогрес %
percent = int(i / c_list_len * 100)
# если есть источник ссылка
if 'url' in list(c_dict):
# бежим весь список ссылок пока не код 200
for c_url in c_dict['url']:
progres_print([c_url, f"{percent}%"])
try:
session = requests.Session()
session.headers.update(get_headers())
if (result:=session.get(c_url, timeout=(5, 60), stream=True)) and result.status_code == 200 and result.text:
# пополняем словарь ipv4_list
if ipv4: ipv4_list.update(ipv4_find(result.text,ipv4))
# пополняем словарь ipv6_list
if ipv6: ipv6_list.update(ipv6_find(result.text,ipv6))
break
except requests.exceptions.RequestException: pass
progres_print([c_url, f"{percent}%"],1,1)
# если есть статичные записи ipv4
if ipv4 and 'static4' in list(c_dict):
progres_print(["StaticIPv4", f"{percent}%"])
# пополняем словарь ipv4_list
ipv4_list.update(ipv4_find(str(c_dict['static4']),ipv4))
# если есть статичные записи ipv6
if ipv6 and 'static6' in list(c_dict):
progres_print(["StaticIPv6", f"{percent}%"])
# пополняем словарь ipv6_list
ipv6_list.update(ipv6_find(str(c_dict['static6']),ipv6))
# сжимаем подсети ipv4
if ipv4_list:
# создаем дерево
Root = net_tree.Node(net_tree.Net(0, 0, 4))
# добавляем IPv4 подсети
for ip_int, mask in sorted(ipv4_list, key=lambda x: x[0]):
Root.insert(net_tree.Net(ip_int, mask, 4))
# сжатие по CIDR, если ключ сжимать, иначе убираем только родителей, покрываемых детьми
ipv4_list = Root.export_compress('route {addr}/{masklen} blackhole;') if compress else Root.export('route {addr}/{masklen} blackhole;')
else:
ipv4_list:bool=False
# сжимаем подсети ipv6
if ipv6_list:
# строим дерево
Root = net_tree.Node(net_tree.Net(1 << 127, 0, 6))
# добавляем IPv6 подсети
for ip_int, mask in sorted(ipv6_list, key=lambda x: x[0]):
Root.insert(net_tree.Net(ip_int, mask, 6))
# сжатие по CIDR, если ключ сжимать, иначе убираем только родителей, покрываемых детьми
ipv6_list = Root.export_compress('route {addr}/{masklen} blackhole;') if compress else Root.export('route {addr}/{masklen} blackhole;')
else:
ipv6_list:bool=False
# возвращаем 2 списка маршрутов
return ipv4_list, ipv6_list
except Exception as e:
# исключение
print(f"\nОшибка: {e}")
return False, False
# метод анализа элементов списка (аргументов)
def right_list(ip_list: list, args_list: list=[]):
"""
Метод анализа элементов списка выгрузок, возвращает актульный список выгрузок,
основываясь на списке аргументов, переданных вторым параметром
"""
# собираем словарь аргументов
# элементы списка начинающиеся с "-"
# попадают в off, остальные в on
c_dict = defaultdict(list)
for c in args_list:
key = "off" if c[0] == "-" else "on"
value = c[1:].upper() if c[0] == "-" else c.upper()
c_dict[key].append(value)
c_dict = dict(c_dict)
# если словарь аргументов не пустой
if c_dict:
# пробегаем список выгрузок
for c in ip_list[:]:
# пропускаем
if len(c_dict) == 2 and c in c_dict["on"] and not c in c_dict["off"]: continue
if len(c_dict) == 1 and (("on" in c_dict and c in c_dict["on"]) or ("off" in c_dict and not c in c_dict["off"])): continue
# удаляем элемент из списка
ip_list = list(filter(lambda x: x != c, ip_list))
return ip_list
# главная фукция
if __name__ == "__main__":
# словарь выгружаемых списков
ip_list = dict()
try:
# если файл list содержет json структуру, парсим его
with open(list_file:=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'list'), "r") as file:
ip_list = ast.literal_eval(file.read())
print(f"Список выгрузки (файл): {list_file}")
except (ValueError, SyntaxError):
try:
# если файл list ссылка, загружаем и парсим его
with open(list_file, "r") as file:
session = requests.Session()
session.headers.update(get_headers())
if (result:=session.get(url_list_file:=file.readline().strip(), timeout=(5, 5))) and result.status_code == 200 and result.text:
ip_list = ast.literal_eval(result.text)
print(f"Список выгрузки (url): {url_list_file}")
except requests.exceptions.RequestException:
print(f"Невалидный URL/ошибка выгрузки", file=sys.stderr)
sys.exit(1)
except (ValueError, SyntaxError):
print(f"Ошибочная структура json", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Ошибка: {e}", file=sys.stderr)
sys.exit(1)
except FileNotFoundError:
print(f"Файл со списками не найден", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Ошибка: {e}", file=sys.stderr)
sys.exit(1)
# проверяем на пустой список выгрузки
if (not ip_list):
print(f"Список выгрузки пустой", file=sys.stderr)
sys.exit(1)
# cловари для группировки
ipv4_dict=dict()
ipv6_dict=dict()
# список того, что будем выгружать/обновлять
download_ip_list=right_list(list(ip_list.keys()), sys.argv[1:])
# создаем дерриктори. для сохранения
outdir=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'unloading')
if not os.path.exists(outdir):
os.makedirs(outdir,exist_ok=True)
# создаём временный файл экспортируемой конфигурации, перед выгрузкой
open(ipv4_bird2_m4 := f"{outdir}/bird2_v4.m4.tmp", "w").close()
open(ipv6_bird2_m4 := f"{outdir}/bird2_v6.m4.tmp", "w").close()
# удаляем старые файлы группировок
[os.remove(path) for f in os.listdir(outdir) if "__" in f and os.path.isfile(path := os.path.join(outdir, f))]
# обходим массив списков для выгрузки
for clist, value in ip_list.items():
# имена выходых файлов
ipv4_out_file=f"{outdir}/{clist.lower()}_v4.txt"
ipv6_out_file=f"{outdir}/{clist.lower()}_v6.txt"
# извлекаем community
community=[c for c in value[0].get('community', "").split(",") if c]
# обновляем только указанные списки
if clist in download_ip_list:
print("")
# вычисляем кол-во записей прошлой выгрузки
ipv4_count_old = sum(1 for line in open(ipv4_out_file)) if os.path.isfile(ipv4_out_file) else 0
ipv6_count_old = sum(1 for line in open(ipv6_out_file)) if os.path.isfile(ipv6_out_file) else 0
# выполняем выгрузку
print(f"Выгрузка списка IP: {clist}")
ipv4_list, ipv6_list=list_ip(value)
# сохраняем ipv4
if ipv4_list and len(ipv4_list.splitlines()) >= ipv4_count_old * 0.5:
# сохраняем в файл
with open(ipv4_out_file, "w") as file:
file.write(ipv4_list)
progres_print([f"Файл {ipv4_out_file}", "сохранён"])
# сохраняем ipv6
if ipv6_list and len(ipv6_list.splitlines()) >= ipv6_count_old * 0.5:
# сохраняем в файл
with open(ipv6_out_file, "w") as file:
file.write(ipv6_list)
progres_print([f"Файл {ipv6_out_file}", "сохранён"])
# открываем файл выгрузки и пополняем словарь для группировки ipv4
if os.path.exists(ipv4_out_file):
with open(ipv4_out_file, "r") as file:
ipv4_dict[clist] = [community,list(file.readlines())]
# открываем файл выгрузки и пополняем словарь для группировки ipv6
if os.path.exists(ipv6_out_file):
with open(ipv6_out_file, "r") as file:
ipv6_dict[clist] = [community,list(file.readlines())]
print("\n\nКонфигурация Bird2:")
# обновляем временный файл конфигурации ipv4
# из группировок
if ipv4_dict:
for k,v in get_dict_groups(ipv4_dict).items():
# имена выходых файлов
ipv4_out_file=f"{outdir}/{k.lower()}_v4.txt"
# сохраняем в файл
with open(ipv4_out_file, "w") as file:
file.write("".join(v[1]))
progres_print([f"Группировка {ipv4_out_file}", "сохранена"])
# список комьюнити маршрутов
bgp_community=" ".join([f"bgp_community.add(({str(c).replace(':',',')}));" for c in sorted(v[0])])
with open(ipv4_bird2_m4, "a") as file:
file.write(f"protocol static static_{k.lower()}_v4 {{\n\tipv4 {{ import filter {{ {bgp_community} preference=400; accept; }}; }};\n\tinclude \"{ipv4_out_file}\";\n}}\n")
progres_print([f"В {ipv4_bird2_m4}", f"добавлен {k}"])
# обновляем временный файл конфигурации ipv6
# из группировок
if ipv6_dict:
for k,v in get_dict_groups(ipv6_dict).items():
# имена выходых файлов
ipv6_out_file=f"{outdir}/{k.lower()}_v6.txt"
# сохраняем в файл
with open(ipv6_out_file, "w") as file:
file.write("".join(v[1]))
progres_print([f"Группировка {ipv6_out_file}", "сохранена"])
# список комьюнити маршрутов
bgp_community=" ".join([f"bgp_community.add(({str(c).replace(':',',')}));" for c in sorted(v[0])])
with open(ipv6_bird2_m4, "a") as file:
file.write(f"protocol static static_{k.lower()}_v6 {{\n\tipv6 {{ import filter {{ {bgp_community} preference=400; accept; }}; }};\n\tinclude \"{ipv6_out_file}\";\n}}\n")
progres_print([f"В {ipv6_bird2_m4}", f"добавлен {k}"])
# дополняем временный файл конфигурации всей выгрузкой ipv4 и ipv6
for clist, value in ip_list.items():
# имена выходых файлов
ipv4_out_file=f"{outdir}/{clist.lower()}_v4.txt"
ipv6_out_file=f"{outdir}/{clist.lower()}_v6.txt"
# список комьюнити маршрутов
bgp_community=" ".join([f"bgp_community.add(({str(c).replace(':',',')}));" for c in sorted([c for c in value[0].get('community', "").split(",") if c])])
if os.path.exists(ipv4_out_file):
# фильтер маршрутов ipv4, если список ignore существует в конфигурации
ip_addresses_filter=set()
if (ignore:=value[0].get('ignore', [])):
ignore.append(f"-{clist}") # защита от фильтрации собственных маршрутов
for c in right_list(list(ip_list.keys()), ignore):
if os.path.exists(f_open:=f"{outdir}/{c.lower()}_v4.txt"):
with open(f_open, "r") as file:
ip_addresses_filter.update([line.strip().split()[1]+"+" for line in file])
with open(ipv4_bird2_m4, "a") as file:
bgp_filter=f"if net ~ [{','.join(ip_addresses_filter)}] then reject; " if ip_addresses_filter else ''
file.write(f"protocol static static_{clist.lower()}_v4 {{\n\tipv4 {{ import filter {{ {bgp_filter}{bgp_community} accept; }}; }};\n\tinclude \"{ipv4_out_file}\";\n}}\n")
progres_print([f"В {ipv4_bird2_m4}", f"добавлен {clist}"])
if os.path.exists(ipv6_out_file):
# фильтер маршрутов ipv6, если список ignore существует в конфигурации
ip_addresses_filter=set()
if (ignore:=value[0].get('ignore', [])):
ignore.append(f"-{clist}") # защита от фильтрации собственных маршрутов
for c in right_list(list(ip_list.keys()), ignore):
if os.path.exists(f_open:=f"{outdir}/{c.lower()}_v6.txt"):
with open(f_open, "r") as file:
ip_addresses_filter.update([line.strip().split()[1]+"+" for line in file])
with open(ipv6_bird2_m4, "a") as file:
bgp_filter=f"if net ~ [{','.join(ip_addresses_filter)}] then reject; " if ip_addresses_filter else ''
file.write(f"protocol static static_{clist.lower()}_v6 {{\n\tipv6 {{ import filter {{ {bgp_filter}{bgp_community} accept; }}; }};\n\tinclude \"{ipv6_out_file}\";\n}}\n")
progres_print([f"В {ipv6_bird2_m4}", f"добавлен {clist}"])
# проверяем, что временный файл конфигурации ipv6 не пустой, сохраняем в постоянный
if os.path.exists(ipv6_bird2_m4) and os.path.getsize(ipv6_bird2_m4) != 0:
os.replace(ipv6_bird2_m4, config_ipv6:=ipv6_bird2_m4.removesuffix(".tmp"))
progres_print([f"Конфиг {ipv6_bird2_m4}", f"перемещён в {os.path.basename(config_ipv6)}"])
else:
progres_print([f"Конфиг {ipv6_bird2_m4}", f"отсутствует/пуст"], 1)
# проверяем, что временный файл конфигурации ipv4 не пустой, сохраняем в постоянный
if os.path.exists(ipv4_bird2_m4) and os.path.getsize(ipv4_bird2_m4) != 0:
os.replace(ipv4_bird2_m4, config_ipv4:=ipv4_bird2_m4.removesuffix(".tmp"))
progres_print([f"Конфиг {ipv4_bird2_m4}", f"перемещён в {os.path.basename(config_ipv4)}"])
else:
progres_print([f"Конфиг {ipv4_bird2_m4}", f"отсутствует/пуст"], 1)
print("\n")
# реконфигурирование Bird2
os.system("systemctl reload bird.service >/dev/null 2>&1 || \
systemctl restart bird.service >/dev/null 2>&1 && \
echo '\\e[32mНовый конфиг Bird2 применён\\e[0m' || echo '\\e[31mBird2 error...\\e[0m'")
-96
View File
@@ -1,96 +0,0 @@
import random
def get_headers():
# ОС Chrome/Firefox
platforms = [
'Windows NT 10.0; Win64; x64',
'Windows NT 10.0; WOW64',
'Macintosh; Intel Mac OS X 10_15_7',
'X11; Linux x86_64',
]
# Chrome версии
chrome_major = random.randint(120, 128)
chrome_build = random.randint(6000, 9999)
chrome_patch = random.randint(10, 200)
chrome_ua = (
f"Mozilla/5.0 ({random.choice(platforms)}) "
f"AppleWebKit/537.36 (KHTML, like Gecko) "
f"Chrome/{chrome_major}.0.{chrome_build}.{chrome_patch} Safari/537.36"
)
# Firefox версии
ff_ver = random.randint(110, 125)
firefox_ua = (
f"Mozilla/5.0 ({random.choice(platforms)}; rv:{ff_ver}.0) "
f"Gecko/20100101 Firefox/{ff_ver}.0"
)
# Выбираем браузер
user_agent = random.choice([chrome_ua, firefox_ua])
# sec-ch-ua зависит только от Chrome
if "Chrome" in user_agent:
sec_ch_ua = f'"Not_A Brand";v="8", "Chromium";v="{chrome_major}", "Google Chrome";v="{chrome_major}"'
sec_ch_mob = "?0"
sec_platform = '"Windows"' if "Windows" in user_agent else '"macOS"' if "Macintosh" in user_agent else '"Linux"'
else:
# Firefox их не отправляет
sec_ch_ua = None
sec_ch_mob = None
sec_platform = None
# Accept-Language
accept_lang = random.choice([
"ru-RU,ru;q=0.9,en-US;q=0.8",
"ru-RU,ru;q=0.8,en-US;q=0.7",
"ru-RU;q=0.9,ru;q=0.8,en-US;q=0.7",
"ru-RU;q=0.8,ru;q=0.7,en-US;q=0.9",
"ru;q=0.9,en;q=0.8",
"ru;q=0.8,en;q=0.7",
"ru;q=0.7,en;q=0.9",
"en-US,en;q=0.9,ru-RU,ru;q=0.8",
"en-US,en;q=0.8,ru-RU,ru;q=0.9",
"en-US;q=0.9,ru-RU,ru;q=0.7",
"en-US;q=0.8,ru-RU,ru;q=0.9",
"en-US;q=0.7,ru-RU,ru;q=0.8",
"en;q=0.9,ru;q=0.8",
"en;q=0.8,ru;q=0.9",
"en;q=0.7,ru;q=0.7",
])
# Реалистичные fetch-заголовки Chrome
sec_fetch_site = random.choice(["none", "same-site", "same-origin", "cross-site"])
sec_fetch_mode = "navigate"
sec_fetch_user = "?1"
sec_fetch_dest = "document"
# Заголовки в случайном порядке как в браузере
headers_list = [
("User-Agent", user_agent),
("Accept", "text/html,application/json,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"),
("Accept-Language", accept_lang),
("Accept-Encoding", "gzip, deflate, br, zstd"),
("Connection", "keep-alive"),
("Upgrade-Insecure-Requests", "1"),
]
if sec_ch_ua:
headers_list.extend([
("sec-ch-ua", sec_ch_ua),
("sec-ch-ua-mobile", sec_ch_mob),
("sec-ch-ua-platform", sec_platform),
("Sec-Fetch-Site", sec_fetch_site),
("Sec-Fetch-Mode", sec_fetch_mode),
("Sec-Fetch-User", sec_fetch_user),
("Sec-Fetch-Dest", sec_fetch_dest),
])
# Перемешиваем порядок (!) — браузеры могут менять порядок
random.shuffle(headers_list)
# Превращаем в dict
headers = {k: v for k, v in headers_list}
return headers
-291
View File
@@ -1,291 +0,0 @@
#
# CIDR AGGREGATOR (IPv4 + IPv6)
#
def mask_to_int(mask_size, total_bits):
return ((1 << total_bits) - 1) ^ ((1 << (total_bits - mask_size)) - 1)
def ip_volume(mask_size, total_bits):
return 1 << (total_bits - mask_size)
class Net:
__slots__ = ["version","bits","mask_size","net","mask","volume"]
def __init__(self, net: int, mask_size: int, version: int=4):
self.version, self.bits = (4, 32) if version==4 else (6, 128)
self.mask_size = mask_size
self.mask = mask_to_int(mask_size, self.bits)
self.net = net & self.mask
self.volume = ip_volume(mask_size, self.bits)
#
# --- IP CONVERSION ---
#
def __int_to_ipv4(self, n):
return ".".join(str((n >> (24 - 8*i)) & 0xFF) for i in range(4))
def __int_to_ipv6(self, n):
blocks = [(n >> (112 - 16*i)) & 0xFFFF for i in range(8)]
best_start = -1
best_len = 0
cur_start = -1
cur_len = 0
for i in range(8):
if blocks[i] == 0:
if cur_start < 0:
cur_start = i
cur_len = 1
else:
cur_len += 1
else:
if cur_len > best_len:
best_len = cur_len
best_start = cur_start
cur_start = -1
cur_len = 0
if cur_len > best_len:
best_len = cur_len
best_start = cur_start
if best_len > 1:
new = []
i = 0
while i < 8:
if i == best_start:
new.append('')
i += best_len
else:
new.append(format(blocks[i], 'x'))
i += 1
res = ":".join(new)
while ":::" in res:
res = res.replace(":::", "::")
return res
return ":".join(format(b, 'x') for b in blocks)
def __int_to_ip(self, n):
return self.__int_to_ipv4(n) if self.version == 4 else self.__int_to_ipv6(n)
#
# --- PUBLIC API ---
#
def getAsString(self, fmt='{addr}/{masklen}'):
return fmt.format(
addr=self.__int_to_ip(self.net),
masklen=self.mask_size
)
def is_adjacent(self, other):
if self.version != other.version: return False
if self.mask_size != other.mask_size: return False
step = 1 << (self.bits - self.mask_size)
return self.net + step == other.net or other.net + step == self.net
def supernet(self):
if self.mask_size == 0:
return self
new_mask = self.mask_size - 1
new_mask_int = mask_to_int(new_mask, self.bits)
new_net = self.net & new_mask_int
return Net(new_net, new_mask, self.version)
class Node:
__slots__ = [
"net", "child0", "child1",
"is_real",
"real_volume", "real_count",
"fake_volume", "weight", "max_child_weight"
]
def __init__(self, net: Net):
self.net = net
self.child0 = None
self.child1 = None
self.is_real = False
self.real_volume = 0
self.real_count = 0
self.fake_volume = 0
self.weight = 0
self.max_child_weight = 0
#
# INSERT NETWORK INTO TRIE
#
def insert(self, new_net: Net):
return self.__insert(new_net, level=0)
def __insert(self, new_net: Net, level):
# если дошли до маски сети — это лист
if level == new_net.mask_size:
if not self.is_real:
self.is_real = True
self.child0 = None
self.child1 = None
return
# разбираем бит адреса
bit_pos = self.net.bits - 1 - level
direction = (new_net.net >> bit_pos) & 1
if direction == 0:
if not self.child0:
child_net = Net(new_net.net & mask_to_int(level+1, self.net.bits), level+1, self.net.version)
self.child0 = Node(child_net)
self.child0.__insert(new_net, level+1)
else:
if not self.child1:
child_net = Net(new_net.net & mask_to_int(level+1, self.net.bits), level+1, self.net.version)
self.child1 = Node(child_net)
self.child1.__insert(new_net, level+1)
#
# CALCULATE WEIGHTS
#
def finalize(self):
if self.is_real:
self.real_volume = self.net.volume
self.real_count = 1
self.fake_volume = 0
self.weight = 0
self.max_child_weight = 0
return
self.real_volume = 0
self.real_count = 0
self.fake_volume = 0
self.max_child_weight = 0
for ch in (self.child0, self.child1):
if ch:
ch.finalize()
self.real_volume += ch.real_volume
self.real_count += ch.real_count
self.fake_volume += ch.fake_volume
self.max_child_weight = max(self.max_child_weight, ch.weight, ch.max_child_weight)
self.__recalc()
def __recalc(self):
missing = self.net.volume - self.real_volume - self.fake_volume
if missing > 0:
self.weight = (self.real_count - 1) / (missing ** 0.5)
else:
self.weight = float('inf')
#
# COLLAPSE / AGGREGATE
#
def collapse(self, min_weight=0, max_delta=float('inf')):
if self.is_real:
return 0,0
delta = 0
fake = 0
# сворачиваем детей
for ch in (self.child0, self.child1):
if ch:
d, f = ch.collapse(min_weight, max_delta - delta)
delta += d
fake += f
# попытаемся объединить
if self.child0 and self.child1:
c0 = self.child0
c1 = self.child1
if (c0.is_real and c1.is_real and
c0.net.is_adjacent(c1.net)):
super_net = c0.net.supernet()
# превращаем текущий узел в супернет
self.net = super_net
self.is_real = True
self.child0 = None
self.child1 = None
self.real_volume = c0.real_volume + c1.real_volume
self.fake_volume = super_net.volume - self.real_volume
self.real_count = 1
self.weight = 0
self.max_child_weight = 0
return delta + 2, fake + self.fake_volume
# пересчитываем статистику
if not self.is_real:
self.real_volume = 0
self.real_count = 0
self.fake_volume = 0
self.max_child_weight = 0
for ch in (self.child0, self.child1):
if ch:
self.real_volume += ch.real_volume
self.real_count += ch.real_count
self.fake_volume += ch.fake_volume
self.max_child_weight = max(self.max_child_weight, ch.weight, ch.max_child_weight)
self.__recalc()
return delta, fake
def export_compress(self, fmt='{addr}/{masklen}'):
# считаем статистику
self.finalize()
# сжимаем
self.collapse()
result = []
def walk(node):
if node is None:
return
# если суперсеть реальная –> дети не нужны
if node.is_real:
result.append(node.net.getAsString(fmt))
return
walk(node.child0)
walk(node.child1)
walk(self)
return "\n".join(result)
def export(self, fmt='{addr}/{masklen}'):
# считаем статистику
self.finalize()
result = []
def walk(node):
if node is None:
return
if node.is_real:
# дети полностью покрывают диапазон родителя?
child_real_vol = 0
for ch in (node.child0, node.child1):
if ch:
child_real_vol += ch.real_volume
# если дети полностью покрывают родителя -> родители не нужны
if child_real_vol >= node.net.volume:
walk(node.child0)
walk(node.child1)
return
# иначе выводим родителя и детей
result.append(node.net.getAsString(fmt))
walk(node.child0)
walk(node.child1)
walk(self)
return "\n".join(result)
-55
View File
@@ -1,55 +0,0 @@
{
'RU': [
# параметры конфигурации должны быть в самом верху списка
{
'ipv4': True, # не обязательный аргумент, по умолчанию True (24)
'ipv6': True, # не обязательный аргумент, по умолчанию False (64)
'ignore': ['CHINA', 'JAPAN'], # игнорируем маршруты, которые входят в перечисленный список выгрузок (работает кроме: -CHINA)
'compress': False, # не обязательный аргумент, по умолчанию True
'community': '65432:LOCATION,65432:200' # не обязательный аргумент, по умолчанию пусто
},
# Большая часть RU сегмента
{ 'url': ['https://stat.ripe.net/data/country-resource-list/data.json?resource=RU'] },
{ 'url': ['https://ipv4.fetus.jp/ru.txt'] },
{ 'url': ['https://github.com/ipverse/rir-ip/blob/master/country/ru/aggregated.json'] },
# HLL LLC
{ 'url': ['https://bgp.he.net/AS51115#_prefixes', 'https://ipinfo.io/widget/demo/AS51115?dataset=asn', 'https://api.hackertarget.com/aslookup/?q=AS51115'] },
# STATIC
{ 'static4': '188.130.255.0/24' },
],
'CHINA': [
{
# пример без community
'ipv4': True,
'ipv6': False,
'compress': True,
#'community': '65432:LOCATION,65432:201'
},
# Большая часть CH сегмента
{ 'url': ['https://stat.ripe.net/data/country-resource-list/data.json?resource=CN'] },
{ 'url': ['https://ipv4.fetus.jp/cn.txt'] },
{ 'url': ['https://github.com/ipverse/rir-ip/blob/master/country/cn/aggregated.json'] },
],
'JAPAN': [
{
'ipv6': True,
'community': '65432:LOCATION,65432:202',
},
# Большая часть KR сегмента
{ 'url': ['https://stat.ripe.net/data/country-resource-list/data.json?resource=JP'] },
{ 'url': ['https://ipv4.fetus.jp/jp.txt'] },
{ 'url': ['https://github.com/ipverse/rir-ip/blob/master/country/jp/aggregated.json'] },
],
'KOREA': [
{
'ipv6': True,
'community': '65432:LOCATION,65432:203'
},
# Большая часть KR сегмента
{ 'url': ['https://stat.ripe.net/data/country-resource-list/data.json?resource=KR'] },
{ 'url': ['https://ipv4.fetus.jp/kr.txt'] },
{ 'url': ['https://github.com/ipverse/rir-ip/blob/master/country/kr/aggregated.json'] },
# LG DACOM Corporation
{ 'url': ['https://bgp.he.net/AS3786#_prefixes', 'https://ipinfo.io/widget/demo/AS3786?dataset=asn', 'https://api.hackertarget.com/aslookup/?q=AS3786'] },
],
}