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

Implement expression for matching on source IP? #5

Open
twitchyliquid64 opened this issue Jan 4, 2019 · 7 comments
Open

Implement expression for matching on source IP? #5

twitchyliquid64 opened this issue Jan 4, 2019 · 7 comments

Comments

@twitchyliquid64
Copy link

Heya,

TL;DR: I'm trying to implement the equivalent of running:

sudo strace nft add rule nat postrouting ip saddr 192.168.69.2 masquerade

It doesnt look like this library supports source address rules out of the box. I found this which doesnt seem to be terribly different, but I also dont understand (we are throwing bytecode down the netlink socket?)

I tried stracing nft, I tried poking around the nfnl source ... everything seems really hard to follow. Could you give me some direction on how to implement this? happy to write the code, I'm just very lost rn.

Tail end of the strace:


socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER) = 3
fcntl(3, F_SETFL, O_RDONLY|O_NONBLOCK)  = 0
mmap(NULL, 204800, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7feba6a75000
setsockopt(3, SOL_SOCKET, SO_SNDBUFFORCE, [131072], 4) = 0
sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[{{len=20, type=0x10 /* NLMSG_??? */, flags=NLM_F_REQUEST, seq=0, pid=0}, "\x00\x00\x0a\x00"}, {{len=184, type=0xa06 /* NLMSG_??? */, flags=NLM_F_REQUEST|0xe00, seq=1, pid=0}, "\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x8c\x00\x04\x80"...}, {{len=20, type=0x11 /* NLMSG_??? */, flags=NLM_F_REQUEST, seq=2, pid=0}, "\x00\x00\x0a\x00"}], iov_len=224}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 224
select(4, [3], NULL, NULL, {tv_sec=0, tv_usec=0}) = 0 (Timeout)
munmap(0x7feba6a75000, 204800)          = 0
close(3)                                = 0
exit_group(0)                           = ?
@stapelberg
Copy link
Collaborator

Hey! Here’s a few pointers:

  1. You can work in a network namespace to play around with nft without any consequences for your host system:
# ip netns add nattest
# ip netns exec nattest /bin/bash
  1. You can use nft’s --debug all flag to get a bunch more details about what’s going on. For this particular case:
# nft --debug all add rule nat postrouting ip saddr 192.168.69.2 masquerade
[…]
ip nat postrouting 
  [ payload load 4b @ network header + 12 => reg 1 ]
  [ cmp eq reg 1 0x0245a8c0 ]
  [ masq ]
[…]

These are the expressions you’ll need to model with your Go code.

  1. Check out
    func TestConfigureNAT(t *testing.T) {
    — you’ll want to duplicate that, run nft through strace, stuff the resulting byte slices into the test and then work on the Go portion until it matches. I’ve done this to verify that all the building blocks are in place, and will commit it for illustration purposes in a sec.

Hope that helps,

@twitchyliquid64
Copy link
Author

Thank you so much!

Would you be open to a PR where I implement an abstraction for building these rules? Something like:

builder := nftables.ExprBuilder{Chain: chain}
if err := builder.FilterSourceAddr(srcIP); err != nil {
  // ... Errors such as ipv6 address /w ipv4 table family
}
builder.ActionCounter(&counter)
builder.ActionMasquerade()
conn.AddRule(&Rule{ ... Exprs: builder.Expr() ... })

@stapelberg
Copy link
Collaborator

Disclaimer: I haven’t thought much about how a good abstraction layer would look like.

To me, an important question that I can’t answer yet, is whether the ideal abstraction would implement nft(8)’s config/command line syntax (and how stable those are), or whether something else makes more sense.

Intuitively, I’d gravitate towards implementing nft’s syntax: that way, users could just copy their already-existing configuration files. What do you think?

@twitchyliquid64
Copy link
Author

For the sake of minimizing the learning curve, I agree we should keep the concepts as similar to nft & nft's representation as possible.

However, I do think the abstraction needs to be embodied in types; any approach which does string parsing is probably re-inventing the wheel, and loosing the benefits of a type system.

Maybe:

builder.Table(Table{
  Name: ...
  Family: ...
  Chain: &Chain{
    ...
    Rules: []builder.Rules{
      builder.FilterSaddr(saddr),
      builder.Masquerade(),
    },
  },
}).Build()

Thoughts?

@stapelberg
Copy link
Collaborator

However, I do think the abstraction needs to be embodied in types; any approach which does string parsing is probably re-inventing the wheel, and loosing the benefits of a type system.

I’m not sure about that — neither for nor against, I just can’t tell. Given that, I think the best course of action is to develop your abstraction in a separate repository and see how well it works out in practice.

@twitchyliquid64
Copy link
Author

I'll give it a try and report back.

@twitchyliquid64
Copy link
Author

I tried a few things but complexity always crept in to the stage where the APIs were roughly equivalent.

The best thing we can do is probably a bunch of examples & maybe a paragraph on using nft --debug all to work out what to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants