2 pontos por GN⁺ 2024-09-26 | 1 comentários | Compartilhar no WhatsApp

Tecnologia do Meu Blog

Este servidor web é um servidor mínimo projetado para hospedar meu blog. Foi construído desde o início para ser robusto o suficiente para aguentar a internet pública. Não precisa de proxy reverso. Dá para ver funcionando de verdade em http://playin.coz.is/index.html. Pedi no Reddit para tentarem hackear e coletei gigabytes de logs de requisições divertidas e maliciosas. Salvei algumas em attempts.txt e pretendo fuçar mais depois por diversão.

Mas... por quê?

Eu gosto de criar minhas próprias ferramentas e cansei de ouvir que tudo precisa ser "testado em batalha". E se der crash? Bugs podem ser corrigidos.

Especificações

  • Apenas Linux
  • Implementa HTTP/1.1, pipelining e conexões keep-alive
  • Suporte a HTTPS (usando BearSSL até TLS 1.2)
  • Dependências mínimas (libc e BearSSL ao usar HTTPS)
  • Timeouts configuráveis
  • Logs de acesso, logs de crash, rotação de logs, limite de uso de disco
  • Sem Transfer-Encoding: Chunked (responde com 411 Length Required para induzir o cliente a reenviar com Content-Length)
  • Núcleo único (deve mudar quando eu conseguir um VPS melhor)
  • Sem cache de arquivos estáticos (ainda)

Benchmark

Embora o foco deste projeto seja robustez, ele não é lento de forma alguma. Uma comparação simples com nginx (endpoint estático, ambos single-thread, limite de 1K conexões):

  • (blogtech)

    $ wrk -c 500 -d 5s http://127.0.0.1:80/hello
    
    • Latência média: 6.66ms
    • Requisições/seg: 76974.24
    • Transferência/seg: 6.09MB
  • (nginx)

    $ wrk -c 500 -d 5s http://127.0.0.1:8080/hello
    
    • Latência média: 149.11ms
    • Requisições/seg: 44227.78
    • Transferência/seg: 8.27MB

Configuração do nginx:

worker_processes 1;
events {
  worker_connections 1024;
}
http {
  server {
    listen 8080;
    location /hello {
      add_header Content-Type text/plain;
      return 200 "Hello, world!";
    }
  }
}

Build e execução

Por padrão, o build do servidor é somente HTTP:

$ make

Esse comando gera os executáveis serve (build de release), serve_cov (build de cobertura) e serve_debug (build de debug). O build de release escuta na porta 80, e o build de debug na porta 8080.

Para ativar HTTPS, é preciso clonar e compilar o BearSSL:

$ mkdir 3p
$ cd 3p
$ git clone https://www.bearssl.org/git/BearSSL
$ cd BearSSL
$ make -j
$ cd ../../
$ make -B HTTPS=1

Os mesmos executáveis são gerados, mas com conexões seguras nas portas 443 (release) ou 8081 (debug). Você precisa colocar os arquivos cert.pem e key.pem no mesmo diretório do executável. Para mudar nome e localização, altere:

#define HTTPS_KEY_FILE "key.pem"
#define HTTPS_CERT_FILE "cert.pem"

Para testar HTTPS localmente, gere um certificado autoassinado (e a chave privada):

openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365

Uso

O servidor serve conteúdo estático da pasta docroot/. Para mudar isso, altere a função respond:

typedef struct {
  Method method;
  string path;
  int major;
  int minor;
  int nheaders;
  Header headers[MAX_HEADERS];
  string content;
} Request;

void respond(Request request, ResponseBuilder *b) {
  if (request.major != 1 || request.minor > 1) {
    status_line(b, 505); // HTTP Version Not Supported
    return;
  }

  if (request.method != M_GET) {
    status_line(b, 405); // Method Not Allowed
    return;
  }

  if (string_match_case_insensitive(request.path, LIT("/hello"))) {
    status_line(b, 200);
    append_content_s(b, LIT("Hello, world!"));
    return;
  }

  if (serve_file_or_dir(b, LIT("/"), LIT("docroot/"), request.path, NULLSTR, false))
    return;

  status_line(b, 404);
  append_content_s(b, LIT("Nothing here :|"));
}

Aqui você pode adicionar endpoints tratando o campo request.path. O caminho é apenas um slice do buffer da requisição. A URI não é analisada.

Testes

Eu executo o servidor regularmente com valgrind e sanitizers (address, undefined) e faço testes com wrk. Também estou adicionando testes automatizados em tests/test.py para verificar conformidade com a especificação HTTP/1.1. Hospedo meu site com ele e posto links aqui e ali para manter a pressão. Todos os bots que escaneiam sites vulneráveis na internet viram ótimos fuzzers.

Problemas conhecidos

  • O servidor responde a clientes HTTP/1.0 com HTTP/1.1

Contribuição

Eu trabalho principalmente na branch DEV e ocasionalmente faço merge para a MAIN. Ao abrir um pull request, vai ser mais fácil se ele tiver como alvo a DEV.

Resumo do GN⁺

  • Este projeto é um servidor web com foco em dependências mínimas e robustez.
  • Suporta HTTP/1.1 e HTTPS, além de oferecer vários recursos de logging e timeouts configuráveis.
  • Os resultados de benchmark mostram tempos de resposta mais rápidos que o nginx.
  • Foi projetado para que desenvolvedores possam curtir criar suas próprias ferramentas e corrigir bugs no processo.
  • Projetos com funcionalidades parecidas incluem Nginx e Apache HTTP Server.

1 comentários

 
GN⁺ 2024-09-26
Comentários do Hacker News
  • Sem necessidade de proxy reverso: usou Jetty para colocar apps na internet sem proxy reverso e não teve problemas

    • há muitas opiniões dizendo para usar proxy reverso sem motivos concretos de segurança ou desempenho
    • fica a dúvida se um proxy reverso é realmente necessário
  • Servidor web em C feito por conta própria: criou um servidor web em C que chegou a operar sites comerciais

    • lidava com muito tráfego usando 128 MB de RAM e 1 CPU
    • menciona que a internet de 20 anos atrás era menos hostil
    • bots são ótimos fuzzers, mas ainda assim é preciso fazer fuzzing de verdade
  • Satisfação de construir serviços: é muito satisfatório criar serviços básicos usando APIs do sistema

    • surpreende que a função poll() entregue desempenho tão alto
    • as funções por conexão, structs relacionadas e arrays lembram nginx, redis e memcached
    • ótimo trabalho
  • Apresentação de um pequeno projeto: apresenta um projeto interessante iniciado no tempo livre

  • Recomendação do framework Kore: se for incômodo escrever a parte exposta ao público de apps em C, recomenda o framework Kore

    • traz recursos embutidos como gerenciamento de certificados ACME, Pgsql, curl e WebSocket
    • permite compilar e executar módulos misturando Lua/Python com C
  • Compartilhamento de link interessante: a instância de althttpd do sqlite.org processa mais de 500 mil requisições HTTP por dia

    • entrega 200 GB de conteúdo em um Linode de US$ 40/mês
    • 19% das requisições HTTP acessam o repositório de código-fonte Fossil via CGI
  • A diversão de criar as próprias ferramentas: está cansado da opinião de que tudo precisa ser "testado em batalha"

    • bugs podem ser corrigidos
  • Palestra no Chaos Communication Congress: relembra uma palestra sobre um blog/servidor web escrito em C com recursos de segurança

    • inclui recursos como armazenamento imutável, redução de privilégios e impossibilidade de acesso aos certificados TLS
  • Site estável: um site que não trava nem quando aparece na primeira página

  • De volta ao básico: gosta da abordagem de voltar ao básico usando apenas o necessário

    • questiona o impacto de recursos desnecessários de software no desempenho
    • parabeniza o desenvolvedor