-
Notifications
You must be signed in to change notification settings - Fork 40
Add DNS rewrite example with packet expansion support #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
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]>
| @@ -0,0 +1,16 @@ | |||
| # SPDX-FileCopyrightText: (c) 2025 Ring Zero Desenvolvimento de Software LTDA | |||
There was a problem hiding this comment.
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") | |||
There was a problem hiding this comment.
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") | |||
There was a problem hiding this comment.
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") | |||
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
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?
| 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); |
There was a problem hiding this comment.
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}, |
There was a problem hiding this comment.
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..
| -- 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 ;-)
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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..
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