Skip to content

FTP confounds firewalls everywhere.

Look, FTP's actually kind of a tricky protocol. It's got this “separate channel” thing going on, that can be a bit fidgety to get stateful firewalls to handle, since the data bits go on a different (and undetermined) port, picked after the connection for the command channel (with dest port 21 on the FTP server) is already established, by sending traffic up at the application layer on that socket.

That said, this is a Done Thing.

So, when I needed to transfer some large (well, larger than the 5 MB permissible per email message) to Veritas from $CURRENT_EMPLOYER, and I knew that they had an explicitly listed FTP proxy in the web browser auto-configuration file, I was a bit surprised to find that it simply did not work. Didn't work when I told WS_FTP.EXE to use the proxy in its firewall configuration tab, didn't work when I played dumb and attempted to FTP through Internet Explorer, and wasn't failing just because I was connecting to a write-only directory over at Veritas; it just doesn't work. (Please don't suggest what you think the problem is here. My bet's on broken NAT in a Checkpoint firewall, but I don't really care. The solution, for me, is “open a ticket”.)

Fine, whatever. I'll just FTP it up from home.

And, lo and behold… that doesn't work either! And now it is my fault!

Ah, the wonderful world of ipfilter's obtuse method of building rules, wherein the software does what you say as obstinately as possible, rather than making some obvious assumptions about what you meant. It took some glancing back and forth between this and my very own /etc/ipnat.conf before the head-slapper moment. What they've got:

For NAT and ftp clients behind NAT to work, add the following minimum mappings to /etc/ipnat.conf:

# Use ipfilter ftp proxy for ftp client transfers mode: active
map ep1 192.168.1.0/24 -> 0.0.0.0/32 proxy port ftp ftp/tcp

# Map all tcp and udp connections from 192.168.1.0/24 to external IP address,
# changing the source port number to something between 40,000 and 60,000 inclusive
map ep1 192.168.1.0/24 -> 0.0.0.0/32 portmap tcp/udp 40000:60000

# For all other IP packets, map to the external IP address
map ep1 192.168.1.0/24 -> 0.0.0.0/32

Make sure all the `proxy' lines are before any generic `portmap' lines, as the first match always wins.

What I had (for the 802.11b subnet; 10.0.0.0/24 is wired):

map sf0 10.0.1.0/24 -> 66.92.234.98/32 portmap tcp/udp 40000:42999
map sf0 10.0.1.0/24 -> 66.92.234.98/32
map sf0 10.0.1.0/24 -> 66.92.234.98/32 proxy port ftp ftp/tcp

What's wrong with that picture?

Well, here's what works:

map sf0 10.0.1.0/24 -> 66.92.234.98/32 proxy port ftp ftp/tcp
map sf0 10.0.1.0/24 -> 66.92.234.98/32 portmap tcp/udp 40000:42999
map sf0 10.0.1.0/24 -> 66.92.234.98/32

The difference there is purely one of order, and given that I knew that both ipnat and ipf applied rules in a “first match” approach, this can be said to be my mistake.

But that's dumb!

Sure, I can conceive of some cases in which there is no clear choice, and in that case falling back to first-match might be fine, but in this case the rule that was simply never being matched (the ftp proxy) is a clear subset of the larger rule that was being matched first (the overall map for the IP address range), and it's counter-intuitive to have more specific rules for the same set of number space come before more general ones. Or, if you want to get really clever, how 'bout we go do another Done Thing and do the same damn thing that every OS out there does for routing: longest match! You know, considering we're effectively doing routing here anyway, and that's the expected behavior for configuring routes…

And it's not like this is hard to change, either! ipnat's not rereading this configuration file for every packet that comes in (that would be abysmally slow, would mean that you could make config changes on the fly without stopping/restarting ipnat, and is really not what the code's doing–I've looked)… it's parsing this config file once, at startup, building a state machine off that, and then matching packets against its in-memory, indexed-for-speed state machine. That means you're sitting there, at the time that you're building the state machine, and you really can notice that this rule will simply never be applied, and it will never be applied because it comes after this other rule for the same goddamn address range.

As with any time that you're doing something slightly other than what the user said, you should clearly print a warning message in this case. And, clearly, there's a limit to the breakage you can save the user from here, but it's really not that hard to make a very small change to the parsing mechanism that blows away 90% of the simple errors and gets the user what they actually wanted.

Which brings up another point: you can traverse this state machine and say, “Hey, this rule will never be matched.” In fact, if you're optimizing for efficiency, you're doing that anyway and throwing away unmatched rules so that the memory footprint of your state machine is smaller. Not warning the user of unmatched rules is lazy enough to be called grossly negligent. Even that would be enough to have smacked me upside the head the first time I misconfigured this.

Bullshit like this is what infuriates me about Windows (where, of course, it happens way the hell more often). Tell me what you're fucking doing, you damn computer. I'm the user: you work for me, not the other way 'round.

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*