Skip to content
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

add xtable userspace library #153

Merged
merged 3 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
103 changes: 99 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1149,9 +1149,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.
* `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.
* `destroy`: function to be called for destroying the xtable extension.
* `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,9 +1167,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.
* `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.
* `destroy`: function to be called for destroying the xtable extension.
* `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 @@ -1208,6 +1210,59 @@ netfilter hooks to Lua.
* `"LOCAL_OUT"`: `NF_INET_LOCAL_OUT`. The packet is generated by the local system.
* `"POST_ROUTING"`: `NF_INET_POST_ROUTING`. The packet is about to be sent out.

### luaxt

The `luaxt` [userspace library](usr/lib/xtable) provides support for generating userspace code for [xtable extensions](https://inai.de/projects/xtables-addons/).

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.
3. Run `LUAXTABLE_MODULE=<ext_name> make` to build the extension and `LUAXTABLE_MODULE=<ext_name> make install` (as root) to install the userspace plugin to the system.

Now load the extension normally using `iptables`.

#### `luaxt.match(opts)`

_luaxt.match()_ returns a new [luaxt](https://inai.de/projects/xtables-addons/) object for match extensions.
This function receives the following arguments:
* `opts`: a table containing the following fields:
* `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 `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)`

_luaxt.target()_ returns a new [luaxt](https://inai.de/projects/xtables-addons/) object for target extensions.
This function receives the following arguments:
* `opts`: a table containing the following fields:
* `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 `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`

_luaxt.family_ is a table that exports
address families to Lua.

* `"UNSPEC"`: Unspecified.
* `"INET"`: Internet Protocol version 4.
* `"IPV4"`: Internet Protocol version 4.
* `"IPV6"`: Internet Protocol version 6.
* `"ARP"`: Address Resolution Protocol.
* `"NETDEV"`: Device ingress and egress path
* `"BRIDGE"`: Ethernet Bridge.

# Examples

### spyglass
Expand Down Expand Up @@ -1356,6 +1411,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
sheharyaar marked this conversation as resolved.
Show resolved Hide resolved

# 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
15 changes: 15 additions & 0 deletions examples/dnsdoctor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 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
sheharyaar marked this conversation as resolved.
Show resolved Hide resolved

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
sheharyaar marked this conversation as resolved.
Show resolved Hide resolved

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 _, nameoff, name = skb:getstring(off):find("([^\0]*)")
return name, nameoff + 1
end

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

local packetdst = skb:getuint32(16)
lneto marked this conversation as resolved.
Show resolved Hide resolved
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 nop() 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)
sheharyaar marked this conversation as resolved.
Show resolved Hide resolved

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

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

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
sheharyaar marked this conversation as resolved.
Show resolved Hide resolved

set -eux

# add namespaces ns1 for dns server ns2 for server
lneto marked this conversation as resolved.
Show resolved Hide resolved
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

Loading