Forum Discussion
Matthew_Gamble_
Nimbostratus
Mar 12, 2009TFTP Server?
Is it possible to write a simple TFTP server as an iRule to return static content? I see some examples for load balancing TFTP requests, but not for actually acting as a TFTP server. I have a very small and static TFTP file that I need to distribute, and rather than running a dedicated TFTP server it would be far better if it could be deployed as an iRule.
Is this possible? Any resources I should be looking at?
25 Replies
- James_Quinby_46Historic F5 AccountInteresting question. I'm looking at a simple TFTP server written in Tcl (http://wiki.tcl.tk/12711) and trying to discern whether or not this is doable. There is also an iRule posted to the codeshare repository that helps do LB for tftp servers (http://devcentral.f5.com/wiki/default.aspx/iRules/TFTP_Load_Balancing.html).
It's probably possible to do this, though I'm wondering how the encoding of the file in chunks can be done most efficiently. There's an example of a simple UDP server implemented in an iRule here:
http://devcentral.f5.com/wiki/default.aspx/iRules/fast_DNS.html
...that returns an immediate answer from the LTM to certain DNS queries. - JRahm
Admin
I don't think the protocol handling would be that difficult, the problem is how to handle serving up the file, that command is disabled in iRules. Here's a start, maybe someone else has some ideas?when CLIENT_ACCEPTED { log local0. "Client connected from [IP::client_addr]" binary scan [UDP::payload] xc opcode log local0. "Opcode is $opcode" if { $opcode == 1 } { binary scan [UDP::payload] xxa* string log local0. "String is $string" set stringlist [split $string \000] set file [lindex $stringlist 0] set mode [lindex $stringlist 1] log local0. "File is $file, Mode is $mode" if { $mode != "octet" && $mode != "netascii" } { reject } else { log local0. "Send the file!" write goes here } } else { reject } } - JRahm
Admin
BTW, the code above is hacked from the example jquinby mentioned on the TCL wiki. Obviously don't need a few of those variables, just there for development. - JRahm
Admin
The logic is working from a pumpkin tftp client in netascii mode:
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server : Client connected from 10.10.10.10
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server : Opcode is 1
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server : String is test.txtÀnetasciiÀtsizeÀ0ÀblksizeÀ2048ÀtimeoutÀ30À
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server : File is test.txt, Mode is netascii
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server : Send the file! - James_Quinby_46Historic F5 AccountI think dropping the file into some sort of class (along the lines of the http-server-in-an-irule) is a possibility, but the TFTP protocol specifies 512 byte chunks sent to the client after each previous one is ACK'ed.
1. client sends RRQ (opcode 1) with filename
2. Server sends block 1, client rx's and ACKs block 1.
3. Server sends block 2, client acks block 2, and so on.
4. As soon as the client gets a block thats <512 bytes, it knows that this is the final block.
We can also do away with the necessity of handling write-requests (WRQ, opcode 2), since this is read-only. Respond to them with opcode 5 ("error", along with an error number and some useful ascii text). - James_Quinby_46Historic F5 AccountI'm groping towards lrange here, and iterating over $::my_tftp_file_to_send.
Unfortunately, as citizen_elah points out, initiating the outbound connection on the ephemeral port for sending the data is problematic. - Nat_Thirasuttakorn
Employee
how about we use something like this
when CLIENT_ACCEPTED {
set destination to client
node [IP::client_addr] [UDP::client_port]
snat to virtual address. BIG-IP will pick ephemeral port automatically
snat [IP::local_addr]
}
when CLIENT_DATA {
replace with data to be sent
UDP::payload replace 0 [UDP::payload length] [binary format H* 0003 0001 ]
}
when SERVER_DATA {
check what is block number
binary scan [UDP::payload] I x
set opcode [expr $x >> 16]
set blockid [expr $x & 0xffff]
we could probably pre-format packet in hex and put in array variable or class...
UDP::respond $pre_format_packet([expr $blockid + 1])
drop this packet...
UDP::drop
}
sorry, this is not tested...
Nat - James_Quinby_46Historic F5 AccountAh, a NAT. Forgot about that - good catch.
All that remains (and I suppose it's easy enough for a non-developer to say) is a method of storing the file, then iterating over it in 512 byte chunks until one of length < 512 can be sent.
For better compliance with the TFTP spec, the server is expected to deal well with a "resend block X" command sent by the client. - Nat_Thirasuttakorn
Employee
I got a chance to test ...here is my irulewhen CLIENT_ACCEPTED { set blockdata(1) [binary format H* 000300014620494e495452442e494d473b31\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 2020202020202020202020202020202009696e697472642e696d670d0a4620524541444d\ 452e3b312020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202009524541444d450d0a4620564d\ 4c494e555a2e3b3120202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020] set blockdata(2) [binary format H* 000300022020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020\ 20202020202020202020202020202020202009766d6c696e757a0d0a] node [IP::client_addr] [TCP::client_port] snat [IP::local_addr] } when CLIENT_DATA { UDP::payload replace 0 [UDP::payload length] $blockdata(1) } when SERVER_DATA { binary scan [UDP::payload] I tmp set opcode [expr $tmp >> 16] set blockid [expr $tmp & 0xffff] if { $blockid < [array size blockdata] } { UDP::respond $blockdata([expr $blockid + 1]) } UDP::drop }
it works when I connect from linux tftp client... right, this irule is not complete. I see that citizen_elah and jquinby already started a good one. so this can be merge with irule in previous post. For example, no filename checking and no type checking, so this irule will always return same data no matter what filename/mode client ask for. - James_Quinby_46Historic F5 AccountA little closer. This works with the pre-embedded blocks that natty76 is using in his. I've created an external file class called "tftp_file". Just need to do that bit. Two things left undone:
1. client mode switching (netascii v. binary)
2. client retries (resend block X)when CLIENT_ACCEPTED { log local0. "Client connected from [IP::client_addr]" binary scan [UDP::payload] xc opcode log local0. "Opcode is $opcode" if { $opcode == 1 } { binary scan [UDP::payload] xxa* string log local0. "String is $string" set stringlist [split $string \000] set file [lindex $stringlist 0] set mode [lindex $stringlist 1] log local0. "File is $file, Mode is $mode" if { $mode != "octet" && $mode != "netascii" } { reject } else { log local0. "Send the file!" set blockdata(1) [binary format H* 000300014620494e495452442e494d473b3120202020202020202020202020202020202020\ 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202\ 020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020\ 202009696e697472642e696d670d0a4620524541444d452e3b312020202020202020202020202020202020202020202020202020202020202020\ 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020\ 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020\ 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202009524541444d45\ 0d0a4620564d4c494e555a2e3b3120202020202020202020202020202020202020202020202020202020202020202020202020202020202020202\ 020202020202020202020202020] set blockdata(2) [binary format H* 0003000220202020202020202020202020202020202020202020202020202020202020202020\ 2020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020\ 202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202009766d6c696e757a0d0a] UDP::respond $blockdata(1) UDP::respond $blockdata(2) } } else { reject } }
Recent Discussions
Related Content
DevCentral Quicklinks
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com
Discover DevCentral Connects
