Skip to content

Conversation

@ArifAlam
Copy link

Introduce a new DNS rewrite example that demonstrates kernel-level DNS interception and response generation using Lunatik's netfilter hooks. The example intercepts DNS queries for 'test.internal' and responds directly with 127.0.0.1, preventing queries from reaching external DNS servers.

Changes include:

  • Add dnsrewrite example with netfilter hook implementation that intercepts outgoing and incoming DNS packets at LOCAL_OUT and PRE_ROUTING points

  • Implement DNS packet parsing, response building, and checksum recalculation

  • Extend luadata API with expand() method to support dynamic packet buffer expansion using skb_put(), enabling in-place DNS response construction

Introduce a new DNS rewrite example that demonstrates kernel-level
DNS interception and response generation using Lunatik's netfilter hooks. The
example intercepts DNS queries for 'test.internal' and responds directly with
127.0.0.1, preventing queries from reaching external DNS servers.

Changes include:

- Add dnsrewrite example with netfilter hook implementation that intercepts
  outgoing and incoming DNS packets at LOCAL_OUT and PRE_ROUTING points

- Implement DNS packet parsing, response building, and checksum recalculation

- Extend luadata API with expand() method to support dynamic packet buffer
  expansion using skb_put(), enabling in-place DNS response construction

Signed-off-by: Arif Alam <[email protected]>
@lneto lneto requested review from lneto and sheharyaar November 24, 2025 21:29
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: (c) 2025 Ring Zero Desenvolvimento de Software LTDA
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this makefile.. we only have it on the other dns filter examples for using iptables (it requires a userspace plugin).. if we this only uses netfilter, we can avoid this file..

@@ -0,0 +1,220 @@
local linux = require("linux")
local string = require("string")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to require Lua standard libs.. it's safe to just use string.*

@@ -0,0 +1,220 @@
local linux = require("linux")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should add your copyright and license on top =)

@@ -0,0 +1,26 @@
local nf = require("netfilter")
local common = require("examples.dnsrewrite.common")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary to split this module in two parts.. common and nf_*.. we used this pattern only for the ones we have support for both iptables and netfilter.. I would keep it in just one single file..

char *ptr;
size_t size;
uint8_t opt;
struct sk_buff *skb; /* optional sk_buff pointer for packet expansion */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better approach is to introduce a new option.. e.g., LUADATA_OPT_SKB.. then, we can use container_of to get this struct, right?

Comment on lines +455 to +464
void luadata_set_skb(lunatik_object_t *object, struct sk_buff *skb)
{
luadata_t *data;

lunatik_lock(object);
data = (luadata_t *)object->private;
data->skb = skb;
lunatik_unlock(object);
}
EXPORT_SYMBOL(luadata_set_skb);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could skip this if using a new option, we would just need to set the flag

#endif
{"getstring", luadata_getstring},
{"setstring", luadata_setstring},
{"expand", luadata_expand},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would provide a more generic API, e.g. resize, allowing both shrink and growth, both skb and raw buffers, what do you think? If you think it's better to have only expand, I would at least support both skb and raw buffer, making it more ergonomic..

Comment on lines +77 to +83
-- Get protocol from IP header (offset 9)
local proto = skb:getuint8(eth_len + 9)

-- Only process UDP packets
if proto ~= udp then
return action.ACCEPT
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's totally alright to do it at this level, but we can also leverage the nft integration, so you can match the UDP protocol on nft and mark only the packets you want to call this hook.. you just need to add mark = X on your hook.. it usually makes the lua portion cleaner and the overall flow more efficient =), as we are not calling the script for every packet..


-- Check if destination port is DNS (53) - this means it's a DNS query
local dstport = linux.ntoh16(skb:getuint16(thoff + 2))
if dstport ~= dns then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can decide this at nft level as well.. (not mandatory though)

-- Update IP total length
skb:setuint16(eth_len + 2, linux.hton16(ip_len))

-- Recalculate UDP checksum
Copy link
Contributor

@lneto lneto Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would need to check, but I have the impression that when we change the SKB at INET level, netfilter will recalc the checksums by itself.. but now sure.. (sorry if memory is not that reliable ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jperon I think you have played with it recently, right?


#define luadata_clear(o) (luadata_reset((o), NULL, 0, LUADATA_OPT_KEEP))

struct sk_buff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's ok for us to include the skb header in luadata.h..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants