Clarify TCP lifecycle and platform address selection contracts
This commit is contained in:
1 parent
e5fafacf99
commit
0e15234ce1
3 files changed
+43
-19
No files matched your search
@@ -187,19 +187,21 @@ int random_bytes(uint8_t *buffer, size_t len);
|
||||
uint32_t get_interface_ip_by_index(uint32_t netif_index);
|
||||
|
||||
// Get IPv6 address of interface by its index (ifindex)
|
||||
// temporary=1: get temporary (privacy) IPv6, temporary=0: get permanent IPv6
|
||||
// POSIX: temporary=1 выбирает temporary (privacy), temporary=0 — постоянный адрес.
|
||||
// Windows игнорирует temporary: предпочитает глобальный адрес, затем локальный, исключая link-local/loopback/::.
|
||||
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found
|
||||
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out);
|
||||
|
||||
// Get IPv6 address using netlink (Linux-only, accurate IFA_F_* flags)
|
||||
// prefer_stable=1: prefer permanent/stable (IFA_F_MANAGETEMPADDR) over temporary
|
||||
// prefer_stable=0: prefer temporary over stable
|
||||
// Выбрать IPv6: Linux использует netlink с IFA_F_*, прочие POSIX — getifaddrs(), Windows — IP Helper.
|
||||
// POSIX: prefer_stable=1 предпочитает постоянный адрес, 0 — temporary; допускается fallback на другой тип.
|
||||
// Windows игнорирует prefer_stable, выбор как у get_interface_ipv6_by_index().
|
||||
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error
|
||||
int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out);
|
||||
|
||||
// Determine the interface index for the default route (outgoing traffic)
|
||||
// Creates a temp UDP socket, connects to a well-known address to force
|
||||
// kernel routing, then matches the chosen source IP to getifaddrs()
|
||||
// POSIX: UDP connect к фиксированному внешнему адресу, затем сопоставление локального IP с getifaddrs().
|
||||
// Windows: GetBestInterfaceEx к тому же адресу. Результат — выбранный маршрут к этому адресу,
|
||||
// который может отличаться от default route при более специфичных маршрутах.
|
||||
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
|
||||
uint32_t get_default_route_netif_index(int family);
|
||||
|
||||
|
||||
+34
-12
@@ -2,7 +2,7 @@
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
Асинхронный TCP-коннектор, построенный на event loop `uasync` и lock-free очередях `ll_queue`.
|
||||
Асинхронный TCP-коннектор, построенный на event loop `uasync` и очередях `ll_queue` одного потока.
|
||||
Одна структура `struct tcp_conn` = одно TCP-соединение.
|
||||
Используется в SOCKS/HTTP-прокси, STCP, TCP-прокси и тестах.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- Два memory pool: `entry_pool` (struct ll_entry) и `data_pool` (буферы), всё выделение на горячем пути через пулы.
|
||||
- Backpressure на чтение: high_water/low_water + `queue_waiter_wait`, EPOLLIN вкл/выкл по уровню заполнения очереди.
|
||||
- Backpressure на запись: порог 32 записи в write_queue + `queue_waiter_wait`, EPOLLOUT вкл/выкл при частичной отправке.
|
||||
- Graceful shutdown: FIN (shutdown SHUT_WR) и CLOSE (close сокета) — оба как сентинелы в write_queue, все предшествующие данные гарантированно отправлены.
|
||||
- Graceful shutdown: FIN (shutdown SHUT_WR) и CLOSE (close сокета) — сентинелы в write_queue; сначала предшествующие данные передаются ОС.
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
@@ -29,10 +29,11 @@ struct tcp_conn* tc = tcp_conn_create(
|
||||
on_fin_cb, on_error_cb, arg);
|
||||
```
|
||||
|
||||
После создания нужно установить callback на read_queue и включить отложенную обработку:
|
||||
После создания нужно установить callback на read_queue. Отложенный первый вызов и уведомления backpressure настраиваются отдельно:
|
||||
|
||||
```c
|
||||
queue_set_callback(tc->read_queue, on_read_cb, my_conn);
|
||||
queue_set_callback_defer(tc->read_queue, 1); // необязательно: первый consumer callback через call_soon
|
||||
queue_set_waiter_defer(tc->read_queue, 1);
|
||||
```
|
||||
|
||||
@@ -44,6 +45,7 @@ static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
// обработать e->dgram (e->len байт)
|
||||
memory_pool_free(c->tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q); // обязательно!
|
||||
}
|
||||
@@ -51,7 +53,8 @@ static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
|
||||
### Запись данных
|
||||
|
||||
Пользователь напрямую кладёт entry в `write_queue`, отправка авто (deferred callback сам шлёт когда сокет готов):
|
||||
Пользователь напрямую кладёт entry в `write_queue`. Первый callback может выполниться синхронно из `queue_data_put`;
|
||||
продолжение после `queue_resume_callback` откладывается через `uasync_call_soon`:
|
||||
|
||||
```c
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
@@ -71,7 +74,10 @@ tcp_conn_push_close(tc); // close сокета после отправки вс
|
||||
```
|
||||
|
||||
Можно вызывать из callback-ов (on_fin, on_read_cb и т.д.).
|
||||
Повторные вызовы игнорируются (fin_local=1/closed=1 — возвращают -1).
|
||||
`push_fin` возвращает -1 как при уже поставленном FIN (`fin_pending=1`), так и после успешного shutdown (`fin_local=1`).
|
||||
`push_close` возвращает -1 после фактического закрытия (`closed=1`); до обработки CLOSE повторная постановка не блокируется.
|
||||
При ошибке shutdown вызывается `on_error`, а `on_fin_sent` не вызывается. Успешный push означает принятие сентинела в очередь;
|
||||
связанный callback может выполниться ещё до возврата из push.
|
||||
|
||||
### Уничтожение
|
||||
|
||||
@@ -79,7 +85,10 @@ tcp_conn_push_close(tc); // close сокета после отправки вс
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
```
|
||||
|
||||
Идемпотентен (`destroyed` флаг). Дренирует обе очереди, закрывает сокет, освобождает пулы через отложенный `uasync_call_soon`.
|
||||
Сразу отменяет connect-таймер и callbacks очередей/сокета, дренирует очереди и закрывает сокет.
|
||||
Пулы и сам `tc` освобождаются через `uasync_call_soon` в том же потоке. После destroy владелец не использует указатель.
|
||||
Повторный вызов безопасен только до выполнения отложенного free; NULL безопасен всегда.
|
||||
Перед остановкой/уничтожением uasync нужно выполнить отложенную очистку (`uasync_drain_immediate` вне текущих callbacks).
|
||||
|
||||
### Обработка ошибок
|
||||
|
||||
@@ -99,7 +108,7 @@ static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
|
||||
```c
|
||||
tcp_conn_set_flushed(tc, my_flushed_cb);
|
||||
// коллбэк вызовется один раз когда write_queue + write_buf полностью опустеют, затем сбросится
|
||||
// коллбэк вызовется один раз когда write_queue + write_buf полностью опустеют; сбрасывается перед вызовом
|
||||
```
|
||||
|
||||
### Приостановка чтения
|
||||
@@ -108,12 +117,24 @@ tcp_conn_set_flushed(tc, my_flushed_cb);
|
||||
tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_waiter
|
||||
```
|
||||
|
||||
### Connect-таймаут
|
||||
|
||||
`tcp_conn_set_connect_timeout(tc, timeout_ms)` заменяет интервал ожидания, начиная с текущего вызова.
|
||||
Значение <=0 отменяет прежний таймер; положительное значение ограничено `INT_MAX/10` мс из-за единиц uasync (0.1 мс).
|
||||
Для уже установленного соединения настройка сохраняется без создания таймера.
|
||||
Возврат: 0 — применено, `-EINVAL` — аргументы/состояние, `-ENOMEM` — создание таймера, `-EIO` — нарушение состояния отмены.
|
||||
При ошибке прежние настройка и handle сохраняются. Новый таймер резервируется перед отменой старого;
|
||||
операция выполняется целиком в uasync-потоке без вызова пользовательских callbacks. Вызывающий код проверяет результат.
|
||||
Истечение вызывает `on_error(ETIMEDOUT)`; завершение connect, фатальная ошибка и destroy отменяют таймер.
|
||||
|
||||
### Ограничения и многопоточность
|
||||
|
||||
- **Однопоточный.** Всё работает в одном `uasync` event loop. Нельзя вызывать из другого потока.
|
||||
- **FIN-сентинел в очереди.** После `push_fin` данные продолжат отправляться, сам FIN выполнится только когда все предшествующие данные отправлены.
|
||||
- **FIN-сентинел в очереди.** После успешного `push_fin` новые данные в write_queue не помещают;
|
||||
предшествующие данные передаются ОС до shutdown.
|
||||
- **Нельзя вызывать close сокета напрямую.** Только через `tcp_conn_push_close` — иначе нарушится порядок обработки очереди.
|
||||
- **Можно вызывать push_fin/push_close из callback-ов** — защита от повторного вызова встроена (fin_local/closed флаги).
|
||||
- **Можно вызывать push_fin/push_close из callback-ов.** FIN защищён от повторной постановки;
|
||||
CLOSE проверяет только уже выполненное закрытие. Владелец учитывает возможный синхронный callback.
|
||||
|
||||
## 3. API
|
||||
|
||||
@@ -128,7 +149,7 @@ tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_wai
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_conn_create(ua, sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, on_fin, on_error, arg)` | Создать соединение: аллоцирует tc, два memory pool, два ll_queue, регистрирует сокет в uasync с EPOLLIN+EPOLLOUT. Вовращает NULL при ошибке |
|
||||
| `tcp_conn_destroy(tc)` | Дренирует read_queue и write_queue, закрывает сокет, освобождает пулы через `uasync_call_soon`. Идемпотентен |
|
||||
| `tcp_conn_destroy(tc)` | Сразу отменяет таймер/callbacks, дренирует очереди и закрывает сокет; пулы и tc освобождает через `uasync_call_soon` |
|
||||
|
||||
### Коллбэки (поля struct tcp_conn)
|
||||
|
||||
@@ -137,7 +158,7 @@ tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_wai
|
||||
| `on_fin(tc, arg)` | FIN получен от удалённой стороны (recv==0): сразу если read_queue пуста, иначе через `queue_set_empty_callback` после дренажа |
|
||||
| `on_fin_sent(tc, arg)` | FIN отправлен (`shutdown(SHUT_WR)`) — после отправки всех данных перед FIN-сентинелом |
|
||||
| `on_error(tc, err, arg)` | Фатальная ошибка сокета. Вызывается после close сокета. **Обязан вызвать tcp_conn_destroy.** После возврата tc недействителен |
|
||||
| `on_flushed(tc, arg)` | write_queue + write_buf полностью опустели. Одноразовый — после вызова сбрасывается в NULL. Установка: `tcp_conn_set_flushed()` |
|
||||
| `on_flushed(tc, arg)` | write_queue + write_buf полностью опустели. Одноразовый — перед вызовом сбрасывается в NULL. Установка: `tcp_conn_set_flushed()` |
|
||||
| `on_closed(tc, arg)` | Сокет закрыт через CLOSE-сентинел (`tcp_conn_push_close`) после отправки всех предшествующих данных |
|
||||
|
||||
### Graceful shutdown
|
||||
@@ -145,7 +166,7 @@ tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_wai
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_conn_push_fin(tc)` | Помещает FIN-сентинел (`dgram=&tcp_fin_sentinel, len=0`) в write_queue. После отправки предшествующих данных: shutdown(SHUT_WR), fin_local=1, вызывает on_fin_sent. Повторный вызов → -1 |
|
||||
| `tcp_conn_push_close(tc)` | Помещает CLOSE-сентинел (`dgram=NULL, len=0`) в write_queue. После отправки предшествующих данных: close сокета, удаление из uasync, closed=1, вызывает on_closed. Повторный вызов → -1 |
|
||||
| `tcp_conn_push_close(tc)` | Помещает CLOSE-сентинел (`dgram=NULL, len=0`) в write_queue. После отправки предшествующих данных: close сокета, удаление из uasync, closed=1, вызывает on_closed. После закрытия → -1 |
|
||||
|
||||
### Прочее
|
||||
|
||||
@@ -153,3 +174,4 @@ tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_wai
|
||||
|---------|----------|
|
||||
| `tcp_conn_set_flushed(tc, cb)` | Установить одноразовый коллбэк на полное опустошение write_queue |
|
||||
| `tcp_conn_pause_read(tc)` | Принудительно выключить EPOLLIN и отменить ожидание низкого порога |
|
||||
| `tcp_conn_set_connect_timeout(tc, ms)` | Заменить/отключить connect-таймер; возвращает 0 или отрицательный код ошибки |
|
||||
+1
-1
@@ -16,7 +16,7 @@ extern "C" {
|
||||
#include <stdint.h> // For uint64_t
|
||||
#include <stddef.h> // For size_t
|
||||
|
||||
typedef uint64_t TimeoutTime; // Абсолютное время в шкале владельца (uasync использует timebase: 0.1 мс).
|
||||
typedef uint64_t TimeoutTime; // Абсолютное время в шкале владельца (uasync хранит здесь миллисекунды).
|
||||
|
||||
typedef struct {
|
||||
TimeoutTime expiration; // Sort key (smaller = earlier)
|
||||
|
||||
Reference in new issue
Block a user