- Part 1 – running tftp server non root (xinetd)
- Part 2 – the tftp client requires firewalld changes as well (this blog post)
- Part 3 – replacing xinetd with systemd
firewalld change on TFTP clients
The rest of this blog post will elaborate on what happens if you don’t do this.
The quick bit is: if you want to run the TFTP client on, say, RHEL7, you need to enable a service in firewalld on the client.
Obviously, I’m assuming you’ve got firewalld turned on, otherwise you wouldn’t be here.
/bin/firewall-cmd --permanent --zone public --add-service tftp-client
Puppet code, using crayfishx/firewalld, and the firewalld-cmd fix for this follows.
firewalld_service {'enable tftp client in firewalld': ensure => 'present', zone => 'public', service => 'tftp-client', }
Quick test:
tftp -4 -v 192.168.1.223 -c get foo/bar
the files transfer, but they’re empty?!
24 hours earlier.
Once I’d got the TFTP server running cleanly, unhelpful stuff was happening. I tried various things, in summary:
client:
$ tftp -4 -v 192.168.1.223 -c get foo Connected to 192.168.1.223 (192.168.1.223), port 69 getting from 192.168.1.223:foo to foo [netascii] @���� $ ls -al foo -rw-rw-r--. 1 ben ben 0 Mar 12 17:06 foo
server:
in.tftpd[2235]: RRQ from 192.168.1.245 filename foo
Yeah, complete with random noise from the client. Nice.
The file’s empty. There’s no error anywhere, and there’s nothing else logged. (Spoiler: if it was all working correctly, more would be logged on the server.)
- I turned off the server’s firewalld.
- I set selinux permissive on the server, and the following is clean.
ausearch -m AVC,USER_AVC -ts recent
From Part 1: the tftp daemon stays running.
That makes it much easier to trace, which at this point became necessary. Ran out of ideas. I’m not a syscall guru by any means, but it all started with truss on Solaris, and I still fall back to it sometimes.
I was expecting a problem reading the file, in which case the syscalls will show this.
I’ve annotated the end of it here; the PID is that root owned TFTP process. (see Part 1.)
# strace -fp <PID> # server IP [pid 2249] bind(0, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.1.223")}, 16) = 0 # client IP [pid 2249] connect(0, {sa_family=AF_INET, sin_port=htons(53450), sin_addr=inet_addr("192.168.1.245")}, 16) = 0 [pid 2249] setsockopt(0, SOL_IP, IP_MTU_DISCOVER, [0], 4) = 0 # open requested fgile [pid 2249] open("foo", O_RDONLY|O_LARGEFILE) = 1 [pid 2249] fstat64(1, {st_mode=S_IFREG|0444, st_size=29, ...}) = 0 [pid 2249] fcntl64(1, F_SETLK64, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=0, l_len=0}) = 0 [pid 2249] fcntl64(1, F_GETFL) = 0x20000 (flags O_RDONLY|O_LARGEFILE) [pid 2249] gettimeofday({1552408345, 873122}, NULL) = 0 # syslog 'Mar 12 16:32:25 in.tftpd[2249]: RRQ from 192.168.1.245 filename foo' [pid 2249] send(3, "<29>Mar 12 16:32:25 in.tftpd[224"..., 72, MSG_NOSIGNAL) = 72 [pid 2249] fstat64(1, {st_mode=S_IFREG|0444, st_size=29, ...}) = 0 [pid 2249] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f88000 # the file foo contains the output of 'date' [pid 2249] read(1, "Tue Mar 12 16:26:48 UTC 2019n", 4096) = 29 [pid 2249] read(1, "", 4096) = 0 # send the contents of the file .. [pid 2249] send(0, "31Tue Mar 12 16:26:48 UTC 2019"..., 33, 0) = 33 [pid 2249] poll([{fd=0, events=POLLIN}], 1, 1000) = 1 ([{fd=0, revents=POLLERR}]) # # here's the key error - E(rror) HOST UNREACH(able) # [pid 2249] recv(0, 0x9690b0, 516, 0) = -1 EHOSTUNREACH (No route to host) # also a bit weird; I think this is error logging # perhaps file handles are used to control the volume on debugging. [pid 2249] write(2, "fatal: ", 7) = -1 EBADF (Bad file descriptor) [pid 2249] write(2, "recvfrom_flags_with_timeout: rec"..., 54) = -1 EBADF (Bad file descriptor) [pid 2249] fstat64(1, {st_mode=S_IFREG|0444, st_size=29, ...}) = 0 [pid 2249] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f87000 [pid 2249] write(1, "n", 1) = -1 EBADF (Bad file descriptor) [pid 2249] exit_group(1) = ? [pid 2249] +++ exited with 1 +++
Not a problem reading the file.
It reads the file, tries to pass the data to the client, loses the client.
A network issue.
firewalld on the client
Process of elimination.
I don’t know tftp that well, but I do know that FTP traffic is a bit odd and involves two sessions, and firewalls have to cope with that.
Here we have a hand off between daemons on the server side, perhaps there’s some oddness with ports, and the *client* firewall has to keep track of it. Otherwise, you’ve got incoming data from an unexpected source.
Of course, you don’t usually run the TFTP client – that’s a Cisco device or appliance doing a backup, or it’s your PXE client. It just works, right?
So client side:
$ sudo systemctl stop firewalld [sudo] password for ben: $ sudo systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled) Active: inactive (dead) since Tue 2019-03-12 20:19:32 GMT; 6s ago Docs: man:firewalld(1) $ tftp -4 -v 192.168.1.223 -c get foo Connected to 192.168.1.223 (192.168.1.223), port 69 getting from 192.168.1.223:foo to foo [netascii] Received 29 bytes in 0.1 seconds [1854 bit/s] # # server side # in.tftpd[2724]: RRQ from 192.168.1.245 filename foo in.tftpd[2724]: Client 192.168.1.245 finished foo # # client side # $ sudo systemctl start firewalld $ tftp -4 -v 192.168.1.223 -c get foo Connected to 192.168.1.223 (192.168.1.223), port 69 getting from 192.168.1.223:foo to foo [netascii] Received 29 bytes in 0.1 seconds [2203 bit/s]
Insert scratchy screetching noise here.
Just ignore that last bit. Firewalld on the client I was testing with got screwed up. That shouldn’t have worked.
firewalld[28663]: WARNING: COMMAND_FAILED: '/usr/sbin/iptables -w2 -w --table mangle --delete POSTROUTING --out-interface virbr0 --protocol udp --destination-port 68 --jump CHECKSUM --checksum-fill' failed: iptables: No chain/target/match by that name.
- I rebooted and the problem came back.
- Post reboot, stopping firewalld fixed it again, starting it broke it again.
The firewalld tftp-client service was removed in 1.0.0 of firewalld (https://github.com/firewalld/firewalld/commit/624cb81067299d57ae5a9567dd1db1a0b10bc5be), and doesn’t work in RHEL 8.5 (using firewalld 0.9.3).
You now use a policy to allow responses from the TFTP server:
firewall-cmd –permanent –new-policy hostTftpTraffic
firewall-cmd –permanent –policy hostTftpTraffic –add-ingress-zone HOST
firewall-cmd –permanent –policy hostTftpTraffic –add-egress-zone ANY
firewall-cmd –permanent –policy hostTftpTraffic –add-service tftp
firewall-cmd –reload
For RHEL 7, the firewalld tftp-client service still works.
LikeLike
Thank you!
LikeLike