Anmol Sarma
Posted on June 30, 2019
A few months ago, while reading the man page for recvmmsg()
, I came across this snippet:
$ while true; do echo $RANDOM > /dev/udp/127.0.0.1/1234;
sleep 0.25; done
And as advertised, it sends a UDP datagram containing a random number to port 1234 every 250 ms. I didn’t recall ever seeing a /dev/udp
and so was a bit surprised that it worked. And as it happens, ls
was not able to access the file that I had just written to:
ls: cannot access '/dev/udp/127.0.0.1/1234': No such file or directory
Puzzled and intrigued, I echoe
d Foo Bar Baz to /dev/udp/127.0.0.1/1337
and reached for strace
:
...
2423 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 4
12423 connect(4, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
12423 fcntl(1, F_GETFD) = 0
12423 fcntl(1, F_DUPFD, 10) = 10
12423 fcntl(1, F_GETFD) = 0
12423 fcntl(10, F_SETFD, FD_CLOEXEC) = 0
12423 dup2(4, 1) = 1
12423 close(4) = 0
12423 fstat(1, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
12423 write(1, "Foo Bar Baz\n", 12) = 12
...
Seemingly, a normal UDP socket was being created and written to using the regular sycall interface. That refuted my initial suspicion that some kind of a special file backed by the kernel was involved. But who was actually creating the socket?
A peek at Bash’s code answered that question:
redir.c:
/* A list of pattern/value pairs for filenames that the redirection
code handles specially. */
static STRING_INT_ALIST _redir_special_filenames[] = {
#if !defined (HAVE_DEV_FD)
{ "/dev/fd/[0-9]*", RF_DEVFD },
#endif
#if !defined (HAVE_DEV_STDIN)
{ "/dev/stderr", RF_DEVSTDERR },
{ "/dev/stdin", RF_DEVSTDIN },
{ "/dev/stdout", RF_DEVSTDOUT },
#endif
#if defined (NETWORK_REDIRECTIONS)
{ "/dev/tcp/*/*", RF_DEVTCP },
{ "/dev/udp/*/*", RF_DEVUDP },
#endif
{ (char *)NULL, -1 }
};
So, redirection involving /dev/udp/
is handled specially by Bash1 and it uses BSD Sockets API to create a socket:
lib/sh/netopen.c:
/*
* Open a TCP or UDP connection to HOST on port SERV. Uses the
* traditional BSD mechanisms. Returns the connected socket or -1 on error.
*/
static int
_netopen4(host, serv, typ)
char *host, *serv;
int typ;
{
struct in_addr ina;
struct sockaddr_in sin;
unsigned short p;
int s, e;
if (_getaddr(host, &ina) == 0)
{
internal_error (_("%s: host unknown"), host);
errno = EINVAL;
return -1;
}
if (_getserv(serv, typ, &p) == 0)
{
internal_error(_("%s: invalid service"), serv);
errno = EINVAL;
return -1;
}
memset ((char *)&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = p;
sin.sin_addr = ina;
s = socket(AF_INET, (typ == 't') ? SOCK_STREAM : SOCK_DGRAM, 0);
if (s < 0)
{
sys_error ("socket");
return (-1);
}
if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0)
{
e = errno;
sys_error("connect");
close(s);
errno = e;
return (-1);
}
return(s);
}
Which means we can actually make HTTP requests using Bash:
exec 3<> /dev/tcp/checkip.amazonaws.com/80
printf "GET / HTTP/1.1\r\nHost: checkip.amazonaws.com\r\nConnection: close\r\n\r\n" >&3
tail -n1 <&3
No curl
needed! /jk
Apart from Bash, in the versions and configurations packaged in Ubuntu 18.04, only ksh
supports network redirections – ash
, csh
, dash
, fish
, and zsh
do not.
I don’t think I will actually have any use for network redirections but this was a fun little rabbit hole to dive into.
NOTE: Code snippets from Bash are licensed under GPLv3, the snippet from the man page is licensed differently
- At least on Linux, the other special patterns handled by bash like
/dev/fd
and/dev/stdint
actually are special files backed by the kernel. The Bash manual notes that it may emulate them internally on platforms that do not support them. [return]
Posted on June 30, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 20, 2024