はじめに
インターネットには、ときどき
「なぜ存在しているのかわからないが、確かに仕様がある」
そんなプロトコルが残っている。
そのひとつが
Quote of the Day(QOTD)
TCP/UDP 17番ポートで動くサービスだ。
そして今回は、
RFC 865
を真面目に読み(A4一枚程度の文量)、
返す内容を「アイカツ格言」にして実装してみた。
RFC 865 をちゃんと読む
RFC 865 Quote of the Day Protocol は、1983 年 5 月に Jon Postel によって書かれた、 ARPANET 時代の非常に小さなサービス定義だ。
冒頭で明言されている目的はこれだけである。
A useful debugging and measurement tool is a quote of the day service.
A quote of the day service simply sends a short message without regard to the input.
日本語訳 by チャッピー
有用なデバッグおよび測定ツールの一つが、Quote of the Day(QOTD)サービスである。
QOTDサービスは、入力内容を一切考慮せず、単に短いメッセージを送信するだけのサービスである。
つまり、
何を送っても関係なく、向こうから一方的に短い文章が返ってくるだけの、超シンプルなサービス
という認識で良いと思う。
TCP ベースの QOTD
RFC は TCP 版の挙動を、非常に具体的に定義している。
- サーバは TCP ポート 17 で待ち受ける
- 接続が確立したら 短いメッセージを送信
- クライアントから受信したデータは すべて破棄
- メッセージ送信後、即座に接続を閉じる
上記より、TCPでありながら
- ネゴシエーションなし
- コマンドなし
という、エクストリームな設計になっている。
UDP ベースの QOTD
UDP版はさらに簡潔だ。
- サーバはUDPポート17で待ち受ける
- データグラムを1つ受信したら、内容を無視して格言を含むデータグラムを1つ返信
送られてきたデータが何であろうと、 意味を解釈する必要は一切ない。
Quote構文について
RFC 865は、Quoteの内容についても最小限しか縛らない。
- 特定の構文は 存在しない
- ASCII の印字可能文字、空白、CR/LF を推奨
- 1行でも複数行でもよい
- 512文字未満であること
ここで重要なのは、
There is no specific syntax for the quote.
とある。
つまり、 「今日の格言」が何であるかは、完全に実装者の自由である。
じゃあアイカツ格言にしよう!
格言ってなんだ?
という連想ゲームをしていたら、2ノード目くらいにアイカツ格言 が出てきた。
アイカツ格言を知らない方に説明すると、2012年くらいに放送していたTVアニメアイカツの最後にSDキャラが格言を言うコーナーがあった。
アイカツ格言は
- 短い
- 単体で成立する
- 文脈不要
- 何度受け取ってもダメージがない(むしろ回復する)
- デバッグ中に見ても心が荒れない という完全にQOTD向きコンテンツでもある。
RFC的には単なる文字列かもしれないが、人間(アイカツおじさん)にとっては エモーショナルである。
実装
今回の実装はシンプルだ。
- TCP / UDP 両対応
- 受信データは完全無視
- アイカツ格言をランダムに1つ選ぶ
- 512文字以内で返す
- それっぽく「今日の格言」感を出す
これだけである。
実装コード
Pythonで実装した。
ポート17番はrootで実行する必要があるので、各自で色々やってみてほしい。
import socket
import threading
import random
import time
HOST = "0.0.0.0"
PORT = 17
# アイカツ格言(50話まで)
AIKATSU_QUOTES = [
"芸能人はカードが命",
"全ては今日からのレッスンにかかっている",
"アイドルは特訓も命",
"カメラの向こう側へ微笑め",
"ランウェイはみんなの滑走路",
"サインは顔をあげて",
"おいしいものは最初に食べろ",
"ライバルは成長のスパイス",
"Move・on・now!",
"みんな見えないところでがんばってる",
"ラブはパワー",
"Merry Christmas!",
"甘いものは別腹!!",
"アドリブは空気を読んで",
"アイドルは時間も命",
"アピールはステージの華",
"失敗を恐れるな!",
"思いはチョコにのせて♥",
"キャラクターは一日にしてならず",
"壁に耳あり障子に目あり",
"チームワークは大切に",
"変装は穏やかに",
"ファッションは笑顔から",
"旅は道連れ",
"事実は小説より奇なり",
"花には団子",
"アイカツは楽しんで",
"眠れぬ夜には散歩",
"あいさつは大切に",
"ライブは一体感!",
"母の味は忘れがたし",
"かけもちはほどほどに",
"傘はしっかり持とう",
"オーディションだってステージ!",
"面接は普段どおりに",
"見られてこそ星は輝く",
"3人寄ればソレイユ!",
"対バンライブで盛りあがろう!",
"獅子の子落とし",
"ドレスは人がまとってこそ完成する",
"台風は大変",
"快眠は寝床から",
"おいしい笑顔はかわいい",
"音楽はハッピー",
"たまには休もう",
"ジョニーに歴史あり!",
"目は口ほどにものを言う",
"アイドルは崖が好き",
"緊張したら笑おう",
"アイドルはおもしろい!",
]
def make_quote() -> bytes:
quote = random.choice(AIKATSU_QUOTES)
msg = f"今日のアイカツ格言\n{quote}\n"
return msg[:512].encode("utf-8", errors="replace")
def tcp_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(128)
print(f"[TCP] listening on {HOST}:{PORT}")
while True:
conn, addr = s.accept()
threading.Thread(
target=handle_tcp_client,
args=(conn, addr),
daemon=True
).start()
def handle_tcp_client(conn: socket.socket, addr):
with conn:
try:
conn.settimeout(0.2)
try:
conn.recv(4096)
except socket.timeout:
pass
conn.sendall(make_quote())
except Exception as e:
print(f"[TCP] {addr} error: {e}")
def udp_server():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.bind((HOST, PORT))
print(f"[UDP] listening on {HOST}:{PORT}")
while True:
_, addr = s.recvfrom(65535)
try:
s.sendto(make_quote(), addr)
except Exception as e:
print(f"[UDP] {addr} error: {e}")
def main():
threading.Thread(target=tcp_server, daemon=True).start()
threading.Thread(target=udp_server, daemon=True).start()
print("Aikatsu QOTD server running.")
while True:
time.sleep(3600)
if __name__ == "__main__":
main()
なお、見るからにDoS攻撃の標的になりそうな処理なので、流石に公開はしていない。

