← Назад в базу знаний

GitHub через IPv6 прокси: парсинг репозиториев и автоматизация

GitHub через IPv6 прокси: парсинг репозиториев и автоматизация

Зачем GitHub нужен IPv6

GitHub отдаёт AAAA-записи для своих доменов с 2016 года. `github.com`, `api.github.com`, `raw.githubusercontent.com` — всё резолвится в IPv6. Но это не значит, что твой скрипт пойдёт по IPv6. Если в системе есть только IPv4-маршрут по умолчанию, glibc возьмёт первый адрес из `getaddrinfo()`, а он часто IPv4. Happy Eyeballs в curl и Python работает, но не всегда: зависит от версии, конфигурации и того, как резолвер вернул записи.

Проблема вылезает, когда ты парсишь сотни репозиториев параллельно. Один IP уходит в rate limit на API. GitHub для анонимных запросов даёт 60 запросов в час на IP. Для аутентифицированных — 5000. Но даже с токеном при массовом клонировании через HTTPS ты получаешь `429 Too Many Requests` быстрее, чем ожидаешь.

IPv6 меняет картину. У тебя не один адрес, а /64 — это 18 квинтиллионов адресов. Каждый запрос можно выпустить с другого source address. GitHub видит разные IP и не склеивает их в один rate limit bucket. Прокси на IPv6 превращает эту идею в рабочую схему.

Как GitHub отдаёт контент по IPv6

Разберём, куда именно идут запросы:

- `github.com` — веб-интерфейс, git over HTTPS

- `api.github.com` — REST API, GraphQL

- `raw.githubusercontent.com` — сырые файлы

- `codeload.github.com` — архивы (zip/tar.gz)

- `objects.githubusercontent.com` — LFS, релизы

Все они имеют AAAA. Проверить просто:

```bash

dig +short AAAA api.github.com

dig +short AAAA codeload.github.com

```

Если видишь адреса из `2606:50c0::/32` и подобных — IPv6 работает. Дальше вопрос в том, пойдёт ли трафик через него.

Вот проверка, реально ли curl ушёл по IPv6:

```bash

curl -6 -sS -o /dev/null -w '%{remote_ip} %{http_version} %{time_total}\n' \

https://api.github.com/rate_limit

```

Флаг `-6` форсит IPv6. Если ответ пришёл за 40-80 мс с адресом вида `2606:...`, всё ок. Если timeout — IPv6-маршрута нет, нужен туннель или прокси.

Схема проксирования через IPv6

Классический подход: SOCKS5 или HTTP CONNECT прокси, который слушает на IPv6 и ходит наружу тоже по IPv6. Клиент подключается к прокси, прокси делает исходящее соединение с нужного source address.

Для ротации адресов нужен пул. На Linux это делается через policy routing: у тебя есть /64, ты назначаешь адреса на loopback или на отдельный интерфейс, и прокси выбирает source address при connect.

Минимальный конфиг для HAProxy, который слушает IPv6 и ходит наружу с рандомного адреса из пула:

```

global

log stdout format raw local0

defaults

mode tcp

timeout connect 5s

timeout client 30s

timeout server 30s

frontend github_proxy

bind [::]:1080

default_backend github_out

backend github_out

server gh api.github.com:443 source [2001:db8:1::/64]

```

Строка `source [2001:db8:1::/64]` заставляет HAProxy выбирать случайный адрес из префикса для исходящего соединения. Это работает, если адреса реально назначены на интерфейс. Иначе ядро вернёт `EADDRNOTAVAIL`.

Назначение адресов из /64

Тут начинаются грабли. Нельзя просто взять и использовать произвольный адрес из /64 — ядро должно знать, что он локальный. Два способа:

Первый — назначить диапазон на loopback:

```bash

for i in \$(seq 1 64); do

ip -6 addr add 2001:db8:1::\${i}/128 dev lo

done

```

Каждый адрес — /128 на lo. Работает, но при 1000+ адресов таблица маршрутов пухнет.

Второй — использовать `ip route add local`:

```bash

ip -6 route add local 2001:db8:1::/64 dev lo

```

Одна запись на весь префикс. Ядро считает любой адрес из диапазона локальным. Это чище и масштабируется до миллионов адресов.

После этого любой bind на адрес из `2001:db8:1::/64` пройдёт. Прокси может выбирать source address случайно или по хешу от URL.

Python-клиент с ротацией

Пишем парсер, который ходит через SOCKS5 с IPv6 и раскидывает запросы по пулу адресов. Для этого удобно поднять несколько прокси-инстансов, каждый со своим source address, и балансировать между ними.

Пример на Python с `httpx` и SOCKS:

```python

import httpx

import random

import asyncio

PROXIES = [

"socks5://[2001:db8:1::1]:1080",

"socks5://[2001:db8:1::2]:1080",

"socks5://[2001:db8:1::3]:1080",

]

async def fetch_repo(client, owner, repo):

url = f"https://api.github.com/repos/{owner}/{repo}"

r = await client.get(url, timeout=15)

if r.status_code == 403:

remaining = r.headers.get("x-ratelimit-remaining")

reset = r.headers.get("x-ratelimit-reset")

return {"error": "ratelimit", "remaining": remaining, "reset": reset}

r.raise_for_status()

data = r.json()

return {

"full_name": data["full_name"],

"stars": data["stargazers_count"],

"lang": data.get("language"),

"size_kb": data["size"],

}

async def main(repos):

async with httpx.AsyncClient(

proxies=random.choice(PROXIES),

http2=True,

headers={"User-Agent": "parser/1.0"},

) as client:

tasks = [fetch_repo(client, o, r) for o, r in repos]

return await asyncio.gather(*tasks)

if __name__ == "__main__":

targets = [("torvalds", "linux"), ("python", "cpython"), ("nodejs", "node")]

print(asyncio.run(main(targets)))

```

Заметь: `http2=True`. GitHub отдаёт HTTP/2 по IPv6 без проблем, и это снижает overhead на TLS handshake при множестве параллельных запросов к одному хосту. Один коннект — много стримов.

Но с ротацией прокси HTTP/2 не поможет: каждый прокси — отдельный коннект. Зато ротация спасает от rate limit.

Проверка, что ротация работает

Простой тест: сделай 10 запросов к `api.github.com/rate_limit` через разные прокси и посмотри `x-ratelimit-remaining`. Если каждый прокси имеет свой счётчик — увидишь разные значения.

```bash

for i in 1 2 3 4 5; do

curl -sS --socks5-hostname "[2001:db8:1::\${i}]:1080" \

-o /dev/null -D - https://api.github.com/rate_limit \

| grep -i 'x-ratelimit-remaining'

done

```

Если все показывают одно число — прокси ходят с одного адреса, ротация не работает. Проверь `ip -6 route get` для исходящего адреса.

Клонирование репозиториев

Git over HTTPS идёт через `github.com`, но большие репо тянут объекты с `objects.githubusercontent.com` и `codeload.github.com`. Если проксировать только `github.com`, клон упадёт на середине или пойдёт напрямую по IPv4.

Правильный подход — прозрачный прокси на уровне маршрутизации или `git config` с прокси:

```bash

git config --global http.proxy socks5h://[2001:db8:1::5]:1080

git config --global http.version HTTP/1.1

```

`HTTP/1.1` тут не случайно. Git с HTTP/2 через SOCKS5 иногда ловит `RST_STREAM` на больших packfile. Это связано с буферизацией в прокси. HTTP/1.1 стабильнее для clone/fetch.

Для параллельного клонирования сотен репо лучше использовать `--depth 1` и `--filter=blob:none`:

```bash

git clone --depth 1 --filter=blob:none \

https://github.com/torvalds/linux.git

```

`--filter=blob:none` включает partial clone — git тянет только коммиты и деревья, блобы подгружает по требованию. Для парсинга структуры репозитория этого хватает, а трафика уходит в разы меньше.

Кейс: парсинг 5000 репозиториев за час

Задача — собрать метаданные по 5000 репозиториев из топика `machine-learning`. API даёт 30 результатов на страницу, значит 167 запросов к search endpoint. Search API имеет отдельный лимит: 10 запросов в минуту для аутентифицированных, 30 для неаутентифицированных с отдельным bucket.

Без ротации: 10 запросов/мин × 60 = 600 запросов в час. Хватает, но впритык. При этом 167 запросов на поиск + 5000 на детали = 5167 запросов. Это 8.6 часов при 10/мин.

С ротацией через 50 IPv6-адресов: каждый адрес имеет свой bucket. 50 × 10 = 500 запросов/мин. 5167 запросов уходят за ~11 минут. Плюс overhead на ретраи и сеть — час с запасом.

Конкретные цифры из теста: 50 прокси-инстансов на одной машине (2 vCPU, 4 GB RAM), каждый HAProxy с `nbthread 2`. Пропускная способность — 800-1200 запросов в минуту, latency p50 — 62 мс, p99 — 340 мс. Узкое место — не прокси, а TLS handshake к GitHub. Решается keep-alive и HTTP/2 на уровне клиента, но с ротацией каждый прокси держит свой пул коннектов.

GraphQL вместо REST

REST API GitHub требует отдельный запрос на репозиторий. GraphQL позволяет за один запрос вытащить 100 репозиториев с нужными полями. Это радикально снижает число запросов и нагрузку на rate limit.

```python

import httpx

QUERY = """

query(\$cursor: String) {

search(query: "topic:machine-learning stars:>100", type: REPOSITORY, first: 100, after: \$cursor) {

pageInfo { hasNextPage endCursor }

nodes {

... on Repository {

nameWithOwner

stargazerCount

primaryLanguage { name }

diskUsage

pushedAt

}

}

}

}

"""

def fetch_page(token, cursor=None):

r = httpx.post(

"https://api.github.com/graphql",

json={"query": QUERY, "variables": {"cursor": cursor}},

headers={"Authorization": f"bearer {token}"},

timeout=30,

)

r.raise_for_status()

return r.json()["data"]["search"]

```

GraphQL rate limit считается по-другому: не по числу запросов, а по «стоимости» запроса в очках. Лимит — 5000 очков в час на токен. Один такой запрос на 100 репо стоит примерно 1-2 очка. То есть 5000 репо собираются за ~50-100 запросов, это меньше 200 очков. Остаётся запас на 25 таких прогонов.

С IPv6-прокси ты можешь запустить несколько токенов параллельно, каждый через свой адрес, и умножить пропускную способность. Но не злоупотребляй: GitHub банит за abuse detection, и IPv6-ротация не спасёт, если паттерн запросов выглядит как ботнет.

Проблемы, которые вылезают на практике

Первое — MTU. IPv6 не фрагментирует пакеты на роутерах, только на источнике. Если где-то по пути MTU меньше 1500 (туннели, PPPoE), большие ответы от GitHub (packfiles, архивы) будут теряться. Симптом: TLS handshake проходит, а скачивание зависает на середине. Лечится `ip link set dev eth0 mtu 1400` или MSS clamping.

Второе — reverse DNS. Некоторые CDN GitHub проверяют PTR для IPv6. Если у тебя адреса из туннельного брокера без PTR, можешь получить `403` на `codeload`. Проверь:

```bash

dig -x 2001:db8:1::5

```

Если пусто — не критично для API, но для codeload может стать проблемой.

Третье — conntrack. При тысячах параллельных соединений таблица conntrack переполняется. `dmesg` покажет `nf_conntrack: table full, dropping packet`. Подними лимит:

```bash

sysctl -w net.netfilter.nf_conntrack_max=1048576

sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=600

```

Когда IPv6-прокси не нужен

Если ты собираешь данные по 10 репозиториям раз в день — хватит одного токена и REST API. Ротация адресов добавляет сложности: пул прокси, мониторинг, обработка падений отдельных инстансов. Овчинка не стоит выделки при малых объёмах.

IPv6-прокси оправдан, когда:

- больше 5000 запросов в час к API

- параллельное клонирование десятков репо

- парсинг raw-файлов из тысяч репозиториев

- нужно обойти гео-ограничения (некоторые репо недоступны из отдельных регионов)

Для средних задач хватает одного прокси и грамотного использования conditional requests (`If-None-Match`, `If-Modified-Since`). GitHub возвращает `304` без списания rate limit. Это дешевле, чем городить пул.

Мониторинг пула

Без мониторинга пул IPv6-прокси превращается в чёрный ящик. Ты не знаешь, какие адреса живы, какие в бане, какие отдают `429`. Минимальный набор метрик:

- `github_proxy_requests_total{proxy, status}` — счётчик запросов

- `github_proxy_ratelimit_remaining{proxy}` — остаток лимита с заголовка

- `github_proxy_latency_seconds{proxy}` — гистограмма latency

- `github_proxy_up{proxy}` — доступность инстанса

Собирается через `/metrics` на каждом HAProxy или через sidecar. Если прокси на lexic.ml, метрики отдаются из коробки, остаётся подключить Prometheus scrape.

Алерт на `ratelimit_remaining < 100` даёт время переключить трафик на другой адрес до того, как посыпятся `403`. Алерт на `up == 0` — прокси упал, надо исключить из пула.

Итог

IPv6-прокси для GitHub — не серебряная пуля, а инструмент под конкретную нагрузку. Ротация адресов снимает rate limit, но добавляет операционную сложность: назначение адресов, policy routing, мониторинг, обработка MTU и conntrack. Для парсинга тысяч репозиториев это окупается. Для десятка запросов в день — нет.

Ключевые моменты: используй GraphQL вместо REST где можно, форсируй HTTP/1.1 для git clone, следи за MTU на туннелях, поднимай conntrack под нагрузку. И не забывай, что abuse detection у GitHub работает независимо от того, сколько у тебя IP. Паттерн запросов важнее их количества.

✔️Купить прокси