GitHub через IPv6 прокси: парсинг репозиториев и автоматизация
Содержание
- Зачем GitHub нужен IPv6
- Как GitHub отдаёт контент по IPv6
- Схема проксирования через IPv6
- Назначение адресов из /64
- Python-клиент с ротацией
- Проверка, что ротация работает
- Клонирование репозиториев
- Кейс: парсинг 5000 репозиториев за час
- GraphQL вместо REST
- Проблемы, которые вылезают на практике
- Когда 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. Паттерн запросов важнее их количества.