Skip to main content

Transferring Files Over TCP With Bash

There are times when I have Ethernet working on an embedded GNU/Linux board, but no tool available to actually transfer files. Latest example is the Yocto build for an i.MX evaluation board from NXP that I just built. The system lives on the SD card, so adding a package would mean to reconnect the SD card to my development machine, copy the file(s) and put it back into the board. And I am just too lazy for that. The EVK has an IP address on my local network, so there must be an easy way to transfer a file with standard tools, right?

BASH To The Rescue

Having BASH available on the target, we can use redirection from a TCP port without any additional helper. All that is needed on the other side is a quick way to offer a single file when connecting to a specific port. Turns out Netcat is such a tool and I usually have it installed on my systems. So this one-liner starts nc listening to a TCP port and will send the whole file to a connecting client and then quit (host has IPv4 address 192.168.1.2):

dzu@krikkit:...imx-yocto-6.6.3-1.1.0/build-imx8mnlpddr4evk$ cat tmp/deploy/deb/armv8a/dropbear_2022.83-r0_arm64.deb \
  | nc -q 1 -l 192.168.1.2 23234

With this, we can receive the file on the target and install it easily:

root@imx8mn-lpddr4-evk:~# cat < /dev/tcp/192.168.1.2/23234 > /tmp/new.deb
root@imx8mn-lpddr4-evk:~# dpkg -i /tmp/new.deb                                                          

We can then start the ssh service with systemctl start sshd and from here on we are again on familiar grounds.

Serial Only

Writing the first paragraph reminded me that even without Ethernet there are multiple ways to transfer a file to an attached GNU/Linux board. One of the most important is when you have a serial connection only

Plain Text And (Almost) No Tools

It is a little bit more difficult to transfer files over serial only as the communication is not safe against transmission errors. If we disregard this, we could try transferring plain text files only with cat. Let's start cat on the remote end and redirect all input to a file:

root@imx8mn-lpddr4-evk:~# cat > /tmp/rx-data                                                                   

Then we close the serial connection, by entering "C-\ q" in kermit. Back at our local machine, we create a simple text file and output that to our serial port. We then reconnect with kermit, press "C-d" to signal EOF to cat and inspect the output:

Closing /dev/ttyUSB2...OK                                                                                     
dzu@krikkit:~$ cat > /tmp/tx-data                                                      
line1                                                                                                         
line2                                                                                                         
line3                                                                                                         
dzu@krikkit:~$ cat /tmp/tx-data > /dev/ttyUSB2                                                                
dzu@krikkit:~$ kermit -l /dev/ttyUSB2 -b 115200 -c                                                            
Connecting to /dev/ttyUSB2, speed 115200                                                                      
 Escape character: Ctrl-\ (ASCII 28, FS): enabled                                                             
Type the escape character followed by C to get back,                                                          
or followed by ? to see other options.                                                                        
----------------------------------------------------                                                          
root@imx8mn-lpddr4-evk:~# cat /tmp/rxdata                                                                     
line1                                                                                                         

line2                                                                                                         

line3                                                  

root@imx8mn-lpddr4-evk:~#

Almost! There is something going on as the line feeds get duplicated. Let's check the stty settings of the remote machine and our serial port:

root@imx8mn-lpddr4-evk:~# stty -a
speed 115200 baud; rows 34; columns 110; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
root@imx8mn-lpddr4-evk:~# 
Closing /dev/ttyUSB2...OK
dzu@krikkit:~$ stty -F /dev/ttyUSB2 -a
speed 115200 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
dzu@krikkit:~$ 

There is a lot listed here, but the culprit is the onlcr setting on the host and the corresponding icrnl on the remote end. Sending a line feed will output lf and cr to go to the next line and return to the first character. On the remote machine the cr will again be translated into an lf and thus two line feeds will be transmitted!

Fixing this is very easy. We just need to fix the setting on our local machine:

dzu@krikkit:~$ stty -F /dev/ttyUSB2 -onlcr
dzu@krikkit:~$ 

If we repeat the "start cat on remote end, switch to local machine and cat test data" dance, things now look fine:

dzu@krikkit:~$ kermit -l /dev/ttyUSB2 -b 115200 -c
Connecting to /dev/ttyUSB2, speed 115200
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
root@imx8mn-lpddr4-evk:~# cat > /tmp/rx-data

Closing /dev/ttyUSB2...OK
dzu@krikkit:~$ cat /tmp/tx-data > /dev/ttyUSB2
dzu@krikkit:~$ kermit -l /dev/ttyUSB2 -b 115200 -c
Connecting to /dev/ttyUSB2, speed 115200
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
root@imx8mn-lpddr4-evk:~# cat /tmp/rx-data 
line1
line2
line3
root@imx8mn-lpddr4-evk:~# 

Binary Data

Trying this approach with binary data will fail, as several control characters and especially EOF (0xd hexadecimal) will be interpreted by the remote serial port instead of written to the data file. One way to fix this would be to convert the binary data into pure text files (i.e. UUENCODE, BASE64 or similar), but this usually also requires extra tools on the remote end, so I will ignore this for now.

Instead we head for another option which will also add error correction in the transmission. Such protocols were established when the serial connection was all there was, i.e. in the days of modem communication. One of the oldest is the XMODEM protocol. It is implemented in many chat programs and command line utilities like rx from the lrzsz package. If you have rx available on the host and the target, kermit can be used nicely to transfer a file. It will call sx and rx for you on both ends. This time we only press "C-\ c" to get to kermits command prompt and start the transfer from there:

root@imx8mn-lpddr4-evk:~# 
(Back at krikkit)
----------------------------------------------------
(~/) C-Kermit>send /protocol:xmodem /etc/motd motd
Sende /etc/motd, 2 Blöcke:Starten Sie nun Ihr XMODEM-Empfangsprogramm.
Bytes gesendet:    384   BPS:359                            

Übertragung abgeschlossen
(~/) C-Kermit>c
Connecting to /dev/ttyUSB1, speed 115200
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
4-evk:~# ls
motd
root@imx8mn-lpddr4-evk:~# 

A quick test showed that minicom also supports x/y/z-modem transfers, but gtkterm does not. Oh well.

Summary

Transferring files has a very long history going back to dial up lines and sometimes the tools from that era are still very helpful.

Comments

Comments powered by Disqus