インフラ」カテゴリーアーカイブ

BrokenPipeError: [Errno 32] Broken pipe調査

ESP8266(ESP-WROOM-02)の内容。ESP-WROOM-32ではないので注意。

事象

esp8266 -> nginx(proxy) -> python(HTTPServer)という構成でサーバへespからサーバーへリクエストを投げたところ、かなりの確率で以下のログがpythonで吐かれていた。

Exception occurred during processing of request from ('127.0.0.1', 48024)
Traceback (most recent call last):
  File "/usr/lib/python3.10/socketserver.py", line 316, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python3.10/socketserver.py", line 347, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python3.10/socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.10/socketserver.py", line 747, in __init__
    self.handle()
  File "/usr/lib/python3.10/http/server.py", line 433, in handle
    self.handle_one_request()
  File "/usr/lib/python3.10/http/server.py", line 421, in handle_one_request
    method()
  File "/home/yoshitake/api/mail_api.py", line 26, in do_GET
    self.wfile.write(response_content)
  File "/usr/lib/python3.10/socketserver.py", line 826, in write
    self._sock.sendall(b)
BrokenPipeError: [Errno 32] Broken pipe

直接のメッセージは、pythonがリクエスト元に書きこもうとしたら相手がすでに受信待ち状態を終了しているような場合に起きるやつ。esp側では接続したもののヘッダーも取得せずにすぐに終了している。

ちなみにnginxのaccess.logには以下のようにステータス499と(pythonのHTTPServer他で使われている非標準の)クライアントが先に閉じたよというのが残っている。

150.9.94.220 - - [26/Feb/2024:11:31:56 +0900] "GET /api/reserve_count HTTP/1.0" 499 0 "-" "ESP8266"

直接の原因箇所

リクエストを送るesp側でデータの受信待ちをしなくてすぐに閉じているのが直接の原因

delay(1000);とかを入れたらとりあえず動く。

とはいえ、リクエストを送ったのちにデータ受信後の終了信号的なものをみたらまたはタイムアウトでクローズの方がよさそう。

また、同じ接続方式でself-signed.badssl.comにリクエストを送るとレスポンスがきちんと取得できているのでサーバサイドにも何か改善点がありそう。

根本解決

クライアントサイド(ESP8266)

取り急ぎavailable()では接続できたかしか確認していない。 WiFiServerSecureBearSSL.cpp(サーバの方)を間違えてみていた。

改めてWiFiClientSecureBearSSL.cpp確認。

字面で見る限りなんか入ってたら0以外が入って、なにも入っていないとサーバ側が準備中でも一番下に入って0がくるっぽい。だいぶ中の方に書いてあるので内部データを見ると色々多変そうなので最初のデータ来るかタイムアウトまでの待ち処理を入れるのがよさそう。断続的にデータがくるならちゃんと終端処理を見た方がよいかも。

本体から、書き出しメソッド呼び出し。

サーバサイド(nginx+pythonのHTTPServer)

こちらは単純にレスポンスが早く返りだすかどうかで違いが出ていたっぽい。レスポンスが早いという理由だった場合はクライアント側の処理で解決すべき話なので深く追わない。

nginxでpythonを動かす

最終的にやりたいこと

店の予約メールが入ったときにすぐわかるように家でパトランプ的なものを光らせたい。

  1. メールサーバに予約っぽいメールが来たら特定フォルダに振り分けておいて
  2. 未読メールがあるかどうか返すAPIをメールサーバに建てておいて
  3. 家のパトランプてきな端末から定期的にサーバをチェックしにいく

家の端末にサーバ側から投げるのは家のファイヤウォールあけるか端末側からつなげっぱにして切れたら再接続とか面倒なことになりそうなので端末側から定期的に見に行く。セキュリティ的にも件数返すだけAPIならあんま頑張んないでもよさげなので。

今回やる範囲

2のAPIを立てるとこ。nginxは未インストールの状態から。pythonはhttp.serverあたりを使ってnginxからproxyで飛ばす予定。あとは実作業しながら書いていく。

開始前の状況

postfix+dovecotでMaildir形式でメールサーバを構築済み。なお、”Ubuntu 22.04.3 LTS”

また、LetsEncriptでtls通信をさせているため証明書も入手済み。ただ、80番ポートが空いている前提のstandaloneモードで証明書の更新をしているためwebroot方式?だかに直さないといけないはず。

メールサーバに予約っぽいメールが来たときはsieveで規定フォルダに振り分けている。設定ファイルはしたので、題名に”予約確定通知”か”予約が確定”とあったら”INBOX.ジム.予約”フォルダに振り分けるというもの。(サンダーバードだと”「受信トレイ」の下の「ジム」の下の「予約」”)

作業開始

nginxインストール&設定

して、とりあえず443ポートをあける。(letsencript使うのに80番も開ける必要があるはずなので空いてなければそちらも)

既に作成済みの証明書を見る形でhttpsを有効にする。ついでにhttpからhttpsへのリダイレクトもかけておく。あと、nginxのバージョンも一応隠す。

で新規ファイルを作り下を追加

でnginx再起動

これでとりあえずブラウザからサーバにサクセスするとhttpsに転送されてnginxにもとから入っているwelcome画面が表示されるはず。

pythonを使えるようにする

nginx側のproxy設定

ssl.conf(と深く考えずに名前を付けたので)に直接かくものじゃないのでインクルードして/etc/nginx/conf.d/mail_api.txt に設定を書く。mail_api.confと拡張子をconfにすると/etc/nginx/nginx.confからのinclude設定と重なり多分うまく動かない。

でserver(443の方)の末尾にインクルード設定を追加

そしてmail_api.txtを作成

にて

これでsudo systemctl restart nginx したら8000番ポートで起動している奴があればそちらにリクエストをすべて飛ばすはず(location / で ルートディレクトリ以下全部)。8000番起動していなくてもとりあえずnginxを先に起動できると思う。

python側のリクエスト受信

http.serverにてnginxのproxyで指定した8000番ポートで待ち受け機能を作る。

(本来ローカルで完結するのでファイヤウォールの8000番は開かなくてよいけどVSCodeでやるとちょいちょいつなげに行こうという処理が走って面倒だった)

取り急ぎmailサーバの任意の場所に以下のmail_api.pyを作ってpython3 mail_api.pyで起動

ローカルのPCからhttps://mail.hoge.co.jp/api/reserve_countにアクセスするとstatus200 で Hello, this is the root path.という文字がかえる。

他だとstatus404 で 404 not foundという文字がかえる。

/api/reserve_count が呼ばれたときに実際に件数を返す処理を作る

response_content = b’Hello, this is the root path.’のところを件数返す関数に変更

してvi reserve_count.py し関数作る。例外処理ちゃんと入れていないので各自実装必要。

あんまり落ちなそうな気はするので実際には例外処理なしで突き進む。あと、ログイン代わりにquery_paramsみてパラメータがあっていたら件数確認処理呼び出すような緩いセキュリティ分岐を入れるつもり。

pythonのimport imaplibで日本語フォルダ名を使う場合はエンコードが必要

環境

  • PRETTY_NAME=”Ubuntu 22.04.3 LTS”
  • Python 3.10.12
  • pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

手順

imap_toolsというのが楽らしいのでインストール

pip install imap_tools

インポートして実行

import imap_tools
mailbox_name = "INBOX.ジム.予約"
status, messages = mail.select(imap_tools.imap_utf7.encode(mailbox_name))


※Ubuntsuを24にあげたときにインストールしたimap_tools-1.7.2ではimap_tools.imap_utf7.encodeからimap_tools.imap_utf7.utf7_encodeと微妙な名称変更が入っている

何もしない場合のエラー

mail.select(“INBOX.ジム.予約”)

として

yoshitake@mail:~/dovecot_delivery$ python3 test.py 
Traceback (most recent call last):
  File "/home/yoshitake/dovecot_delivery/test.py", line 15, in <module>
    mail.select("INBOX.ジム.予約")
  File "/usr/lib/python3.10/imaplib.py", line 756, in select
    typ, dat = self._simple_command(name, mailbox)
  File "/usr/lib/python3.10/imaplib.py", line 1230, in _simple_command
    return self._command_complete(name, self._command(name, *args))
  File "/usr/lib/python3.10/imaplib.py", line 987, in _command
    arg = bytes(arg, self._encoding)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-7: ordinal not in range(128)

ソース全体

import imaplib
import imap_tools
from email.header import decode_header

# IMAPサーバの設定
mail_server = 'mail.huga.co.jp'
username = 'hoge'
password = 'hugahuga'

# IMAPサーバに接続
mail = imaplib.IMAP4_SSL(mail_server)
mail.login(username, password)

# # メールボックスを選択
mailbox_name = "INBOX.ジム.予約"
status, messages = mail.select(imap_tools.imap_utf7.encode(mailbox_name))

# # メール検索(例: 未読メール)
status, messages = mail.search(None, "(UNSEEN)")
unread_count = len(messages[0].split())
print(f"未読メールの件数: {unread_count}")

# 接続を閉じる
mail.close()
mail.logout()

docker-letsencrypt-nginx-proxy-companionが古かった

ACME server returned an error: urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Error creating new authz :: Validations for new domains are disabled in the V1 API (https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430)

新しく追加したサブドメイン&コンテナで上記エラー。

V1 API使えなくなるよ新しいの使えと公式がいっていたのは知ってたけど、docker-compose build –pullしていたので油断していた。docker-letsencrypt-nginx-proxy-companionはbuildしていないからdocker-compose pullしないといけなかった。

新しくなっていると思って別の場所から探っていったのでだいぶ時間をとかしてしまった。docker-compose pullは普段してないから、手を全く入れていないコンテナで古くなっているの残ってそう。

Let’s Encryptの認証局が古いAndroidで使えなくなるので延命

いまさらながら、もう少しで古いAndroid(7.1以下?未満? 大体3割強ぐらい?)だとLet’sEncriptのsslが効かなくなることを知った。「出来立てのころは自前のCAだと知名度なくて実質認証できないからよそのCAに協力してもらってたけど基本自立するよ」ということらしい。(意訳)

optionで1年ぐらい延命できるとのことなので対応中。作業メモがてら記録に残しておく。

certbotの場合はoption(–preferred-chain)で元々のCA(DST Root CA X3)をしていすれば良いらしい。なお、–force-renewalオプションを付けていないとまだ期限内だよと更新してくれないから付けておく。まとめると下の感じ。

certbot-auto certonly --preferred-chain "DST Root CA X3"  --force-renewal +いつものオプション

いちおう証明書のCAと内容も確認しておく

openssl s_client -connect ドメイン:ポート | openssl x509 -noout -enddate
openssl s_client -connect ドメイン:ポート -showcerts

見た感じ想定どおり更新されている。程良い試験端末ないので動作確認はしない!

メインのサーバ群はletsencrypt-nginx-proxy-companion使っているけどそっちでの設定方法(あるのか?)はこれから調べる。

(追記)

2020/10/1時点では対応されていなけど、issueがあがっていて使っているsimp_leが対応してからの模様。「先にsimp_le(のフォーク)にイシューたてな!」といっているのですぐではなさそうな雰囲気。

GitlabPagesでアカウント名にドットが入っているとうまく動かない

事象

アカウント名がfoo.varとドットが入っているとGitlabPagesは https://foo.var.gitlab.io/val  みたいなURLになる。

しかし、GitlabPagesからもらえる証明書は*.gitlab.ioのワイルドカード証明書なため、foo.varと二段階になっているサブドメインではエラーとなる。

対処

URLにこだわりはないのでドットの入っていないグループを作成しそちらにレポジトリ移管した。https://epea.gitlab.io/sanko5/ (epeaが新しくつくったグループ)

自力でLetsEncript動かしてもよさそうなきもするけどそれはそれで面倒そうなので試していない。

取得してしばらくしたドメインがDNS_PROBE_FINISHED_NXDOMAIN

TwitterにWordpressのサイトを立ち上げて少し立ったサイトがDNS_PROBE_FINISHED_NXDOMAINでつながらないという事象で困っているクライマーがいました。

ブログのネタついでに切り分けしたので、切り分け方法と解消方法をメモしときます。

DNS_PROBE_FINISHED_NXDOMAINとは

インターネットのアドレスのドメイン部分(https://www.rocher.kyoto.jp/arbr/〜のwww.rocher.kyoto.jp部分)はどのサーバ(インターネットの向うの機械)かを表していて、管理は数字(ipアドレス)メインで行われています。

で、ざくっと雰囲気で説明すると、ドメインの文字が実際にはどの数字かを教えてくれるシステムがあってそれをDNSといいます。そのDNSにドメインのIPを教えてと自分の端末(PC/スマホetc)からサーバに接続しようと自動で質問したけどその答えが得られないときに発生するのがDNS_PROBE_FINISHED_NXDOMAINというエラーです。

結構あたる解決策

ドメインをお名前comなりムームードメインなりで取得したと思いますが、期限内にメールアドレスの認証をきちんとしていないとそのドメインのDNS機能を停止させられます。

ドメインを取得してしばらく経った後に、使えていたドメインが止まってしまったら認証漏れの可能性が高いです。

「【重要】~ ドメイン 情報認証のお願い」みたいなメールが来ていると思うのでそいつに従って手続きをしたら、通常数10分ほどで復活します。

切り分け

きちんと調べてから治したい人向けの切り分けです。(そもそもお名前comなりの管理画面にログインしたら分かり易い警告でているかもしれません。)

自分の端末の問題かつなげているDNSがおかしいかの切り分けが必要です。

確実ではないですが、自分の端末がおかしいか確かめるために違うwifiの友達なりにおかしいURLを送って、同じブラウザで同じエラーが出るか確かめてもらうのが手軽です。

同じエラーが出なかったら自分の端末側の問題が大きいですが、ここでは取り扱いません。(ほかを再度ぐぐってください)

で、他の人もおかしかったらDNS側の問題の可能性が高いです。
whoisというコマンドで調べられます(オンラインだとこことかですが、管理アドレスによってどこで調べ直せと管理先URLを教えられてもう一度調べ直しになります。

で、そこで「Domain Status」という処がclientHoldというものになっていたらメールアドレスの認証エラーです。

clientHoldの理由には認証のメールに返信していなかった以外にも、登録した個人情報が怪しまれた場合もありますがそちらはレアケースなのでそうなってしまったら、サポートに連絡するしか無いと思います。

追記

ピンバックはできるだけ対応するので、参考になったらリンクはっていただけるとありがたいです

/etc/sudoers.dの#

vagrantのsudo設定
/etc/sudoers.d/vagrant
にしかなさそうだけど/etc/sudoersコメントアウトアウトされてるよねと必死に設定探していたら

## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment)
#includedir /etc/sudoers.d

!! コメントじゃない。

これはみな一度は騙されるに違いない。。。。

 

Dockerサポート外

会社用メインサーバは秘伝のたれが詰まったCentOS6でミドルも都度都度足していったもので構成よくわからなくなっている。取りあえず、よく使うやつらだけでもDockerに切り出しておこうかと思ったらCentOS6系はもうサポート対象外なのね。昔からあるサーバは特にいじっていなかったから知らなかった。

このまま使い続けるのもより、いっそのこと今OSもCentOS7にあげてしまった方がいいかな。Docker用OSも考えたけど、何かあったときにミドルをコンテナでなくホストに慌ててインストールとか時間かかりそうなので、メインサーバは無難にCentOSにしとく。

 

取り急ぎのメンテナンス画面(実作業)

昨日の
・一般の人が見ると「まだOPENしてませんよ」に飛ばす
・業者はものを見れるようにしておく。
・触ると色々巻き込まれそうなのでできるだけ今あるものには手を加えたくない
という感じの告知画面追加の実作業。

まずは、dockerのopenresty使って、クッキーチェック付きのリバースプロキシを表に出す。
SSLもプロキシを通そうとしたけど、ECCubeが乗っているapacheのhtaccessに告知画面への302リダイレクト追加

コンテナからグローバルIPの8081ポートを指定したのでファイヤウォールを開けてやる。
稼働予定の本番環境では内部のネットワークのみにしてやらんといけんけどそのままもりっと。
で、apacheのポートやらを変えてやって取りあえずいったん終了。