En ciertas arquitecturas, surge la necesidad de atender simultáneamente diferentes protocolos de red sobre el mismo puerto. Un caso común es la exigencia de soportar HTTPS junto con un protocolo privado adicional en el puerto 443. Además, este protocolo privado podría requerir la transferencia del descriptor de archivo (file descriptor, FD) de la conexión a un servicio especializado para su procesamiento mediante sendmsg.
Inicialmente, se consideró la implementación de esta funcionalidad dentro del módulo balancer de OpenResty. Sin embargo, esta vía se descartó rápidamente al constatar que el contexto de balancer no permite predecir o inspeccionar una porción inicial de los datos entrantes de manera efectiva. Se ha registrado una solicitud de característica al respecto en el repositorio de GitHub de lua-nginx-module, pero aún sin respuesta oficial.
Una alternativa más prometedora se basa en la capacidad preread_by_lua_*, disponible en la configuración de gateway de OpenResty. Esta directiva permite utilizar cosocket:peek para examinar una cantidad limitada de datos TCP antes de que sean procesados por completo.
El siguiente desafío consiste en obtener acceso al descriptor de archivo (FD) de la conexión subyacente. Una propuesta en la comunidad de OpenResty aborda este punto, pero la respuesta oficial sugiere evitar la manipulación directa del FD para no comprometer el encapsulamiento de la capa de entrada/salida (I/O). En su lugar, se recomienda una estrategia que combine ngx.semaphore, lua_shared_dict y ngx.sleep.
Tras un intento inicial de emplear una función FFI recomendada, ngx_http_lua_ffi_socket_tcp_sslhandshake, se observó que el FD obtenido no era el correcto. Tras una depuración exhaustiva, se comprendió el error: se estaba intentando acceder a una interfaz de http en un momento en que la conexión aún se encontraba en el contexto de stream. La estructura de datos esperada por las definiciones de http en C no se correspondía con la estructura real en el contexto de stream. Después de buscar sin éxito la definición de la estructura adecuada en los encabezados de Nginx, se localizó en el código fuente de stream-lua-nginx-module (específicamente en ngx_stream_lua_request.h). Una modificación directa para utilizar la definición correcta permitió obtener el FD válido.
local ngx = require 'ngx'
local base = require "resty.core.base"
-- Asegurarse de que la sub-configuración (http/stream) está permitida
base.allows_subsystem('http', 'stream')
local request = base.get_request() -- Obtener la solicitud actual
local ffi = require 'ffi'
local C = ffi.C
-- Cargar una biblioteca externa que expone la función para obtener el FD
local fd_lib = ffi.load('/tmp/fd.so')
-- Definición de las funciones C que serán accesibles vía FFI
ffi.cdef[[
// Función para obtener el FD desde una estructura de solicitud HTTP
long ngx_http_lua_ffi_socket_fd(void *req);
// Función genérica para obtener el FD en el contexto de Lua
int lua_ffi_socket_fd(void *req);
]]
-- Obtener el FD usando la función FFI definida en el .so
local socket_fd = fd_lib.lua_ffi_socket_fd(request)
-- Registrar el FD obtenido para depuración
ngx.log(ngx.INFO, string.format("El descriptor de archivo es: %d", socket_fd))
El código fuente de fd.so se compone de los siguientes archivos:
fd.c:
#include "header.h" // Incluye la definición de estructuras
#include <stddef.h> // Para NULL
// Función expuesta para Lua vía FFI
int lua_ffi_socket_fd(ngx_http_request_t *r) {
if (r == NULL) {
return -1; // Retorna error si la solicitud es nula
}
// Accede al FD a través de la conexión asociada a la solicitud
return r->connection->fd;
}
header.h:
// Definición simplificada de la estructura de conexión
typedef struct {
void* data;
void* data1;
void* data2;
int fd; // El descriptor de archivo
} ngx_connection_t;
// Definición simplificada de la estructura de solicitud HTTP
typedef struct {
ngx_connection_t *connection; // Puntero a la conexión
} ngx_http_request_t;
Esta metodología permite obtener el FD correcto sin necesidad de modificar el código fuente de OpenResty. El principio fundamental es que, mientras los desplazamientos de los miembros dentro de las estructuras C coincidan con los utilizados por OpenResty, se podrá acceder a los datos de manera fiable.
La solución completa se integra en la configuración de OpenResty de la siguiente manera:
stream {
server {
listen 1080; # Puerto de escucha para el stream
proxy_pass 127.0.0.1:80; # Redirección a un servidor HTTP
# Ejecuta el script Lua antes de procesar la solicitud
preread_by_lua_file test.lua;
}
}
http {
include mime.types;
default_type application/octet-stream;
# ... (otras configuraciones HTTP)
}
El archivo test.lua contendrá la lógica para identificar el protocolo privado. Una vez detectado, la conexión se redirigirá al servicio especializado. Para una implementación detallada de la transferencia de la conexión, se puede consultar el ejemplo proporcionado por la comunidad en: websocket-fd-handoff.