BashでUDP送信に失敗していた

3 min読了の目安(約3400字TECH技術記事

できごと

Datadog へのカスタムメトリクスの送信のため、データグラム形式とシェルの使用方法にある

echo -n "custom_metric:60|g|#shell" >/dev/udp/localhost/8125

のように、bashのリダイレクトでUDP送信をするユーティリティを利用していた。

https://docs.datadoghq.com/ja/developers/dogstatsd/datagram_shell/

その利用箇所を広げていったところ、特定のホストだけ Datadog にデータが入ってこないという問題がおきた。

特にエラーも出ていなかったため、問題解決までに時間がかかってしまったが、送信先の dogstatsd がIPv4アドレス 127.0.0.1 の port 8125 で待ち受けしていたのに対し、/etc/hosts に

127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback

と書かれており、何も待ち受けていないIPv6アドレス ::1 の port 80 にUDP送信してしまっていたのが原因だった。

解決方法は、 /etc/hosts の ::1 の行から localhost を外すか、あるいは

echo -n "custom_metric:60|g|#shell" >/dev/udp/127.0.0.1/8125

のようにIPアドレスで指定することだった。

学んだこと

UDP送信は待ち受けしてない port に向けても黙って成功してしまう。

今回の場合だと、datadog-agent の設定の違いがないかとか別の所に原因があると思ってしまったために解決に時間がかかってしまった。

bash の /dev/udp/host/port

3.6 Redirections - Bash Reference Manual より

/dev/udp/host/port
    If host is a valid hostname or Internet address, and port is
    an integer port number or service name, Bash attempts to open
    the corresponding UDP socket.

対応しているバージョン

  • bash-2.04以降で /dev/{tcp,udp}/host/port に対応している。
  • bash-2.05a以降ではgetaddrinfo(3)を使っていて、port をサービス名で指定できる。

エラー処理

今回の場合だとエラーにならなかったが、エラーになる場合もある。

非対応のバージョンの場合のエラー

bash-2.03だと

$ echo -n "custom_metric:60|g|#shell" >/dev/udp/localhost/8125
bash: /dev/udp/localhost/8125: No such file or directory

というエラーになる。

なお、このバージョンをビルドしようとしたら

malloc.c:522:1: error: conflicting types for 'malloc'
malloc.c:799:1: error: conflicting types for 'calloc'

のエラーが出るため、以下の変更を加えてビルドして試した。

diff --git a/lib/malloc/malloc.c b/lib/malloc/malloc.c
index e96dd27d..0a20ea36 100644
--- a/lib/malloc/malloc.c
+++ b/lib/malloc/malloc.c
@@ -518,7 +518,7 @@ malloc_debug_dummy ()
   ;
 }

-char *
+void *
 malloc (n)             /* get a block */
      size_t n;
 {
@@ -795,7 +795,7 @@ valloc (size)
 #endif /* !HPUX */

 #ifndef NO_CALLOC
-char *
+void *
 calloc (n, s)
      size_t n, s;
 {

名前解決に失敗した場合のエラー

ホストの名前解決、port として数値以外が指定されて、サービス名の解決ができなかった場合にはエラーになる。

$ echo -n "custom_metric:60|g|#shell" >/dev/udp/unknown/8125
bash: unknown: Name or service not known
bash: /dev/udp/unknown/8125: Invalid argument
$ echo -n "custom_metric:60|g|#shell" >/dev/udp/localhost/unknown
bash: unknown: Servname not supported for ai_socktype
bash: /dev/udp/localhost/unknown: Invalid argument

待ち受けしてないportにアクセスした場合のエラー

待ち受けしていない UDP port へのアクセスはエラーにならない。

$ echo -n "custom_metric:60|g|#shell" >/dev/udp/::1/8125
$ echo $?
0

これは SOCK_DGRAM なsocketに対してconnect(2)が黙って成功することによる。

一方でTCPの場合には、待ち受けしていない port へのアクセスは

$ echo >/dev/tcp/localhost/8125
-bash: connect: Connection refused
-bash: /dev/tcp/localhost/8125: Connection refused
$ echo $?
1

のようにエラーになる(これが頭にあったため、問題切り分けで送信処理を疑うという発想に行かず時間かかってしまった)