From 911fcced244fb3f6a650abdad8cd74f4c171d501 Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Sun, 13 Sep 2026 21:06:39 -0300 Subject: [PATCH] fixes --- docker-compose.yml | 45 +++++++++++++++++- docker/wordpress/uploads.ini | 29 ++++++++++++ xdp/xdp_ddos_filter.c | 75 ++++++++++++++++++------------ xdp/xdp_wg_mtu_optimizer.c | 90 +++++++++++++++++++++++++++++------- 4 files changed, 190 insertions(+), 49 deletions(-) create mode 100644 docker/wordpress/uploads.ini diff --git a/docker-compose.yml b/docker-compose.yml index 52593b3..f20a751 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -349,8 +349,12 @@ services: container_name: nginx deploy: resources: + # cpus:'4' antigo = sem limite real num host de 4 vCPUs (podia + # monopolizar a máquina e faminar PHP-FPM/Node dos outros apps + # sob carga de TLS/QUIC). Proxy reverso não precisa da máquina + # inteira; 2 cores é generoso e deixa margem pros apps. limits: - cpus: '4' + cpus: '2' memory: 2G reservations: cpus: '0.5' @@ -1114,8 +1118,17 @@ services: wordpress: image: wordpress:latest container_name: wordpress + deploy: + resources: + limits: + cpus: '1' + memory: 768M + reservations: + cpus: '0.25' + memory: 256M volumes: - ./docker/wp-content:/var/www/html/wp-content + - ./docker/wordpress/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro environment: - LETSENCRYPT_HOST=www.martinsevieira.adv.br,martinsevieira.adv.br - WORDPRESS_DB_NAME=wordpress @@ -1123,7 +1136,12 @@ services: - WORDPRESS_DB_HOST=10.15.0.42 - WORDPRESS_DB_USER=root - WORDPRESS_DB_PASSWORD=34641747 - - WORDPRESS_CONFIG_EXTRA=define('WP_REDIS_HOST', '10.15.0.55'); define('WP_REDIS_PORT', 6379); define('WP_REDIS_DATABASE', 1); define('WP_CACHE_KEY_SALT', 'wp_mv_adv_'); define('DISABLE_WP_CRON', true); + # WP_CACHE precisa estar "true" para o WordPress carregar o + # object-cache.php do plugin Redis — sem essa constante o Redis + # fica configurado mas o cache de objeto nunca entra em ação, e + # toda visita bate direto no MariaDB. Provavelmente a causa + # principal do WordPress "meio lento". + - WORDPRESS_CONFIG_EXTRA=define('WP_CACHE', true); define('WP_REDIS_HOST', '10.15.0.55'); define('WP_REDIS_PORT', 6379); define('WP_REDIS_DATABASE', 1); define('WP_CACHE_KEY_SALT', 'wp_mv_adv_'); define('DISABLE_WP_CRON', true); depends_on: - mariadb restart: always @@ -1167,6 +1185,22 @@ services: POSTGRES_USER: ${POSTGRES_USER:-anatomia} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-anatomia} POSTGRES_DB: ${POSTGRES_DB:-anatomia} + # Ajustado ao limite de 512M do container (padrão do Postgres é + # pensado para bare-metal com bem mais RAM disponível: shared_buffers + # de 128MB e sem effective_cache_size customizado deixa o planner + # pessimista sobre quanto cabe em cache). + command: + - postgres + - -c + - shared_buffers=128MB + - -c + - effective_cache_size=384MB + - -c + - work_mem=8MB + - -c + - maintenance_work_mem=32MB + - -c + - max_connections=50 volumes: - anatomia_db_data:/var/lib/postgresql/data healthcheck: @@ -1179,7 +1213,10 @@ services: deploy: resources: limits: + cpus: '1' memory: 512M + reservations: + cpus: '0.1' anatomia-api: container_name: anatomia-api @@ -1204,7 +1241,10 @@ services: deploy: resources: limits: + cpus: '1' memory: 512M + reservations: + cpus: '0.25' anatomia-web: container_name: anatomia-web @@ -1229,6 +1269,7 @@ services: deploy: resources: limits: + cpus: '0.5' memory: 256M # One-off data pipeline: generates the seed GLB models + populates the DB. diff --git a/docker/wordpress/uploads.ini b/docker/wordpress/uploads.ini new file mode 100644 index 0000000..630a698 --- /dev/null +++ b/docker/wordpress/uploads.ini @@ -0,0 +1,29 @@ +; docker/wordpress/uploads.ini +; Monta em /usr/local/etc/php/conf.d/ dentro do container wordpress. +; +; A imagem wordpress:latest (base php:apache) traz o módulo OPcache +; compilado mas DESLIGADO por padrão — sem isso, todo request recompila +; os .php do WordPress/plugins do zero. Isso costuma ser o maior ganho +; de performance "de graça" disponível para qualquer app PHP. + +[opcache] +opcache.enable=1 +opcache.memory_consumption=192 +opcache.interned_strings_buffer=16 +opcache.max_accelerated_files=10000 +; validate_timestamps=1 (padrão) confere mtime do arquivo a cada +; requisição — mantém deploy de plugin/tema funcionando sem reiniciar +; o container. Se quiser espremer mais performance e não editar temas +; em produção com frequência, mude para 0 e restarte o container a +; cada deploy. +opcache.validate_timestamps=1 +opcache.revalidate_freq=2 + +[PHP] +; Limites de upload/memória — os padrões da imagem (2M/128M) são baixos +; demais para uploads de mídia do WordPress e para temas/plugins mais +; pesados. +upload_max_filesize=64M +post_max_size=64M +memory_limit=256M +max_execution_time=120 diff --git a/xdp/xdp_ddos_filter.c b/xdp/xdp_ddos_filter.c index 068ea6d..5a8a8e2 100644 --- a/xdp/xdp_ddos_filter.c +++ b/xdp/xdp_ddos_filter.c @@ -178,10 +178,23 @@ static __always_inline int is_blacklisted(__u32 ip) { } static __always_inline int is_internal_network(__u32 ip) { + // Fast path: as duas redes internas reais (dockernet e WireGuard) já + // são constantes conhecidas em tempo de compilação (DOCKER_NETWORK_* + // / WG_NETWORK_* no topo do arquivo). Checar isso com duas comparações + // de inteiro evita um bpf_map_lookup_elem em HASH map — que envolve + // hashing + possível cache miss — em todo pacote de origem interna, + // que é o caso mais comum no fast path (ver chamada logo no início + // do programa principal). + if ((ip & DOCKER_NETWORK_MASK) == DOCKER_NETWORK_PREFIX) + return 1; + if ((ip & WG_NETWORK_MASK) == WG_NETWORK_PREFIX) + return 1; + + // Fallback: redes internas adicionais configuradas dinamicamente via + // mapa (para não precisar recompilar o programa a cada rede nova). struct network_config *net; __u32 key = 0; - // Iteração limitada pelo verificador #pragma unroll for (int i = 0; i < 8; i++) { key = i; @@ -235,6 +248,29 @@ static __always_inline void update_stats(__u32 action, __u8 flags) { #define ntohs(x) __builtin_bswap16(x) #define ntohl(x) __builtin_bswap32(x) +// Redireciona para AF_XDP (kernel bypass, af_xdp_user faz bind no xsks_map) +// pacotes já aprovados nas portas de alto tráfego. IMPORTANTE: para 80/443 +// isso só deve ser chamado DEPOIS que o pacote passou pelo rate-limiting — +// chamar antes deixaria um atacante contornar toda a proteção contra DDoS +// caindo direto no redirect. Para as portas já tratadas como fast-path +// (WireGuard, 5061/5161) tanto faz, pois elas já pulam o rate-limiting de +// qualquer forma. +static __always_inline int try_af_xdp_redirect(struct xdp_md *ctx, __u8 protocol, __u16 dest_port) { + int xsk_map_key = ctx->rx_queue_index; + + int redirect_tcp = (protocol == IPPROTO_TCP) && + (dest_port == 80 || dest_port == 443 || dest_port == 5061 || dest_port == 5161); + int redirect_udp = (protocol == IPPROTO_UDP) && + (dest_port == 32100 || dest_port == 32110 || dest_port == 32101 || dest_port == 32400 || + dest_port == 5060 || dest_port == 5160 || + (dest_port >= 18000 && dest_port <= 19000)); + + if (!redirect_tcp && !redirect_udp) + return 0; + + return bpf_map_lookup_elem(&xsks_map, &xsk_map_key) != NULL; +} + // Versão otimizada do programa principal SEC("xdp") int xdp_ddos_filter(struct xdp_md *ctx) { @@ -277,6 +313,8 @@ int xdp_ddos_filter(struct xdp_md *ctx) { if (dest_port == 32100 || dest_port == 32400 || dest_port == 32101 || dest_port == 32110 || dest_port == 5060 || dest_port == 5160 || (dest_port >= 18000 && dest_port <= 19000)) { + if (try_af_xdp_redirect(ctx, IPPROTO_UDP, dest_port)) + return XDP_REDIRECT; update_stats(XDP_PASS, 0); return XDP_PASS; } @@ -368,6 +406,8 @@ int xdp_ddos_filter(struct xdp_md *ctx) { __u8 mark_trusted = 1; bpf_map_update_elem(&trusted_hosts_map, &src_ip, &mark_trusted, BPF_ANY); } + if (try_af_xdp_redirect(ctx, IPPROTO_TCP, dest_port)) + return XDP_REDIRECT; return XDP_PASS; } struct ip_stats *entry = bpf_map_lookup_elem(&ddos_map, &src_ip); @@ -543,6 +583,8 @@ update_entry_and_pass: }; bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); update_stats(XDP_PASS, 0); + if (try_af_xdp_redirect(ctx, ip->protocol, dest_port)) + return XDP_REDIRECT; return XDP_PASS; } update_entry_and_drop: @@ -576,38 +618,11 @@ update_entry_and_drop: }; bpf_map_update_elem(&ddos_map, &src_ip, &new_entry, BPF_ANY); update_stats(XDP_PASS, 0); + if (try_af_xdp_redirect(ctx, ip->protocol, dest_port)) + return XDP_REDIRECT; return XDP_PASS; } - // Redirecionamento AF_XDP para portas 80, 443 (TCP) e 32100 (UDP) - int xsk_map_key = ctx->rx_queue_index; - - // TCP: 80 ou 443 - if (ip->protocol == IPPROTO_TCP) { - struct tcphdr *tcp = (void *)(ip + 1); - if ((void *)(tcp + 1) > data_end) return XDP_PASS; - __u16 dest_port = ntohs(tcp->dest); - // Redirecionar também 5061, 5161 - if (dest_port == 80 || dest_port == 443 || dest_port == 5061 || dest_port == 5161) { - if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key)) - return XDP_REDIRECT; - } - } - - // UDP: 32100 (WireGuard) - if (ip->protocol == IPPROTO_UDP) { - struct udphdr *udp = (void *)(ip + 1); - if ((void *)(udp + 1) > data_end) return XDP_PASS; - __u16 dest_port = ntohs(udp->dest); - // Redirecionar também 5060, 5160, 18000-19000 - if (dest_port == 32100 || dest_port == 32110 || dest_port == 32101 || dest_port == 32400 || - dest_port == 5060 || dest_port == 5160 || - (dest_port >= 18000 && dest_port <= 19000)) { - if (bpf_map_lookup_elem(&xsks_map, &xsk_map_key)) - return XDP_REDIRECT; - } - } - return XDP_PASS; } diff --git a/xdp/xdp_wg_mtu_optimizer.c b/xdp/xdp_wg_mtu_optimizer.c index 6f6c451..a47d03f 100644 --- a/xdp/xdp_wg_mtu_optimizer.c +++ b/xdp/xdp_wg_mtu_optimizer.c @@ -43,29 +43,84 @@ struct { __type(value, __u16); // MTU conhecido } path_mtu_map SEC(".maps"); -// Função para ajustar TCP MSS - totalmente reescrita e simplificada para maior compatibilidade -static __always_inline int adjust_tcp_mss(struct tcphdr *tcp, void *data_end, __u16 max_mss) { - // Verificação básica do cabeçalho TCP +// Atualização incremental de checksum de 16 bits (RFC 1071/1624) — a mesma +// técnica usada pelo csum_replace2() do kernel. Necessária porque XDP não +// tem acesso a helpers de checksum baseados em skb (bpf_l4_csum_replace só +// existe para programas TC/socket, não XDP). +static __always_inline __u16 csum16_replace(__u16 csum, __u16 old_val, __u16 new_val) { + __u32 sum = (__u16)~csum + (__u16)~old_val + new_val; + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + return (__u16)~sum; +} + +// Clampa o MSS anunciado em pacotes SYN, reescrevendo o pacote de verdade +// (a versão anterior só incrementava uma estatística e admitia no comentário +// que não mexia no pacote — ou seja, não fazia nada útil). Como não mudamos +// o tamanho do pacote, dá para escrever em cima do buffer existente sem +// bpf_xdp_adjust_head, só recalculando o checksum do TCP. +static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u16 max_mss_host) { if ((void *)(tcp + 1) > data_end) return 0; - - // Ajustamos MSS apenas em pacotes SYN + + // Ajustamos MSS apenas em pacotes SYN puros (não SYN-ACK): este + // programa roda no ingress de wg0, ou seja, só vê tráfego entrando + // pelo túnel em direção à rede interna. O SYN-ACK de volta sai por + // wg0 (egress), fora do alcance do XDP — cobrir essa direção também + // exigiria um programa TC de egress, não só XDP. if (!tcp->syn || tcp->ack) return 0; - - // Obter comprimento do cabeçalho e verificar se há opções + __u8 tcp_hdr_len = tcp->doff * 4; if (tcp_hdr_len <= sizeof(struct tcphdr)) return 0; // Sem opções TCP - - // Simplificado: remover a complexidade de analisar as opções - // Em vez disso, vamos assumir um MSS conservador para todos os pacotes SYN - __u32 key = 0; - struct mtu_stats *stats = bpf_map_lookup_elem(&mtu_stats_map, &key); - if (stats) - stats->mss_adjusted++; - - return 1; // Reportamos ajuste sem tentar modificar o pacote + + __u8 *opt = (__u8 *)(tcp + 1); + __u8 *opt_end = (__u8 *)tcp + tcp_hdr_len; + if ((void *)opt_end > data_end) + return 0; + + // Loop limitado (verificador do BPF exige bound estático): cabeçalho + // TCP tem no máximo 40 bytes de opções, então 10 iterações cobrem até + // opções de 4 bytes cada — mais que suficiente para chegar no MSS, + // que por convenção vem logo no início das opções. + #pragma unroll + for (int i = 0; i < 10; i++) { + if ((void *)(opt + 1) > data_end || opt >= opt_end) + break; + + __u8 kind = opt[0]; + if (kind == 0) // End of Option List + break; + if (kind == 1) { // NOP + opt += 1; + continue; + } + + if ((void *)(opt + 2) > data_end || opt + 1 >= opt_end) + break; + __u8 opt_len = opt[1]; + if (opt_len < 2 || (void *)(opt + opt_len) > data_end || opt + opt_len > opt_end) + break; + + if (kind == 2 && opt_len == 4) { // Maximum Segment Size + if ((void *)(opt + 4) > data_end) + break; + __be16 *mss_ptr = (__be16 *)(opt + 2); + __u16 cur_mss = bpf_ntohs(*mss_ptr); + if (cur_mss > max_mss_host) { + __be16 old_val = *mss_ptr; + __be16 new_val = bpf_htons(max_mss_host); + *mss_ptr = new_val; + tcp->check = csum16_replace(tcp->check, old_val, new_val); + return 1; + } + return 0; + } + + opt += opt_len; + } + return 0; } // Programa XDP principal - simplificado @@ -135,7 +190,8 @@ int xdp_wg_optimizer(struct xdp_md *ctx) { // Verificar se temos espaço para o cabeçalho TCP if (tcp_ptr + sizeof(struct tcphdr) <= data_end) { struct tcphdr *tcp = tcp_ptr; - adjust_tcp_mss(tcp, data_end, MSS_CLAMPING_VALUE); + if (clamp_tcp_mss(tcp, data_end, MSS_CLAMPING_VALUE) && stats) + stats->mss_adjusted++; } }