Files
mimiclaw/main/proxy/http_proxy.c
titor 7dc4122778
Some checks failed
Build / idf-build (push) Has been cancelled
Build & Release / build (push) Has been cancelled
feat: 添加时区设置功能,默认时区改为 CST-8
- 新增 set_timezone LLM 工具,支持通过对话设置时区
- 新增 set_timezone / timezone_show CLI 命令
- 默认时区从 PST 改为 CST-8(中国标准时间 UTC+8)
- 支持 POSIX 格式和 18 个城市名映射(Asia/Shanghai 等)
- 时区通过 NVS 持久化存储(system_config namespace)
- config_show 中显示当前时区配置
- 更新 changelog.md 和 taolun.md 文档
2026-04-01 00:50:41 +08:00

353 lines
12 KiB
C

#include "http_proxy.h"
#include "mimi_config.h"
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <unistd.h>
#include "esp_log.h"
#include "nvs.h"
#include "esp_tls.h"
#include "esp_crt_bundle.h"
static const char *TAG = "proxy";
/* Only show warnings/errors by default; reduce polling noise */
__attribute__((constructor)) static void proxy_log_level(void)
{
esp_log_level_set(TAG, ESP_LOG_WARN);
}
static char s_proxy_host[64] = {0};
static uint16_t s_proxy_port = 0;
static char s_proxy_type[8] = "http"; // "http" or "socks5"
esp_err_t http_proxy_init(void)
{
/* Start with build-time defaults */
if (MIMI_SECRET_PROXY_HOST[0] != '\0' && MIMI_SECRET_PROXY_PORT[0] != '\0') {
strncpy(s_proxy_host, MIMI_SECRET_PROXY_HOST, sizeof(s_proxy_host) - 1);
s_proxy_port = (uint16_t)atoi(MIMI_SECRET_PROXY_PORT);
if (MIMI_SECRET_PROXY_TYPE[0] != '\0') {
strncpy(s_proxy_type, MIMI_SECRET_PROXY_TYPE, sizeof(s_proxy_type) - 1);
}
}
/* NVS overrides take highest priority (set via CLI) */
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_PROXY, NVS_READONLY, &nvs) == ESP_OK) {
char tmp[64] = {0};
size_t len = sizeof(tmp);
if (nvs_get_str(nvs, MIMI_NVS_KEY_PROXY_HOST, tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_proxy_host, tmp, sizeof(s_proxy_host) - 1);
uint16_t port = 0;
if (nvs_get_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, &port) == ESP_OK && port) {
s_proxy_port = port;
}
// Read proxy type from NVS
len = sizeof(tmp);
memset(tmp, 0, sizeof(tmp));
if (nvs_get_str(nvs, "proxy_type", tmp, &len) == ESP_OK && tmp[0]) {
strncpy(s_proxy_type, tmp, sizeof(s_proxy_type) - 1);
}
}
nvs_close(nvs);
}
if (s_proxy_host[0] && s_proxy_port) {
ESP_LOGI(TAG, "Proxy configured: %s:%d (%s)", s_proxy_host, s_proxy_port, s_proxy_type);
} else {
ESP_LOGI(TAG, "No proxy configured (direct connection)");
}
return ESP_OK;
}
esp_err_t http_proxy_set(const char *host, uint16_t port, const char *type)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
ESP_ERROR_CHECK(nvs_set_str(nvs, MIMI_NVS_KEY_PROXY_HOST, host));
ESP_ERROR_CHECK(nvs_set_u16(nvs, MIMI_NVS_KEY_PROXY_PORT, port));
ESP_ERROR_CHECK(nvs_set_str(nvs, "proxy_type", type));
ESP_ERROR_CHECK(nvs_commit(nvs));
nvs_close(nvs);
strncpy(s_proxy_host, host, sizeof(s_proxy_host) - 1);
s_proxy_port = port;
strncpy(s_proxy_type, type, sizeof(s_proxy_type) - 1);
ESP_LOGI(TAG, "Proxy set to %s:%d (%s)", s_proxy_host, s_proxy_port, s_proxy_type);
return ESP_OK;
}
esp_err_t http_proxy_clear(void)
{
nvs_handle_t nvs;
ESP_ERROR_CHECK(nvs_open(MIMI_NVS_PROXY, NVS_READWRITE, &nvs));
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_HOST);
nvs_erase_key(nvs, MIMI_NVS_KEY_PROXY_PORT);
nvs_erase_key(nvs, "proxy_type");
nvs_commit(nvs);
nvs_close(nvs);
s_proxy_host[0] = '\0';
s_proxy_port = 0;
strcpy(s_proxy_type, "http"); // Reset to default
ESP_LOGI(TAG, "Proxy cleared");
return ESP_OK;
}
bool http_proxy_is_enabled(void)
{
return s_proxy_host[0] != '\0' && s_proxy_port != 0;
}
/* ── Proxied TLS connection ───────────────────────────────────── */
struct proxy_conn {
int sock; /* raw TCP socket (for timeout control) */
esp_tls_t *tls; /* esp_tls handle owns TLS + socket lifecycle */
};
/* Read a line from socket (up to CR-LF). Returns length or -1. */
static int sock_read_line(int fd, char *buf, int max, int timeout_ms)
{
int pos = 0;
struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
while (pos < max - 1) {
char c;
int r = recv(fd, &c, 1, 0);
if (r <= 0) return -1;
if (c == '\n') { buf[pos] = '\0'; return pos; }
if (c != '\r') buf[pos++] = c;
}
buf[pos] = '\0';
return pos;
}
/* Open TCP + CONNECT tunnel for HTTP proxy, returns socket fd or -1 */
static int open_connect_tunnel(const char *host, int port, int timeout_ms)
{
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
struct addrinfo *res = NULL;
char port_str[8];
snprintf(port_str, sizeof(port_str), "%d", s_proxy_port);
if (getaddrinfo(s_proxy_host, port_str, &hints, &res) != 0 || !res) {
ESP_LOGE(TAG, "DNS resolve failed for proxy %s", s_proxy_host);
return -1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) { freeaddrinfo(res); return -1; }
struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(TAG, "TCP connect to proxy %s:%d failed", s_proxy_host, s_proxy_port);
freeaddrinfo(res); close(sock); return -1;
}
freeaddrinfo(res);
ESP_LOGI(TAG, "Connected to proxy %s:%d", s_proxy_host, s_proxy_port);
char req[256];
int len = snprintf(req, sizeof(req),
"CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n\r\n", host, port, host, port);
if (send(sock, req, len, 0) != len) {
ESP_LOGE(TAG, "Failed to send CONNECT"); close(sock); return -1;
}
char line[256];
if (sock_read_line(sock, line, sizeof(line), timeout_ms) < 0) {
ESP_LOGE(TAG, "No response from proxy"); close(sock); return -1;
}
if (strstr(line, "200") == NULL) {
ESP_LOGE(TAG, "CONNECT rejected: %s", line); close(sock); return -1;
}
/* Consume remaining response headers */
while (sock_read_line(sock, line, sizeof(line), timeout_ms) > 0) { }
ESP_LOGI(TAG, "CONNECT tunnel established to %s:%d", host, port);
return sock;
}
/* Open TCP + SOCKS5 tunnel, returns socket fd or -1 */
static int open_socks5_tunnel(const char *host, int port, int timeout_ms)
{
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
struct addrinfo *res = NULL;
char port_str[8];
snprintf(port_str, sizeof(port_str), "%d", s_proxy_port);
if (getaddrinfo(s_proxy_host, port_str, &hints, &res) != 0 || !res) {
ESP_LOGE(TAG, "DNS resolve failed for proxy %s", s_proxy_host);
return -1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) { freeaddrinfo(res); return -1; }
struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(TAG, "TCP connect to proxy %s:%d failed", s_proxy_host, s_proxy_port);
freeaddrinfo(res); close(sock); return -1;
}
freeaddrinfo(res);
ESP_LOGI(TAG, "Connected to SOCKS5 proxy %s:%d", s_proxy_host, s_proxy_port);
/* SOCKS5 handshake: version 5, no authentication */
unsigned char handshake[3] = { 0x05, 0x01, 0x00 };
if (send(sock, handshake, sizeof(handshake), 0) != sizeof(handshake)) {
ESP_LOGE(TAG, "Failed to send SOCKS5 handshake"); close(sock); return -1;
}
/* Receive handshake response */
unsigned char handshake_resp[2];
if (recv(sock, handshake_resp, sizeof(handshake_resp), 0) != sizeof(handshake_resp)) {
ESP_LOGE(TAG, "No response from SOCKS5 proxy"); close(sock); return -1;
}
if (handshake_resp[0] != 0x05 || handshake_resp[1] != 0x00) {
ESP_LOGE(TAG, "SOCKS5 handshake failed: version=%d, auth=%d", handshake_resp[0], handshake_resp[1]);
close(sock); return -1;
}
/* SOCKS5 connect request: version 5, connect command, reserved, domain name address type */
size_t host_len = strlen(host);
size_t req_len = 4 + 1 + host_len + 2;
unsigned char *req = malloc(req_len);
if (!req) { close(sock); return -1; }
req[0] = 0x05; /* version */
req[1] = 0x01; /* connect command */
req[2] = 0x00; /* reserved */
req[3] = 0x03; /* domain name address type */
req[4] = (unsigned char)host_len; /* domain name length */
memcpy(req + 5, host, host_len); /* domain name */
req[5 + host_len] = (unsigned char)(port >> 8); /* port high byte */
req[6 + host_len] = (unsigned char)(port & 0xFF); /* port low byte */
if (send(sock, req, req_len, 0) != (ssize_t)req_len) {
ESP_LOGE(TAG, "Failed to send SOCKS5 connect request");
free(req); close(sock); return -1;
}
free(req);
/* Receive connect response */
unsigned char resp[512];
int resp_len = recv(sock, resp, sizeof(resp), 0);
if (resp_len < 10) {
ESP_LOGE(TAG, "No response from SOCKS5 proxy"); close(sock); return -1;
}
if (resp[0] != 0x05 || resp[1] != 0x00) {
ESP_LOGE(TAG, "SOCKS5 connect failed: version=%d, status=%d", resp[0], resp[1]);
close(sock); return -1;
}
ESP_LOGI(TAG, "SOCKS5 tunnel established to %s:%d", host, port);
return sock;
}
proxy_conn_t *proxy_conn_open(const char *host, int port, int timeout_ms)
{
if (!http_proxy_is_enabled()) {
ESP_LOGE(TAG, "proxy_conn_open called but no proxy configured");
return NULL;
}
int sock;
if (strcmp(s_proxy_type, "socks5") == 0) {
sock = open_socks5_tunnel(host, port, timeout_ms);
} else {
sock = open_connect_tunnel(host, port, timeout_ms);
}
if (sock < 0) return NULL;
proxy_conn_t *conn = calloc(1, sizeof(*conn));
if (!conn) { close(sock); return NULL; }
conn->sock = sock;
/* ── TLS handshake via esp_tls over tunnel ───────────────── */
conn->tls = esp_tls_init();
if (!conn->tls) {
ESP_LOGE(TAG, "esp_tls_init failed");
close(sock); free(conn); return NULL;
}
/* Inject our CONNECT-tunnel socket and skip TCP connect phase */
esp_tls_set_conn_sockfd(conn->tls, sock);
esp_tls_set_conn_state(conn->tls, ESP_TLS_CONNECTING);
esp_tls_cfg_t cfg = {
.crt_bundle_attach = esp_crt_bundle_attach,
.timeout_ms = timeout_ms,
};
int ret = esp_tls_conn_new_sync(host, strlen(host), port, &cfg, conn->tls);
if (ret <= 0) {
ESP_LOGE(TAG, "TLS handshake failed over proxy tunnel");
esp_tls_conn_destroy(conn->tls);
/* esp_tls_conn_destroy closes the socket */
free(conn);
return NULL;
}
ESP_LOGI(TAG, "TLS handshake OK with %s:%d via proxy", host, port);
return conn;
}
int proxy_conn_write(proxy_conn_t *conn, const char *data, int len)
{
int written = 0;
while (written < len) {
ssize_t ret = esp_tls_conn_write(conn->tls, data + written, len - written);
if (ret > 0) {
written += (int)ret;
} else if (ret == ESP_TLS_ERR_SSL_WANT_WRITE) {
continue;
} else {
ESP_LOGE(TAG, "esp_tls_conn_write error: %d", (int)ret);
return -1;
}
}
return written;
}
int proxy_conn_read(proxy_conn_t *conn, char *buf, int len, int timeout_ms)
{
struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
setsockopt(conn->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
ssize_t ret = esp_tls_conn_read(conn->tls, buf, len);
if (ret == ESP_TLS_ERR_SSL_WANT_READ) return 0;
if (ret == 0) return 0;
if (ret < 0) {
ESP_LOGE(TAG, "esp_tls_conn_read error: %d", (int)ret);
return -1;
}
return (int)ret;
}
void proxy_conn_close(proxy_conn_t *conn)
{
if (!conn) return;
if (conn->tls) {
esp_tls_conn_destroy(conn->tls);
}
free(conn);
}