openresty / lua-nginx-module

Embed the Power of Lua into NGINX HTTP servers
https://openresty.org/
11.33k stars 2.03k forks source link

attempt to redefine .. in cdef #2341

Closed ftasnetamot closed 4 months ago

ftasnetamot commented 4 months ago

I am using
nginx version: openresty/1.25.3.2 built with OpenSSL 1.1.1w 11 Sep 2023
from OpenResty Debian repository

I have the following lua module:

local _M = {}

function _M.decide_on_dest( blocklist, ip2check, real_target, block_target)
  local ffi = require 'ffi'
  local cdef = ffi.cdef
  cdef([[
    struct hostent {
      char  *h_name;            /* name of host */
      char **h_aliases;         /* alias list */
      int    h_addrtype;        /* address type */
      int    h_length;          /* length of address */
      char **h_addr_list;       /* list of addresses */
    };
    struct hostent *gethostbyname(const char *name);
    struct addr_t { uint8_t b1, b2, b3, b4; }; 
  ]])
  -- reverse ip2check byte tuples and concatenate with blocklist servername  
  local a,b,c,d = ip2check:match("([%d]+).([%d]+).([%d]+).([%d]+)")
  local dnsblhost = d.."."..c.."."..b.."."..a.."."..blocklist
  local final_destination
  local dnsbl_result = ffi.C.gethostbyname(tostring(dnsblhost))
  if dnsbl_result == nil then
    -- no result is good news
    final_destination = real_target
  else
    final_destination = block_target
  end
  return final_destination
end
return _M

and the following nginx configuration:

stream {
  lua_package_path "/etc/openresty/lua/?.ljbc;/etc/openresty/lua/?.lua;;";
  log_format dovecot  '[$remote_addr] [$time_iso8601] $status ';

server {
    access_log /var/log/nginx/dovecot.log dovecot;
    listen 78.47.65.69:115 ;

    set $final_destination "";
    preread_by_lua_block {
      ngx.var.final_destination = require "block".decide_on_dest( "blocklist.some-domain.top",
                                          ngx.var.remote_addr,
                                          "192.168.255.254:995", "192.168.255.254:5995")
    }
    proxy_bind $remote_addr transparent;
    proxy_pass $final_destination;
 }

  server {
    access_log /var/log/nginx/dovecot.log dovecot;
    listen 192.168.255.254:5995 ssl;
    ssl_protocols TLSv1.2 TLSv1.3;
    .... a full working TLS configuration set here ...
    content_by_lua_block {
      ngx.sleep(1)
      ngx.log(ngx.ERR, 'blocked by dnsbl')
      ngx.status = 500
      ngx.say("500-Access denied, you are blocked for reasons")
      ngx.say("500 Come back after a grace period of 1h without any further misbehaviour")
      return ngx.exit(500)
    }
  }

The idea is, having OpenResty as a blocklist decision server in front of applications, which are unfortunately not able, to be configured itself in such a way. E.g. dovecot. This should be a more friendly way of blocking users, instead of simulation a completly broken server, by denying connections via firewall. This should be the last ressort. A solution like this is especially mandated in many mail server configurations, where also legal clients are producing a big amount of errors, when trying to configure some new devices etc. Those should get notified, otherwise they complain about broken service. The above examples are drastically reduced, just to reproduce the problem I have. I went to the core-C gethostbyname, as I was not successfull with "resty.dns.resolver", as this is not working in _preread_by_luablock only in _content_by_luablock. I followed the advice from this issue and put my ffi.cdef in a module and referenced it in the described way. But unfortunately, when I process some connections in parallel, I receive the error:

2024/07/24 18:27:31 [error] 189530#189530: *8 lua entry thread aborted: runtime error: /etc/openresty/lua/block.lua:13: attempt to redefine 'hostent'                     
stack traceback:                                                                                                                                                          
coroutine 0:                                                                                                                                                              
        [C]: in function 'cdef'                                                                                                                                           
        /etc/openresty/lua/block.lua:13: in function 'decide_on_dest'                                                                                                     
        preread_by_lua(dovecot.conf:215):2: in main chunk while prereading client data

Now I am a little bit clueless. Is here anybody, who can give me some hints, what I have to change?

ftasnetamot commented 4 months ago

I found the solution: The cdef should not be part of the module, the definition stays before and inside the module only references are used:

local ffi = require 'ffi'
local cdef = ffi.cdef
local C = ffi.C
cdef([[
  struct hostent {
    char  *h_name;            /*  name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
  };
  struct hostent *gethostbyname(const char *name);
  struct addr_t { uint8_t b1, b2, b3, b4; }; 
]])
local get_host_by_name = C.gethostbyname
-- now we define the module, which uses only references!!!
local _M = {}

function _M.decide_on_dest( blocklist, ip2check, real_target, block_target)
  -- reverse ip2check byte tuples and concatenate with blocklist servername  
  local a,b,c,d = ip2check:match("([%d]+).([%d]+).([%d]+).([%d]+)")
  local dnsblhost = d.."."..c.."."..b.."."..a.."."..blocklist
  local final_destination
  local dnsbl_result = get_host_by_name(tostring(dnsblhost))
  if dnsbl_result == nil then
    -- no result is good news
    final_destination = real_target
  else
    final_destination = block_target
  end
  return final_destination
end

return _M

So I keep this example here for others and close the issue myself.