Skip to content

Commit

Permalink
add dns doctoring example
Browse files Browse the repository at this point in the history
Signed-off-by: Mohammad Shehar Yaar Tausif <[email protected]>
  • Loading branch information
sheharyaar committed Aug 29, 2024
1 parent b5bc647 commit 00f1fe4
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 35 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ examples_install:
${INSTALL} -m 0644 examples/filter/*.lua ${SCRIPTS_INSTALL_PATH}/examples/filter
${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsblock
${INSTALL} -m 0644 examples/dnsblock/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsblock
${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor
${INSTALL} -m 0644 examples/dnsdoctor/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor

examples_uninstall:
${RM} -r ${SCRIPTS_INSTALL_PATH}/examples
Expand Down
72 changes: 56 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1148,10 +1148,10 @@ This function receives the following arguments:
* `match` : function to be called for matching packets. It receives the following arguments:
* `skb` (readonly): a `data` object representing the socket buffer.
* `par`: a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields.
* `userdata` : a lua string passed from the userspace xtable module.
* `userargs` : a lua string passed from the userspace xtable module.
* The function must return `true` if the packet matches the extension; otherwise, it must return `false`.
* `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument.
* `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument.
* `checkentry`: function to be called for checking the entry. This function receives `userargs` as its argument.
* `destroy`: function to be called for destroying the xtable extension. This function receives `userargs` as its argument.
#### `xtable.target(opts)`
Expand All @@ -1166,10 +1166,10 @@ This function receives the following arguments:
* `target` : function to be called for targeting packets. It receives the following arguments:
* `skb`: a `data` object representing the socket buffer.
* `par` (readonly): a table containing `hotdrop`, `thoff` (transport header offset) and `fragoff` (fragment offset) fields.
* `userdata` : a lua string passed from the userspace xtable module.
* `userargs` : a lua string passed from the userspace xtable module.
* The function must return one of the values defined by the [xtable.action](https://github.com/luainkernel/lunatik#xtableaction) table.
* `checkentry`: function to be called for checking the entry. This function receives `userdata` as it's argument.
* `destroy`: function to be called for destroying the xtable extension. This function receives `userdata` as it's argument.
* `checkentry`: function to be called for checking the entry. This function receives `userargs` as its argument.
* `destroy`: function to be called for destroying the xtable extension. This function receives `userargs` as its argument.
#### `xtable.family`
Expand Down Expand Up @@ -1211,9 +1211,9 @@ netfilter hooks to Lua.
### luaxt
The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/). The user can modify the generated lua code to implement the userspace handlers for the corresponding xtable extension.
The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/).
To generate the library, the following steps are required:
To build the library, the following steps are required:
1. Go to `usr/lib/xtable` and create a `libxt_<ext_name>.lua` file.
2. Register your callbacks for the xtable extension by importing the library (`luaxt`) in the created file.
Expand All @@ -1229,10 +1229,10 @@ This function receives the following arguments:
* `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension).
* `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily)
* `help`: function to be called for displaying help message for the extension.
* `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userdata`. (`par.userdata = "mydata"`)
* `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function.
* `save`: function to be called for saving the arguments. This function recevies `userdata` set by the `init` or `parse` function.
* `parse`: function to be called for parsing the command line arguments. This function receives an `par` table that can be used to set `userdata` and `flags`. (`par.userdata = "mydata"`)
* `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userargs`. (`par.userargs = "mydata"`)
* `print`: function to be called for printing the arguments. This function recevies `userargs` set by the `init` or `parse` function.
* `save`: function to be called for saving the arguments. This function recevies `userargs` set by the `init` or `parse` function.
* `parse`: function to be called for parsing the command line arguments. This function receives an `par` table that can be used to set `userargs` and `flags`. (`par.userargs = "mydata"`)
* `final_check`: function to be called for final checking of the arguments. This function receives `flags` set by the `parse` function.
#### `luaxt.target(opts)`
Expand All @@ -1243,10 +1243,10 @@ This function receives the following arguments:
* `revision`: integer representing the xtable extension revision (**must** be same as used in corresponding kernel extension).
* `family`: address family, one of [luaxt.family](https://github.com/luainkernel/lunatik#luaxtfamily)
* `help`: function to be called for displaying help message for the extension.
* `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userdata`. (`par.userdata = "mydata"`)
* `print`: function to be called for printing the arguments. This function recevies `userdata` set by the `init` or `parse` function.
* `save`: function to be called for saving the arguments. This function recevies `userdata` set by the `init` or `parse` function.
* `parse`: function to be called for parsing the command line arguments. This function receives an `par` table that can be used to set `userdata` and `flags`. (`par.userdata = "mydata"`)
* `init`: function to be called for initializing the extension. This function receives an `par` table that can be used to set `userargs`. (`par.userargs = "mydata"`)
* `print`: function to be called for printing the arguments. This function recevies `userargs` set by the `init` or `parse` function.
* `save`: function to be called for saving the arguments. This function recevies `userargs` set by the `init` or `parse` function.
* `parse`: function to be called for parsing the command line arguments. This function receives an `par` table that can be used to set `userargs` and `flags`. (`par.userargs = "mydata"`)
* `final_check`: function to be called for final checking of the arguments. This function receives `flags` set by the `parse` function.
#### `luaxt.family`
Expand Down Expand Up @@ -1410,6 +1410,46 @@ sudo lunatik run examples/dnsblock/dnsblock false # runs the Lua kernel script
sudo iptables -A OUTPUT -m dnsblock -j DROP # this initiates the netfilter framework to load our extension
```
### dnsdoctor
[dnsdoctor](examples/dnsdoctor) is a kernel script that uses the lunatik xtable library to change the DNS response
from Public IP to a Private IP if the destination IP matches the one provided by the user. For example, if the user
wants to change the DNS response from `192.168.10.1` to `10.1.2.3` for the domain `lunatik.com` if the query is being sent to `10.1.1.2` (a private client), this script can be used.
#### Usage
```
sudo make examples_install # installs examples
cd examples/dnsdoctor
setup.sh # sets up the environment

# test the setup, a response with IP 192.168.10.1 should be returned
dig lunatik.com

# run the Lua kernel script
sudo lunatik run examples/dnsdoctor/dnsdoctor false

# copy the userspace extension to luaxt directory
cp libxt_dnsdoctor.lua ../../usr/lib/xtable/
cd ../../usr/lib/xtable

# build and install the userspace extension for netfilter
LUAXTABLE_MODULE=dnsdoctor make
sudo LUAXTABLE_MODULE=dnsdoctor make install

# add rule to the mangle table
sudo iptables -t mangle -A PREROUTING -p udp --sport 53 -j dnsdoctor

# test the setup, a response with IP 10.1.2.3 should be returned
dig lunatik.com

# cleanup
sudo iptables -t mangle -D PREROUTING -p udp --sport 53 -j dnsdoctor # remove the rule
sudo lunatik unload
cd ../../../examples/dnsdoctor
cleanup.sh
```
## References
* [Scripting the Linux Routing Table with Lua](https://netdevconf.info/0x17/sessions/talk/scripting-the-linux-routing-table-with-lua.html)
Expand Down
14 changes: 14 additions & 0 deletions examples/dnsdoctor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <[email protected]>
# SPDX-License-Identifier: MIT OR GPL-2.0-only

all:
LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable

install:
sudo LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable install

uninstall:
sudo rm -f ${XTABLES_SO_DIR}/libxt_${LUAXTABLE_MODULE}.so

clean:
LUAXTABLE_MODULE=dnsdoctor $(MAKE) -C ../../usr/lib/xtable clean
36 changes: 36 additions & 0 deletions examples/dnsdoctor/cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <[email protected]>
# SPDX-License-Identifier: MIT OR GPL-2.0-only

#!/bin/bash

set -eux

rm dnstest -rf

# backup resolv config
if [[ -f /etc/resolv.conf.lunatik ]] then
echo "Restoring dns config from resolv.conf.lunatik"
sudo rm /etc/resolv.conf
sudo cp /etc/resolv.conf.lunatik /etc/resolv.conf
sudo rm /etc/resolv.conf.lunatik
fi

# down the interfaces
sudo ip -n ns1 link set veth1 down
sudo ip -n ns2 link set veth3 down
sudo ip link set veth2 down
sudo ip link set veth4 down

sudo ip addr delete 10.1.1.2/24 dev veth2
sudo ip -n ns1 addr delete 10.1.1.3/24 dev veth1
sudo ip addr delete 10.1.2.2/24 dev veth4
sudo ip -n ns2 addr delete 10.1.2.3/24 dev veth3

# delete link between host and the namespaces
sudo ip -n ns1 link delete veth1
sudo ip -n ns2 link delete veth3

# delete namespaces ns1 for dns server ns2 for server
sudo ip netns delete ns1
sudo ip netns delete ns2

68 changes: 68 additions & 0 deletions examples/dnsdoctor/dnsdoctor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--
-- SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <[email protected]>
-- SPDX-License-Identifier: MIT OR GPL-2.0-only
--

-- DNS Doctoring : Rewrite DNS type A record to a private address for local clients

local xt = require("xtable")
local linux = require("linux")
local string = require("string")
local action = xt.action
local family = xt.family

local udp = 0x11
local dns = 0x35

local function nop() end

local function get_domain(skb, off)
local i = tostring(skb):sub(off+1):find('\0')
return skb:getstring(off, i - 1), i
end

local function dnsdoctor_tg(skb, par, userargs)
local target_dns, dst_ip, target_ip = string.unpack(">s4 I4 I4", userargs)
local thoff = par.thoff

local packetdst = skb:getuint32(16)
if packetdst ~= linux.hton32(dst_ip) then
return action.ACCEPT
end

local srcport = linux.ntoh16(skb:getuint16(thoff))
if srcport == dns then
local dnsoff = thoff + 8
local nanswers = linux.ntoh16(skb:getuint16(dnsoff + 6))

-- check the domain name
dnsoff = dnsoff + 12
local domainname, nameoff = get_domain(skb, dnsoff)

if domainname == target_dns then
dnsoff = dnsoff + nameoff + 4 -- skip over type, label fields
-- iterate over answers
for i = 1,nanswers do
local atype = linux.hton16(skb:getuint16(dnsoff + 2))
if atype == 1 then
skb:setuint32(dnsoff + 12, linux.hton32(target_ip))
end
dnsoff = dnsoff + 16
end
end
end

return action.ACCEPT
end

xt.target{
name = "dnsdoctor",
revision = 0,
family = family.UNSPEC,
proto = 0,
target = dnsdoctor_tg,
checkentry = nop,
destroy = nop,
hooks = 0,
}

28 changes: 28 additions & 0 deletions examples/dnsdoctor/libxt_dnsdoctor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
local luaxt = require("luaxt")
local family = luaxt.family

local function noop() end

local function dnsdoctor_init(par)
local target_ip = "10.1.2.3"
local target = 0
target_ip:gsub("%d+", function(s) target = target * 256 + tonumber(s) end)

local src_ip = "10.1.1.2"
local src = 0
src_ip:gsub("%d+", function(s) src = src * 256 + tonumber(s) end)

par.userargs = string.pack(">s4 I4 I4", "\x07lunatik\x03com", src, target)
end

luaxt.target{
revision = 0,
family = family.UNSPEC,
help = noop,
init = dnsdoctor_init,
print = noop,
save = noop,
parse = noop,
final_check = noop
}

61 changes: 61 additions & 0 deletions examples/dnsdoctor/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <[email protected]>
# SPDX-License-Identifier: MIT OR GPL-2.0-only

#!/bin/bash

set -eux

# add namespaces ns1 for dns server ns2 for server
sudo ip netns add ns1
sudo ip netns add ns2

# add link between host and the namespaces
sudo ip link add veth1 netns ns1 type veth peer name veth2
sudo ip link add veth3 netns ns2 type veth peer name veth4

# add ip address to the links
# DNS IP : 10.1.1.3
# Server IP : 10.1.2.3
sudo ip addr add 10.1.1.2/24 dev veth2
sudo ip -n ns1 addr add 10.1.1.3/24 dev veth1
sudo ip addr add 10.1.2.2/24 dev veth4
sudo ip -n ns2 addr add 10.1.2.3/24 dev veth3

# up the interfaces
sudo ip -n ns1 link set veth1 up
sudo ip -n ns2 link set veth3 up
sudo ip link set veth2 up
sudo ip link set veth4 up

# make a directory to setup dns server
mkdir dnstest
cd dnstest
python -m venv .venv
source .venv/bin/activate
pip install dnserver

# backup resolv config
echo "Backing up resolver config to /etc/resolver.conf.lunatik"
sudo cp -f /etc/resolv.conf /etc/resolv.conf.lunatik && \
sudo sed -i 's/nameserver/#nameserver/g' /etc/resolv.conf && \
echo "nameserver 10.1.1.3" | sudo tee -a /etc/resolv.conf && \

# add zone info and run dns server in ns1
echo """
[[zones]]
host = 'lunatik.com'
type = 'A'
answer = '192.168.10.1'
[[zones]]
host = 'lunatik.com'
type = 'NS'
answer = 'ns1.lunatik.com.'
[[zones]]
host = 'lunatik.com'
type = 'NS'
answer = 'ns2.lunatik.com.'
""" > zones.toml
sudo ip netns exec ns1 .venv/bin/dnserver --no-upstream zones.toml

2 changes: 1 addition & 1 deletion lib/luaxtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ static int luaxtable_docall(lua_State *L, luaxtable_t *xtable, luaxtable_info_t

lua_insert(L, base + 1); /* op */
lua_pop(L, 1); /* table */
lua_pushstring(L, info->userdata); /* userdata */
lua_pushlstring(L, info->userargs, LUAXTABLE_USERDATA_SIZE); /* userargs */

if (lua_pcall(L, nargs + 1, nret, 0) != LUA_OK) {
pr_err("%s error: %s\n", op, lua_tostring(L, -1));
Expand Down
4 changes: 3 additions & 1 deletion lib/luaxtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
#ifndef luaxtable_h
#define luaxtable_h

#define LUAXTABLE_USERDATA_SIZE 256

struct luaxtable_s;

typedef struct luaxtable_info_s {
char userdata[256];
char userargs[LUAXTABLE_USERDATA_SIZE];

/* used internally by the luaxtable kernel module */
struct luaxtable_s *data __attribute__((aligned(8)));
Expand Down
Loading

0 comments on commit 00f1fe4

Please sign in to comment.