tftp part 2 – the tftp client requires a firewalld as well


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 -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:


$ tftp -4 -v -c get foo
Connected to (, port 69
getting from to foo [netascii]
$ ls -al foo
-rw-rw-r--. 1 ben ben 0 Mar 12 17:06 foo


in.tftpd[2235]: RRQ from 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("")}, 16) = 0
   # client IP
[pid  2249] connect(0, {sa_family=AF_INET, sin_port=htons(53450), sin_addr=inet_addr("")}, 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 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 -c get foo
Connected to (, port 69
getting from to foo [netascii]
Received 29 bytes in 0.1 seconds [1854 bit/s]

# server side

in.tftpd[2724]: RRQ from filename foo
in.tftpd[2724]: Client finished foo

# client side

$ sudo systemctl start firewalld 
$ tftp -4 -v -c get foo
Connected to (, port 69
getting from 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.

2 thoughts on “tftp part 2 – the tftp client requires a firewalld as well

  1. The firewalld tftp-client service was removed in 1.0.0 of firewalld (, 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.


Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s