Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
Answers

TFTP 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?
0
Rate this Question

Answers to this Question

placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Interesting 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.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
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 }
}

0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
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.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
The logic is working from a pumpkin tftp client in netascii mode:
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server <CLIENT_ACCEPTED>: Client connected from 10.10.10.10
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server <CLIENT_ACCEPTED>: Opcode is 1
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server <CLIENT_ACCEPTED>: String is test.txtÀnetasciiÀtsizeÀ0ÀblksizeÀ2048ÀtimeoutÀ30À
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server <CLIENT_ACCEPTED>: File is test.txt, Mode is netascii
Mar 12 06:30:25 tmm tmm[1893]: Rule tftp_server <CLIENT_ACCEPTED>: Send the file!
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
I 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).
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
I'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.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
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 <first-512-byte-data>]
}
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
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Ah, 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.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
I got a chance to test ...here is my irule
when 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.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
A 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 }
}
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Well, now I'm stuck. I have the beginnings of the iteration in 512 byte chunks - I can reference an exteranal uu-encoded file and return it in the proper size. The trouble is that the client is expecting opcode 3 to indicate that data is coming. natty did this by embedding it directly in the block that was being written back (if I'm reading those lines right). I seem to be getting hung up in the binary formatting stuff:
    
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!"
#log local0. "I ought to be sending something here...."
set block [binary format H* "3[string range $::tftp_file 0 511]"]
log local0. "Block is now $block"
UDP::respond $block
}
} else { reject }
}

I don't seem to get anything at all for the "Block is now $block" debugging statement up there. If I use just this:
   
set block "3[string range $::tftp_file 0 511]"

I get this on the client side, which indicates that my "3" is being evaluated as part of the data, and not an opcode.
   
tftp> get foo.txt
getting from 10.10.10.30:foo.txt to foo.txt [netascii]
sent RRQ <file=foo.txt, mode=netascii>
received opcode=3354 sent RRQ <file=foo.txt, mode=netascii>
received opcode=3354 sent RRQ <file=foo.txt, mode=netascii>
received opcode=3354 sent RRQ <file=foo.txt, mode=netascii>
received opcode=3354 sent RRQ <file=foo.txt, mode=netascii>
received opcode=3354 Transfer timed out.
tftp>

It occurs to me that I'm probably also missing other UDP header parameters.
(Incidentally, is there any chance that a moderator could kindly break some of the long lines upthread? Make this page a little easier on the eyes/browser?

By the way - lrange was a bust, as it returns inclusive elements of a list. The file is but a one-element-list, so 'string range' was the trick.

0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Mine is working from the pumpkin tftp client. Still needs to be cleaned up a bit, though:
  
when RULE_INIT {
set blockdata(1) [binary format H* 0003000123212F7573722F62696E2F7065726C202D77DA757365207374726963743BDA757365204E65743A3A534E4D50207177283A736E6D70293BDADA6D7920247573616765203D20226C746D5F696E74537461742E706C203C686F73743E203C736E6D7020636F6D6D756E6974793E203C696E746572666163653E203C696E74657276616C3E223BDADA646965202255736167653A202475736167655C6E222069662024234152475620213D20333BDADA6D792024686F7374203D2024415247565B305D3BDA6D792024736E6D705F636F6D6D203D2024415247565B315D3BDA6D792024696E74203D2024415247565B325D3BDA6D792024696E74657276616C203D2024415247565B335D3BDADA63686F6D70202824686F7374202C2024736E6D705F636F6D6D202C2024696E74202C2024696E74657276616C293BDADA6D7920246C746D5F496E42797465735F496E646578203D2022312E332E362E312E342E312E333337352E322E312E322E342E342E332E312E33223BDA6D7920246C746D5F4F757442797465735F496E646578203D2022312E332E362E312E342E312E333337352E322E312E322E342E342E332E312E35223BDADADA6D792025696E745F6D6170203D202822312E31223D3E222E332E34392E34362E3439222CDA20202020202020202020202020202022312E32223D3E222E332E34392E34362E3530222CDA2]
set blockdata(2) [binary format H* 000300020202020202020202020202020202022312E33223D3E222E332E34392E34362E3531222CDA20202020202020202020202020202022312E34223D3E222E332E34392E34362E3532222CDA20202020202020202020202020202022312E35223D3E222E332E34392E34362E3533222CDA20202020202020202020202020202022312E36223D3E222E332E34392E34362E3534222CDA20202020202020202020202020202022312E37223D3E222E332E34392E34362E3535222CDA20202020202020202020202020202022312E38223D3E222E332E34392E34362E3536222CDA20202020202020202020202020202022312E39223D3E222E332E34392E34362E3537222CDA20202020202020202020202020202022312E3130223D3E222E342E34392E34362E34392E3438222CDA20202020202020202020202020202022312E3131223D3E222E342E34392E34362E34392E3439222CDA20202020202020202020202020202022312E3132223D3E222E342E34392E34362E34392E3530222CDA20202020202020202020202020202022312E3133223D3E222E342E34392E34362E34392E3531222CDA20202020202020202020202020202022312E3134223D3E222E342E34392E34362E34392E3532222CDA20202020202020202020202020202022312E3135223D3E222E342E34392E34362E34392E3533222CDA2020202020202020]
set blockdata(3) [binary format H* 000300032020202020202022312E3136223D3E222E342E34392E34362E34392E3534222CDA20202020202020202020202020202022322E31223D3E222E332E35302E34362E3439222CDA20202020202020202020202020202022322E32223D3E222E332E35302E34362E3530222CDA20202020202020202020202020202022322E33223D3E222E332E35302E34362E3531222CDA20202020202020202020202020202022322E34223D3E222E332E35302E34362E3532222CDA202020202020202020202020202020226D676D74223D3E222E342E3130392E3130332E3130392E31313622293BDADA6D7920246C746D5F696E744279746573496E203D20246C746D5F496E42797465735F496E646578202E2024696E745F6D61707B24696E747D3BDA6D7920246C746D5F696E7442797465734F7574203D20246C746D5F4F757442797465735F496E646578202E2024696E745F6D61707B24696E747D3BDADA6D7920282473657373696F6E2C20246572726F7229203D204E65743A3A534E4D502D3E73657373696F6E28DA20202020202020202D686F73746E616D65202020202020203D3E2024686F73742CDA20202020202020202D636F6D6D756E6974792020202020203D3E2024736E6D705F636F6D6D2CDA20202020202020202D706F727420202020202020202020203D3E203136312CDA20202020202020202D76657273696F6E2]
set blockdata(4) [binary format H* 000300040202020202020203D3E2027736E6D70763263272CDA20202020202020202D6E6F6E626C6F636B696E67202020203D3E2030DA2020202020202020293BDADA6966202821646566696E6564202473657373696F6E29DA20202020202020207BDA20202020202020207072696E7420225265636569766564206E6F20534E4D5020726573706F6E73652066726F6D2024686F73745C6E223BDA20202020202020207072696E742053544445525220224572726F723A20246572726F725C6E223BDA202020202020202065786974202D313BDA20202020202020207DDADA2347657420666972737420696E7374616E6365DA6D7920246F6964735F31203D202473657373696F6E2D3E6765745F7265717565737428DA2020202020202020202020202020202020202D76617262696E646C697374203D3EDA2020202020202020202020202020202020205B246C746D5F696E744279746573496E2C20246C746D5F696E7442797465734F75745D20293BDADA736C6565702024696E74657276616C3BDADA23476574207365636F6E6420696E7374616E6365DA6D7920246F6964735F32203D202473657373696F6E2D3E6765745F7265717565737428DA2020202020202020202020202020202020202D76617262696E646C697374203D3EDA2020202020202020202020202020202020205B246C746D5F696E744279746573496E2C20]
set blockdata(5) [binary format H* 00030005246C746D5F696E7442797465734F75745D20293BDADA2343616C63756C617465205261746573DA6D792024726174655F696E203D2028246F6964735F322D3E7B246C746D5F696E744279746573496E7D202D20246F6964735F312D3E7B246C746D5F696E744279746573496E7D292A38202F2024696E74657276616C3BDA6D792024726174655F6F7574203D2028246F6964735F322D3E7B246C746D5F696E7442797465734F75747D202D20246F6964735F312D3E7B246C746D5F696E7442797465734F75747D292A38202F2024696E74657276616C3BDADA23526F756E6420746F20696E7465676572DA24726174655F696E203D20696E742824726174655F696E202B202E35293BDA24726174655F6F7574203D20696E74202824726174655F6F7574202B202E35293BDA6D792024726174655F746F74616C203D2024726174655F696E202B2024726174655F6F75743BDADA235072696E7420526573756C7473DA7072696E7420225C6E5C6E5C7424726174655F696E20626974732F7365636F6E642028494E295C6E223BDA7072696E7420225C7424726174655F6F757420626974732F7365636F6E6420284F5554295C6E223BDA7072696E7420225C7424726174655F746F74616C20626974732F7365636F6E642028544F54414C295C6E223B0D0A]
set ::debug 1
}
when CLIENT_ACCEPTED {
binary scan [UDP::payload] xc opcode
if { $::debug } { log local0. "Opcode is $opcode" }
switch $opcode {
1 {
binary scan [UDP::payload] xxa* string
if { $::debug } { log local0. "String is $string" }
set file [lindex [split $string \000] 0]
set mode [lindex [split $string \000] 1]
if { $::debug} { log local0. "File is $file, Mode is $mode" }
if { $mode == "octet" || $mode == "netascii" } {
if { $file eq "test.txt" } {
if { $::debug } { log local0. "Request is valid" }
UDP::respond $::blockdata(1)
UDP::respond $::blockdata(2)
UDP::respond $::blockdata(3)
UDP::respond $::blockdata(4)
UDP::respond $::blockdata(5)
} else {
if { $::debug } { log local0. "Request is invalid" }
reject
}
} else {
if { $::debug } { log local0. "Invalid mode selected" }
reject
}
}
2 {
if { $::debug } { log local0. "Write request not supported here" }
}
3 {
if { $::debug } { log local0. "Data receipt not supported here" }
}
4 {
if { $::debug } { log local0. "Ack from client received" }
}
5 {
if { $::debug } { log local0. "Error: $string" }
}
default {
if { $::debug } { log local0. "Opcode $opcode is invalid" }
reject
}
}
}


contents received are in the tftp file are:

#!/usr/bin/perl -wÚuse strict;Úuse Net::SNMP qw(:snmp);ÚÚmy $usage = "ltm_intStat.pl <host> <snmp community> <interface> <interval>";ÚÚdie "Usage: $usage\n" if $#ARGV != 3;ÚÚmy $host = $ARGV[0];Úmy $snmp_comm = $ARGV[1];Úmy $int = $ARGV[2];Úmy $interval = $ARGV[3];ÚÚchomp ($host , $snmp_comm , $int , $interval);ÚÚmy $ltm_InBytes_Index = "1.3.6.1.4.1.3375.2.1.2.4.4.3.1.3";Úmy $ltm_OutBytes_Index = "1.3.6.1.4.1.3375.2.1.2.4.4.3.1.5";ÚÚÚmy %int_map = ("1.1"=>".3.49.46.49",Ú "1.2"=>".3.49.46.50",Ú#ã2#Óâ"ã2ãC’ãCbãS"Í¢#ãB#Óâ"ã2ãC’ãCbãS""Í¢#ãR#Óâ"ã2ãC’ãCbãS2"Í¢#ãb#Óâ"ã2ãC’ãCbãSB"Í¢#ãr#Óâ"ã2ãC’ãCbãSR"Í¢#ã‚#Óâ"ã2ãC’ãCbãSb"Í¢#ã’#Óâ"ã2ãC’ãCbãSr"Í¢#ã#Óâ"ãBãC’ãCbãC’ãC‚"Í¢#ã#Óâ"ãBãC’ãCbãC’ãC’"Í¢#ã"#Óâ"ãBãC’ãCbãC’ãS"Í¢#ã2#Óâ"ãBãC’ãCbãC’ãS"Í¢#ãB#Óâ"ãBãC’ãCbãC’ãS""Í¢#ãR#Óâ"ãBãC’ãCbãC’ãS2"Í¢ "1.16"=>".4.49.46.49.54",Ú "2.1"=>".3.50.46.49",Ú "2.2"=>".3.50.46.50",Ú "2.3"=>".3.50.46.51",Ú "2.4"=>".3.50.46.52",Ú "mgmt"=>".4.109.103.109.116");ÚÚmy $ltm_intBytesIn = $ltm_InBytes_Index . $int_map{$int};Úmy $ltm_intBytesOut = $ltm_OutBytes_Index . $int_map{$int};ÚÚmy ($session, $error) = Net::SNMP->session(Ú -hostname => $host,Ú -community => $snmp_comm,Ú -port => 161,Ú -versionÓâw6æ×c&2rÍ¢Öæöæ&Æö6¶–ærÓâ
¢“½­¦–b‚FVf–æVBG6W76–ö❢½¢&–çB%&V6V—fVBæò4äÕ&W7öç6Rg&öÒF†÷7EÆâ#½¢&–çB5DDU%"$W'&÷#¢FW'&÷%Æâ#½¢W†—BÓ½¢Ý­¢4vWBf—'7B–ç7Fæ6]¦×’Fö–G5óÒG6W76–öâÓævWE÷&WVW7B¢×f&&–æFÆ—7BÓí¢²FÇFÕö–çD'—FW4–âÂFÇFÕö–çD'—FW4÷WEÒ“½­§6ÆVWF–çFW'fý­¢4vWB6V6öæB–ç7Fæ6]¦×’Fö–G5ó"ÒG6W76–öâÓævWE÷&WVW7B¢×f&&–æFÆ—7BÓí¢²FÇFÕö–çD'—FW4–âÂ$ltm_intBytesOut] );ÚÚ#Calculate RatesÚmy $rate_in = ($oids_2->{$ltm_intBytesIn} - $oids_1->{$ltm_intBytesIn})*8 / $interval;Úmy $rate_out = ($oids_2->{$ltm_intBytesOut} - $oids_1->{$ltm_intBytesOut})*8 / $interval;ÚÚ#Round to integerÚ$rate_in = int($rate_in + .5);Ú$rate_out = int ($rate_out + .5);Úmy $rate_total = $rate_in + $rate_out;ÚÚ#Print ResultsÚprint "\n\n\t$rate_in bits/second (IN)\n";Úprint "\t$rate_out bits/second (OUT)\n";Úprint "\t$rate_total bits/second (TOTAL)\n";


Haven't quite figured out the correct ascii to hex conversion. I am cutting into blocks first, then prepending the blocks with 0003xxxx, where xxxx is the blockID.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
first of all, sorry for long line.

regarding the block problem
I did not check RFC but from packet trace it shows that both opcode and block-id use 2 bytes (short integer)
I think you may change it to something like this

set opcode 3
set blockid 1
set block [binary format SSH* $opcode $blockid [string range $::tftp_file 0 511] ]
UDP::respond $block

sorry again, not tested
Nat
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
The important stuff doesn't require a scroll... :-)
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Ok, got the enconding figured out (I ripped it straight from the Tcl wiki). Here's the latest version.
  
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 data [binary format xcSa* 3 1 [string range $::tftp_file 0 511]]
log local0. "data = $data"
UDP::respond $data
}
} else { reject }
}

So - incrementing the block ID (the "1" in the format command above) along with moving to the next 512 bytes is all that's left, modulo the changes citizen_elah has added that improve the protocol handling. This is pretty close. This will hopefully let us avoid having to embed the file into the actual rule.

0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Wow, I can't believe how helpful you have all been so far. This looks like it will do what I need.
A big thank-you to everyone who's posted in this thread <img src=/DesktopModules/NTForums/themes/DC4/emoticons/biggrin.gif width=20 height=20>
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
OK, stuck again. I can't test it because I can't get it to parse right in the IRE. I'm missing something dumb here, but am going cross-eyed trying to find it. I'll post what I have and come back to it later after I clear my head.

(updated with some comments and a little visual cleanup)

  
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 block_id 1
set block_start 0
set block_len 512
set last 0

while { $last < 1 } {

log local0. "Block number $block_id"

set data [binary format xcSa* 3 $block_id [string range $::tftp_file $block_start $block_end]]

# test for the last block - is it less than 512 bytes? If so, set a flag
if { [string length [string range $::tftp_file $block_start $block_end]] < 512 } then {

set last 1
}

log local0. "data = $data"

# send the data

UDP::respond $data

# increment block_id and shift the pointers to the
# next block of data to send

incr block_id
block_start = block_len
set block_len = block_len + 512

}

# We should be breaking out of the while loop now
# The last block should have been sent.

}
}

} else { reject }
}


0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Ok, this will do it. I'm not so good at nested loops and suchlike, so I ham-handed it as seen below. Right up front, here are the caveats (and areas for improvement).

1. The file needs to be dropped into /var/class on the LTM. You'll need to create an external data file class in order to refer to it.

2. I haven't tested simultaneous downloads, so I don't know how it will perform under load

3. We're not checking for client retries. The iRule just crams the download at the client. We're not checking for a filename, either. Whatever the client requests, we're sending the same file.

4. octet and netascii modes seem to work fine, but I've only been testing with a Linux command-line TFTP client.

5. I'm only sending 7 blocks because that's how large of a test file I used - 6 full blocks, plus a partial 7th. The tftp client will automatically see the last small block as the final message and end the session accordingly. You'll need to calculate the size of the file and add more 'set data' and 'respond UDP' lines as appropriate (unless someone wants to fix that with a couple of loops <img src='http://devcentral.f5.com/desktopmodules/ntforums/images/emoticons/smile.gif' height='20' width='20' border='0' title='Smile' align='absmiddle'> )
  

class tftp_file {
type string
filename "/var/class/file.class"
}

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 data [binary format xcSa* 3 1 [string range $::tftp_file 0 511]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 2 [string range $::tftp_file 512 1023]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 3 [string range $::tftp_file 1024 1535]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 4 [string range $::tftp_file 1536 2047]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 5 [string range $::tftp_file 2048 2560]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 6 [string range $::tftp_file 2561 3073]]
#log local0. "data = $data, size = [string length $data]"
UDP::respond $data

set data [binary format xcSa* 3 7 [string range $::tftp_file 3074 3586]]
log local0. "data = $data, size = [string length $data]"
UDP::respond $data
}

} else { reject }
}


My test session looked like this:

  
tftp> get foo.txt
getting from 10.10.10.30:foo.txt to foo.txt [octet]
sent RRQ <file=foo.txt, mode=octet>
received DATA <block=1, 512 bytes>
sent ACK <block=1>
received DATA <block=2, 512 bytes>
sent ACK <block=2>
received DATA <block=3, 512 bytes>
sent ACK <block=3>
received DATA <block=4, 512 bytes>
sent ACK <block=4>
received DATA <block=5, 512 bytes>
sent ACK <block=5>
received DATA <block=6, 512 bytes>
sent ACK <block=6>
received DATA <block=7, 176 bytes>
Received 3248 bytes in 0.0 seconds [inf bits/sec]
tftp>
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Also - the file you drop into file.class has to follow the same format as in the iRule for returning HTML and graphics from an iRule:

http://devcentral.f5.com/wiki/default.aspx/iRules/LTMMaintenancePage.html

Pay particular attention to the treatment of text within the class file. No newlines are allowed, the block must be quoted, and there must be a trailing comma.

The warning about memory is also useful - the file will be read into (and served from) memory, so be wary of using this method to transfer giant files. Not that anyone would do that with TFTP, but you never know.

If you're using this iRule to serve binary data, there may need to be additional tweaks. You might have uuencode it first, then add the b64decode statement (see the above page and the lines for sending the PNG file).
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Hey all, I wrote up a tech tip on the excellent work done here in the forum...great collaboration guys!

http://devcentral.f5.com/Default.aspx?tabid=63&articleType=ArticleView&articleId=343 Click here
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
your irule looks perfect.
just a comment that you may forward request onto server side which you will get 2 things in return
- server will send file from ephemeral port (not port 69)
- retry in case packet loss. you can have routine in SERVER_DATA that replies content based on ack it receives. if let say block 10 is loss, client will resend ack-for block 9. irule can detect block id from ack message and response with block "ack + 1"

however, I didnt read RFC, feel free to correct me if I am wrong.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
natty76 -

The behavior you describe is correct. The way I read it, the server should wait for ACK(block) before sending block(ACK+1). In any case, the final rule looks great. I was a little surprised to not find one out there already.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Very nice collaboration, indeed.

Now if you guys can just figure out a way to serve the LTM ISO from a datagroup, you could build an "Enterprise Manager in an iRule" rule! :D

Aaron
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Yeah, I read the RFC the same way, the server really should ack each segment before sending the next block. I'll work on completing the implementation for future use cases, but it will be a little while.
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Now if you guys can just figure out a way to serve the LTM ISO from a datagroup, you could build an "Enterprise Manager in an iRule" rule! :D


Hmmm....
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
What format are you putting the file on disk in? I've tried text, binary, and hex, but I keep getting this error:


Mar 23 15:24:54 tmm tmm[13663]: Rule Test-TFTP_Server <CLIENT_ACCEPTED>: Opcode is 1
Mar 23 15:24:54 tmm tmm[13663]: Rule Test-TFTP_Server <CLIENT_ACCEPTED>: String is test.txtÀnetasciiÀ
Mar 23 15:24:54 tmm tmm[13663]: Rule Test-TFTP_Server <CLIENT_ACCEPTED>: File is test.txt, Mode is netascii
Mar 23 15:24:54 tmm tmm[13663]: Rule Test-TFTP_Server <CLIENT_ACCEPTED>: Block is integer, no increment necessary
Mar 23 15:24:54 tmm tmm[13663]: 01220001:3: TCL error: Rule Test-TFTP_Server <CLIENT_ACCEPTED> - expected hexadecimal string but got "M2M2NjZjNjE3NDJkNzA3MjZmNjY2OTZjNjUzZWRhMjAyMDNjMjEyZDJkMjA0MzZmNmU2NjY5Njc3NTcyNjE3NDY5NmY2ZTIwNTA3MjZmNjY2OTZjNjUyMDRkNDcyMDJkMjA0ZDYxNzI2MzY4MjAzMjMzNzI2NDJjMjAzMjMwMzAzOTIwMmQyZDNlZGEyMDIwM2M1MDcyNmY3NjY5NzM2OTZmNmU1ZjQ1NmU2MTYyNmM2NTIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjA3NTYxM2Q1YzIyNmU2MTVjMjIzZTU5NjU3MzNjMmY1MDcyNmY3NjY5NzM2OTZmNmU1ZjQ1NmU2MTYyNmM2NTNlZGEyMDIwM2M1MjY1NzM3OTZlNjM1ZjRmNmU1ZjUyNjU3MzY1NzQyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjA3NTYxM2Q1YzIyNmU2MTVjMjIzZTU5NjU3MzNjMmY1MjY1NzM3OTZlNjM1ZjRmNmU1ZjUyNjU3MzY1NzQzZWRhMjAyMDNjNTI2NTczNzk2ZTYzNWY1MjYxNmU2NDZmNmQ1ZjQ0NjU2YzYxNzkyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwNzU2MTNkNWMyMjZlNjE1YzIyM2UzMjNjMmY1MjY1NzM3OTZlNjM1ZjUyNjE2ZTY0NmY2ZDVmNDQ2NTZjNjE3OTNlZGEyMDIwM2M1MjY1NzM3OTZlNjM1ZjUwNjU3MjY5NmY2NDY5NjMyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjA3NTYxM2Q1YzIyNmU2MTVjMjIzZTMwM2MyZjUyNjU3Mzc5NmU2MzVmNTA2NTcyNjk2ZjY

I followed the directions in http://devcentral.f5.com/wiki/default.aspx/iRules/LTMMaintenancePage.html and it's all on a single line. I'm sure I'm doing something pretty stupid, but if anyone could point me in the right direction it would be greatly appreciated.

Thanks!
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
mgamble -

See here:

http://devcentral.f5.com/Default.aspx?tabid=63&articleType=ArticleView&articleId=343

...for a more complete writeup on the iRule, including a link that will do the hex encoding of the file.

0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER

Hi there, nice solution.

I'm trying to change this irule to make use of ifiles instead of hex/string converted files/arrays. Anyone manage to do this already, i'm kind of stuck on the part where i divide the binary (ifile) into 512 byte chunks....

0