先に打つ 3 つの確認(PowerShell で実行します。起動は Windows+X →「ターミナル」または「Windows PowerShell」。自宅の検証機では 3 つとも管理者権限なしで実行できました)。IP・ポート番号・PID は自分の環境の値に置き換えてください。読み方は 4 章です。Test-NetConnection -Port は実際に TCP 接続を張り、接続枠を消費します。

# 1) 相手の IP まで届いているか(L3)。「Reply from」が相手の IP かどうかを見る
ping 192.168.1.16

# 2) そのポートが開いているか(L4)。ここが本命
Test-NetConnection -ComputerName 192.168.1.16 -Port 59001 -InformationLevel Detailed

# 3)【自分がサーバー側を書いている場合のみ】自分の PC で誰が待ち受けているか(二重起動の確認)
Get-NetTCPConnection -State Listen -LocalPort 59001 | Select-Object LocalAddress,LocalPort,State,OwningProcess

# 上の OwningProcess に出た番号に置き換えて実行する(22428 は検証機での実行例。手順は 4.2)
Get-Process -Id 22428

2 番目のコマンドで PingSucceeded : True と TcpTestSucceeded : False が同時に出たら、それが「ping は通るのに繋がらない」の正体です。相手の IP まではパケットが届いていて、そのポートが応答していない状態です(応答を返したのが本当に相手かどうかの確認は 2.3)。この状態だった人は 5 章(よく詰まる 5 つの原因)へ、Python の例外番号が出ている人は 3 章の逆引き表へ直接進んでください。本文中の IP は説明用で、192.168.1.16 が検証機、192.168.1.1 が家庭用ルーター、192.168.1.99 は同一サブネット内の未使用 IP です。

現場で試す前に — 実行前の確認

  • 制御ネットワークに PC を勝手につながない。IP アドレスの払い出しと接続の可否は、設備管理責任者・情報システム部門に必ず確認してください。IP アドレスの重複は設備停止に直結します(5.2)。
  • Test-NetConnection -Port は ping と違い、相手のポートへ実際に TCP 接続を張ります。同時接続数に上限のある産業機器では、切り分けの 1 回がその枠を消費し、既存の接続を弾くことがあります。立ち上げ中・停止中の設備で、担当者の了解が取れているなら、この記事の手順はそのまま進めて構いません。稼働中(運転中)の設備に対しては連続実行を避け、設備担当の承認を得てから実行してください(上限値は機種のマニュアルで確認し、上限の小さい機器では計画停止・保全日に回すのが安全です)。4.4 の Set-NetIPInterface のような設定変更は、この了解には含まれません。設定を変更する場合は、別途その操作について承認を得てください。
  • 本記事で使うコマンドの大半は状態を読むだけですが、Test-NetConnection -Port は実際に接続を張り、4.4 の Set-NetIPInterface は設定を変更します。現場機器への適用は自己責任です。掲載内容は自宅の検証機での実測に基づくもので、実施によって生じた損害の責任は負いかねます。

この記事の目次

1. ping は通るのに通信できない状態を、実際に作ってみる

言葉で説明する前に、その状態を自宅の検証機で作りました。1 台の PC で socket の echo サーバーをポート 59001 で待ち受けさせ、59099 は何も待ち受けていない状態にします。相手は同じ IP(192.168.1.16)、違うのはポート番号だけです。この 2 つに Test-NetConnection を打つと、結果は次のように割れました。

# 出力例(ホスト名はマスク、共通のヘッダーは省略しています)

PS> Test-NetConnection -ComputerName 192.168.1.16 -Port 59001 -InformationLevel Detailed
ComputerName            : 192.168.1.16
RemoteAddress           : 192.168.1.16
RemotePort              : 59001
NetworkIsolationContext : Private Network
IsAdmin                 : False
InterfaceAlias          : イーサネット
SourceAddress           : 192.168.1.16
NetRoute (NextHop)      : 0.0.0.0
TcpTestSucceeded        : True

PS> Test-NetConnection -ComputerName 192.168.1.16 -Port 59099 -InformationLevel Detailed
PingSucceeded           : True
PingReplyDetails (RTT)  : 0 ms
TcpTestSucceeded        : False

PingSucceeded : True なのに TcpTestSucceeded : False。これが「ping は通るのに繋がらない」そのものです。IP 層では届いているのに、届いた先でそのポート番号を誰も聞いていません。ただし「ICMP が返った=相手の機器が生きている」とまでは言えません(返したのが相手とは限らない理由は 2.3)。

出力の癖も 1 つ。TCP テストが成功したときは PingSucceeded の行が出力されません。59001 側にこの行が無いのは ping が失敗したからではなく、ICMP の結果が失敗時にだけ併記されるためです(当サイトで観察した 2 例からの読み取りで、表示仕様は公式には明記されていません)。

同じ相手に Python から接続したときの実測も並べておきます(待ち受けのないポートは 59002 で測定しました)。

宛先結果所要時間(実測)
192.168.1.16:59001(待ち受けあり)接続成功0.01 秒
192.168.1.16:59002(待ち受けなし)ConnectionRefusedError(WinError 10061)2.04 秒

この再現は検証機が自分の LAN IP(192.168.1.16)宛てに打ったもので、パケットは LAN スイッチを経由していません。ここで確認できるのは「ICMP は返るのに、そのポートだけ TCP が成立しない」という層の違いそのもので、経路上の機器の影響は 4 章で切り分けます。

拒否されるまでに 2.04 秒かかっている点に注意してください。「即座にエラーが返るはずだ」と思って再接続ループの間隔を 1 秒に設計すると、1 回の試行がそれより長くかかります。

切り分けの順番は決まっています。L3(相手の IP まで届くか)→ L4(そのポートが開いているか)→ L7(アプリが正しく応答するか)。上から順に潰すこと自体は定石ですが、現場で難しいのは「今どの層で止まっているか」を判定する材料が足りないことです。その材料になるのが、Python が投げた例外と WinError 番号、そして Windows に最初から入っているコマンドの出力です。

2. ping が保証しない 4 つのこと

Microsoft Learn の公式リファレンスは ping をこう説明しています。Verifies IP-level connectivity to another TCP/IP computer by sending Internet Control Message Protocol (ICMP) echo Request messages.——確認しているのは IP レベルの到達性であり、使っているのは ICMP という、TCP とも UDP とも別のプロトコルです。

ping と TCP 接続が見ている層の違いを示す図 ping は ICMP を使い、イーサネットと IP の層までしか確認しない。Python の connect は同じ経路を通ったうえで TCP のポートとアプリケーションの応答まで到達する必要がある。 ping が見ている層と、アプリの通信が必要とする層 L7 アプリケーション 機器のプログラムが電文を解釈して返す L4 TCP そのポート番号を待ち受けている(LISTEN) L3 IP / ICMP 相手の IP アドレスまでパケットが届く L2 イーサネット / ARP 同じセグメントに相手がいて MAC が引ける ping ここまでしか 見ていない connect() は上まで 通らないと 成功しない ping は ICMP、アプリの通信は TCP。別のプロトコルなので、片方だけを通す設定が成立する 「ping が通った」で確認できたのは、図の下 2 段だけ
図 1: ping が確認する範囲と、TCP 接続が成立するために必要な範囲(スマートフォンでは図を横にスクロールできます)

この図を踏まえると、ping の成功が保証していないことは 4 つに整理できます。

2.1 そのポートが開いていること

ICMP と TCP は別プロトコルなので、機器やファイアウォールが「ICMP には応答するが、特定の TCP ポートは通さない」状態は普通に成立します。1 章の実測がその再現です。ping の成否からポートの開閉は分かりません。

2.2 アプリケーションが応答すること

ポートが LISTEN でも、その先のプログラムが止まっていれば通信は成立しません。接続だけ受け付けて電文を返さない状態では、接続は成功して recv() でタイムアウトします。接続の失敗と受信の失敗は原因が別物なので、ログにはどちらの段階で失敗したかを残してください(ログ設計の記事)。産業機器には同時接続数の上限があり、達すると新しい接続だけが弾かれます。上限値は機種ごとに違うため、必ずマニュアルで確認してください。

2.3 応答を返したのが「相手」であること

検証機自身・同一サブネットの未使用 IP・経路のない IP の 3 パターンに ping を打つと、次の出力になりました。

### 実行結果(ping 192.168.1.16 = 検証機自身)
Reply from 192.168.1.16: bytes=32 time<1ms TTL=128
Reply from 192.168.1.16: bytes=32 time<1ms TTL=128
    Packets: Sent = 2, Received = 2, Lost = 0 (0% loss),

### 実行結果(ping 192.168.1.99 = 同一サブネットの未使用 IP)
Reply from 192.168.1.16: Destination host unreachable.
Reply from 192.168.1.16: Destination host unreachable.
    Packets: Sent = 2, Received = 2, Lost = 0 (0% loss),

### 実行結果(ping 10.255.255.1 = 経路なし)
Request timed out.
Request timed out.
    Packets: Sent = 2, Received = 0, Lost = 2 (100% loss),

真ん中が罠です。相手は存在しないのに「Reply from」と表示され、統計は Lost = 0 (0% loss)。返したのは相手ではなく自分自身(192.168.1.16)で、「その IP には届けられません」という ICMP メッセージが応答として数えられています。ロス率だけを見て正常と判断すると間違えます。見るのは Reply from の IP が宛先と一致しているかと本文が Destination host unreachable でないかの 2 点です。

2.4 アプリと同じ条件で試したこと

ping の既定のタイムアウトは 4 秒、送信は 4 回です。一方、タイムアウトを指定しない Python の connect() は実測で 21.03 秒待ってから諦めました(3 章)。「ping はすぐ返るのにアプリは固まる」は、この待ち時間の差だけで説明が付きます。フリーズに見えても、OS のタイムアウトを待っているだけということが起こります。

3. Python の例外から原因を逆引きする

Python 側のエラーは、原因を絞り込むための最も濃い情報源です。socket の例外はすべて OSError の派生で、Windows では winerror 属性に WinSock のエラー番号が入ります。次の表は、自宅の検証機で症状を 1 つずつ作って採取した結果と、その後に打つべきコマンドの対応です。

例外クラスWinError起きていること(実測時の条件)次の一手(コマンド/確認先)
ConnectionRefusedError10061相手までは届いたが、そのポートを誰も待ち受けていない(実測 2.04 秒)Test-NetConnection -ComputerName <相手IP> -Port <番号>(4.1)。機器側のオープン設定とポート番号を仕様書で照合
TimeoutError(timed out)なし(None)Python 側が指定時間で打ち切った。原因の情報は含まれない(timeout=5 で実測 5.00 秒)timeout を外して winerror を見る(3.1)
TimeoutError10060OS が接続を諦めた。応答自体が返っていない(実測 21.03 秒)Get-NetNeighbor -AddressFamily IPv4 | Select-Object IPAddress,State(4.3)/route print -4(4.4)
OSError10048同じ (IP, ポート) に 2 回 bind() した。自分のプログラムの二重起動が多い(実測・即時に発生)netstat -ano | Select-String <ポート番号> → Get-Process -Id <PID>(4.2)
ConnectionResetError10054相手が RST(「そんな接続は知らない」と即座に突き返すパケット)を返して接続を強制的に破棄した(実測は SO_LINGER=0=通常の切断手続きを省いて即座に RST を返す設定での close)相手側のログ・再起動の有無・同時接続数の上限(機種のマニュアル)を確認
ConnectionAbortedError10053公式定義は「自ホスト側のソフトウェアによる接続の中断」。実測では、相手が閉じたあとの接続に sendall() したときに発生したソケットを作り直す再接続処理になっているか見直す(切断検知の解説)
socket.gaierror—(errno 11001)名前解決に失敗(実測 0.01 秒で即座に失敗)nslookup <ホスト名>。IP 直指定で繋がるかを試す
例外なし・recv() が b''—相手が正常に切断(FIN=もう送るものはないという合図)。エラーではない(実測)切断検知の実装を見直す(切断検知の解説)

番号ごとの正式な定義は Microsoft の WinSock エラーコード一覧が一次情報です。たとえば 10061 は No connection could be made because the target computer actively refused it.——actively refused、つまり相手が能動的に拒否しています。パケットは届いているので、疑うのは経路ではなくポート番号や機器側の設定です。逆に 10060 は応答自体が返らなかった状態で、疑う場所が正反対になります。取り違えると、経路の調査に何時間も溶かします。

3.1 同じ TimeoutError でも、winerror の有無で意味が変わる

表の 2 行目と 3 行目は、どちらも TimeoutError です。違いは winerror が付いているかどうかで、これは誰がタイムアウトを決めたかを表しています。確かめ方は次の断片のとおりです。

# ※断片です(実行には相手の IP とポートの置き換えが必要)
import socket, time

def probe(host, port, timeout):
    t0 = time.perf_counter()
    try:
        with socket.create_connection((host, port), timeout=timeout):
            print("connected")
    except OSError as e:
        print(f"{type(e).__name__} winerror={e.winerror} errno={e.errno} "
              f"{time.perf_counter() - t0:.2f}s")

probe("192.168.1.99", 59001, 5)      # タイムアウトを指定する
probe("192.168.1.99", 59001, None)   # 指定しない(ブロッキング)
# 出力例(自宅検証機での実測値を整形したもの)
TimeoutError winerror=None errno=None 5.00s
TimeoutError winerror=10060 errno=10060 21.03s

timeout を指定すると Python 側が打ち切るため、OS が持っていた原因情報(10060)は失われます。メッセージも timed out だけで、相手が居ないのか経路が無いのかを区別できません。逆に指定しなければ WinError 10060 が付く代わりに 21 秒待たされます。運用コードはタイムアウトを指定しておき、原因を調べるときだけ外して winerror を見る——この使い分けが実務的です。ログには e.winerror を必ず出力してください。番号が残っていれば後から原因を絞り込めます。

この違いは宛先によらず再現しました。到達できない 3 つの宛先——同一サブネットの未使用 IP(192.168.1.99)・経路のない 10.255.255.1・RFC 5737 のテスト用アドレス 192.0.2.1——を、timeout=5 あり/なしの 2 通りで測った計 6 試行の結果は次のとおりです。

  • timeout=5 を指定: 3 宛先とも TimeoutError('timed out')・winerror は None・5.00〜5.01 秒
  • timeout を指定しない(ブロッキング): 3 宛先とも TimeoutError・WinError 10060・21.03〜21.05 秒

「No route to host」を意味する WinError 10065 は、この 6 試行で 1 度も再現しませんでした。10065 は経路上のルーターが ICMP 到達不能を明示的に返す構成でのみ現れると考えられますが、自宅の家庭用ルーター環境では確認できておらず、当サイトでは未実測です。ネット上の解説で 10065 を前提にした手順を見かけても、自分の環境で同じ番号が出るとは限りません。

継承関係も押さえておくと except を書き間違えません(MRO=クラスの継承順を実測で確認しました)。

  • ConnectionRefusedError/ConnectionResetError/ConnectionAbortedError … ConnectionError 経由の OSError。socket.gaierror も OSError の派生
  • TimeoutError は ConnectionError を経由しない(直接 OSError の子)→ except ConnectionError: だけではタイムアウトを取りこぼす
  • socket.timeout は Python 3.10 以降 TimeoutError と同一(実測で socket.timeout is TimeoutError が True)→ いま書くなら except TimeoutError:

例外ごとの扱いと再接続の実装は socket のエラー早見表にあります。

4. Windows 標準ツールだけで 5 分切り分け

ここからは手順です。追加のソフトは要りません。自宅の検証機では、この章のコマンドはすべて管理者権限なしの一般ユーザーで実行できました(Test-NetConnection の出力に IsAdmin : False と表示されます)。現場 PC では昇格できないことが多く、これは実務上の前提です。

稼働中の設備に打つ前に、冒頭の実行前の確認をもう一度見てください。とくに Test-NetConnection -Port は相手に実際の TCP 接続を張り、接続枠を 1 つ消費します。

通信できないときの切り分けフローチャート ping で L3 到達性を確認し、Test-NetConnection で L4 のポート開閉を確認し、Python の例外と WinError で原因を絞り、最後にプロトコル固有の設定へ進む 4 段階のフロー。 切り分けフロー(L3 → L4 → L7) プログラムが相手に接続できない ① ping 相手IP Reply from が相手の IP になっているか いいえ L3 / L2 を見る Get-NetNeighbor・arp -a route print・IP とサブネット はい ② Test-NetConnection -Port TcpTestSucceeded は True か False L4 を見る ポート番号・機器側の待ち受け ファイアウォール・接続数上限 (UDP でオープンする機器は判定不可) True ③ Python の例外と winerror 番号で原因を絞る(3 章の表) 10061 = 待ち受けなし 10060 = 応答が返らない timed out = 自分で打ち切り 11001 = 名前解決の失敗 ④ L7 プロトコル固有へ 接続はできるが応答が正しくない 電文仕様・局番・ユニット ID トピック名・認証(6 章) 下の段へ進むほど、原因はネットワークからアプリケーションへ移る ①〜③ のコマンドは、自宅検証機では管理者権限なしで実行できた
図 2: 通信できないときの切り分けフロー(スマートフォンでは図を横にスクロールできます)

4.1 ステップ 1:Test-NetConnection でポートを直接叩く

Test-NetConnection は 公式リファレンスの説明どおり It supports ping test, TCP test, route tracing, and route selection diagnostics. と、ping と TCP テストの両方を 1 回で実行します。-Port を付けたときだけ TCP テストが走り、-InformationLevel Detailed を付けると経路や送信元アドレスまで表示されます。1 章の出力がその例です。

時間の目安も知っておくと事故が減ります。応答のない未使用 IP(192.168.1.99)に -Port 付きで実行したところ、結果が返るまで 44.3 秒かかりました(1 回計測)。固まったと思って Ctrl+C を押しがちですが、環境によって変わるので少なくとも 1 分は待ってください。すぐ返ってきたかどうか自体も情報です。数秒で TcpTestSucceeded : False が返るなら拒否(10061 相当。Python での拒否は 2.04 秒でした)、数十秒待たされるなら応答なし(10060 相当)を疑います。

なお、相手が UDP でオープンする機器の場合は、このコマンドは使えません。Test-NetConnection -Port は TCP の 3 ウェイハンドシェイクを試すもので、UDP のポートは判定できないためです。PLC の MC プロトコルのように機種や設定で UDP を使うものがあり、その場合の TcpTestSucceeded : False は異常とは限りません。UDP には接続の概念がないので、3 章の逆引き表もそのままは当てはまりません(ConnectionRefusedError は原則出ません)。進める道は次の 3 つです。

  • 機器側の設定画面・パラメータ表で、オープン方式(TCP / UDP)とポート番号を確認する(最優先)
  • L3 までは共通なので、4.3 の Get-NetNeighbor で相手が同じセグメントに居るかまでは切り分けられる
  • 実際に電文を投げて応答の有無を見る(PLC なら PLC 通信の記事へ)

4.2 ステップ 2:自分の PC 側で誰が待ち受けているか

自分がサーバー側を書いている場合、あるいは「昨日まで動いていたのに今日は 10048 が出る」場合は、自機のポートを確認します。netstat -ano の -o は 公式リファレンスで Displays active TCP connections and includes the process ID (PID) for each connection. と説明されているとおり、PID が出るのが要点です。

# 出力例(59001 に関する行だけを抜き出しています)
PS> netstat -ano | Select-String 59001
  TCP    0.0.0.0:59001          0.0.0.0:0              LISTENING       22428
  TCP    192.168.1.16:52073     192.168.1.16:59001     TIME_WAIT       0

# PowerShell だけで完結させる場合
PS> Get-NetTCPConnection -State Listen -LocalPort 59001 |
      Select-Object LocalAddress,LocalPort,State,OwningProcess

LocalAddress LocalPort  State OwningProcess
------------ ---------  ----- -------------
0.0.0.0          59001 Listen         22428

PS> Get-Process -Id 22428 | Select-Object Id,ProcessName

   Id ProcessName
   -- -----------
22428 python

見るのは 2 点です。待ち受けアドレスが 0.0.0.0 なら全 NIC で受けているので、どの LAN 側からでも届きます。127.0.0.1 なら自機からしか繋がりません(「自分の PC では動くのに別の PC から繋がらない」の典型)。もう 1 つは PID で、Get-Process -Id に渡せばどのプログラムが占有しているかが分かります(22428 は検証機の値なので自分の環境の番号に読み替えてください)。犯人は終了し損ねた自分のプログラムであることが多いです。

4.3 ステップ 3:L2 で相手が居るかを見る

ping がタイムアウトする、あるいは Destination host unreachable が返るときは、そもそも同じセグメントに相手が居ない可能性を見ます。arp -a は ARP キャッシュ(IP と MAC の対応表)を表示しますが、読みやすいのは PowerShell の Get-NetNeighbor です。状態が言葉で出ます。

# 出力例(IPv4・特定のインターフェースのみ。IP は説明用の値に置き換え、State は実測どおり)
PS> Get-NetNeighbor -AddressFamily IPv4 -InterfaceIndex 17 |
      Select-Object IPAddress,State

IPAddress             State
---------             -----
192.168.1.1       Reachable
192.168.1.20      Reachable
192.168.1.21          Stale
192.168.1.99    Unreachable
192.168.5.1     Unreachable

状態の読み方は 3 つ。Reachable は直前に通信できたこと、Stale は以前は引けたがしばらく通信がないこと(異常とは限らず、通信すれば戻ります)、Unreachable は ARP に応答が返らない、つまりその IP の機器が同じセグメントに見当たらないことを意味します。実測では未使用 IP の 192.168.1.99 と、切断中の別サブネット側の 192.168.5.1 が Unreachable でした。宛先が Unreachable なら L4 を調べても意味がありません。ケーブル・スイッチのポート・機器の IP 設定へ戻ります。

4.4 ステップ 4:どの NIC から出ていくかを確認する

現場 PC は有線・無線・VPN を同時に持っていることがあります。「設定した IP は正しいのに繋がらない」ときに疑うのは、通信が想定と違う NIC から出ている可能性です。経路は route print -4 の Active Routes、インターフェースの優先度は Get-NetIPInterface で確認できます。

# 出力例(NIC 名と一部の行はマスク・抜粋)
PS> route print -4

IPv4 Route Table
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0      192.168.1.1     192.168.1.16     25
      192.168.1.0    255.255.255.0         On-link      192.168.1.16    281

Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0     192.168.5.1        1

この実測ではPersistent Routes(永続経路)に、いま切断されている仮想アダプターが登録したデフォルトルートが残っていました(0.0.0.0/0 → 192.168.5.1、メトリック 1)。該当アダプターが未接続なので現在の経路選択には効いていませんが、そのアダプターを接続すると本来の経路より優先される可能性があります。過去に入れたソフトが追加した経路が残り続ける実例で、原因が見えないときに確認する価値があります。

優先度そのものを決めているのはインターフェースメトリックです。公式リファレンスは the overall metric used to determine the preference is the sum of the route metric and the interface metric と説明しています(経路メトリックとインターフェースメトリックの合計で決まる)。実測はこうでした。

# 出力例(メトリックの昇順・NIC 名はマスク・上位 4 行に抜粋)
PS> Get-NetIPInterface -AddressFamily IPv4 | Sort-Object InterfaceMetric |
      Select-Object ifIndex,InterfaceAlias,InterfaceMetric,ConnectionState

ifIndex InterfaceAlias        InterfaceMetric ConnectionState
------- --------------        --------------- ---------------
      9 (VPN 仮想アダプター)              5       Connected
      7 Wi-Fi                          25    Disconnected
     17 イーサネット                       25       Connected
     19 (別の仮想アダプター)             35    Disconnected

数字が小さいほど優先されます。この検証機では VPN 用の仮想アダプターが 5 で、有線の 25 より優先度が高い状態でした。さらに Wi-Fi と有線がどちらも 25 で同点でした(このとき Wi-Fi は未接続。抜粋のため表に出ていない未接続のインターフェースにも 25 があります)。両方を接続したときどちらが選ばれるかは、この数字だけでは決まりません。有線で繋いだつもりが別経路から出ていた、と疑うならまずこの表を見ます。

必要なら Set-NetIPInterface -InterfaceMetric で優先度を固定できますが、これはその PC の全通信の経路選択を変える設定変更です。実行前に Get-NetIPInterface の現在値を控え、変更後は設備側だけでなく社内 LAN 側の疎通も確認してください。共用 PC では管理者の許可を取ってから行うのが安全です(この変更には管理者権限が必要で、当サイトでは実行していません)。

5. 現場 LAN でよく詰まる 5 つの原因

ここまでで場所は絞れます。次は、その先で何が起きているかです。

5.1 サブネットマスクの不一致

PC と設備が同じスイッチに繋がっていても、IP とサブネットマスクの組み合わせが噛み合っていなければ通信は成立しません。よくあるのはマスクの桁だけが違うケースです。PC 側が 255.255.255.0、機器側が 255.255.0.0 だと、片方からは同一セグメントに見え、もう片方からはゲートウェイ経由に見えます。ipconfig の値と機器側の設定を並べて見比べてください。片方だけでは気付けません。

5.2 IP アドレスの重複

同じ IP を 2 台に設定してしまうと、ping が通ったり通らなかったりします。Windows はこれを検知してイベントログに記録します。公式のイベント定義(イベント ID 4198)は次の文言です。The system detected an address conflict for IP address %2 with the system having network hardware address %3. The local interface has been disabled.——状況によってはローカルのインターフェースが無効化されると明記されています。確認はイベントビューアーの「Windows ログ > システム」でソース Tcpip を探します。典型的な発生経路は、予備機を持ち込んで固定 IP を振ったら既に同じ IP の機器がいた、というものです。再現には固定 IP の設定変更(管理者権限)が要るため、当サイトでは実測していません(公式定義の引用に留めています)。

5.3 複数 NIC とメトリック

4.4 のとおり、設備用 LAN に有線・社内 LAN に無線という同時接続では、どちらから出ていくかを決めているのは OS です。Test-NetConnection -InformationLevel Detailed の SourceAddress と InterfaceAlias を見れば、その宛先に実際に使われる送信元が分かります(1 章の出力では イーサネット)。

5.4 ファイアウォールのプロファイル

Windows のファイアウォールは Domain / Private / Public の 3 プロファイルを持ち、いま接続中のネットワークがどれに分類されているかで、適用される規則が変わります。読み取りは管理者権限なしでできます。

# 出力例
PS> Get-NetFirewallProfile | Select-Object Name,Enabled,DefaultInboundAction,DefaultOutboundAction

Name    Enabled DefaultInboundAction DefaultOutboundAction
----    ------- -------------------- ---------------------
Domain     True        NotConfigured         NotConfigured
Private    True        NotConfigured         NotConfigured
Public     True        NotConfigured         NotConfigured

PS> Get-NetConnectionProfile | Select-Object InterfaceAlias,NetworkCategory,IPv4Connectivity

InterfaceAlias NetworkCategory IPv4Connectivity
-------------- --------------- ----------------
イーサネット             Private         Internet

NotConfigured は既定値(受信はブロック、送信は許可)のままという意味で、3 プロファイルとも有効でした。接続中のイーサネットは Private 扱いでした。注意点は分類が接続の仕方で変わり得ることで、Private 向けに作った受信許可の規則は、その接続が Public と判定されている間は効きません。自作サーバーを現場 PC で動かして「他の PC から繋がらない」ときは、Get-NetConnectionProfile の NetworkCategory を先に見てください。受信規則を実際にブロックへ設定したときに接続がどう見えるか(拒否として返るのか、無応答になるのか)は、規則の追加に管理者権限が必要なため当サイトでは実測していません。

もう 1 つ、Windows 標準のファイアウォールとは別に、サードパーティのセキュリティ製品が独自のネットワーク保護を持っていることがあります。Get-NetFirewallProfile が正常でも自作アプリの通信だけがブロックされている、という状態が成立します。標準ツールの出力には現れないので、ここまでで説明が付かないときの候補にしてください。確認は各製品の管理画面(ブロック履歴・アプリケーション制御の一覧)で行い、変更には管理者権限と情報システム部門の承認が要ります。当サイトは該当製品を導入していないため、この挙動は未検証です。

5.5 ケーブル・スイッチ側

物理層も候補から外せません。厄介なのは、オートネゴシエーションが片側だけ手動固定になった状態(Duplex ミスマッチ)です。リンクは張れているのに通信品質だけが落ちます。Cisco のサポート文書は、相手側がオートネゴシエーションを設定していない場合について The port does not detect it because there is no method available to perform this.——Duplex を検出する手段がない、と明記しています。そのうえでリンクの両端はオートネゴシエーションを揃える(両方 ON か両方 OFF)ことを求めており、片側が全二重・もう片側が半二重という不一致は 10/100Mb リンクの性能問題として最も多い原因のひとつだとも書かれています。該当する機材がないため、当サイトではこの現象・管理型スイッチの VLAN・産業用スイッチ特有の挙動をいずれも検証していません。工場の基幹ネットワークに関わる部分は、社内のネットワーク管理者に相談するのが早道です。

6. プロトコル別の次の一手

L3 と L4 が通ってもなお動かないなら、原因はプロトコル固有の層に移ります。ここから先は調べる場所がプロトコルごとに変わるので、該当する記事へ進んでください。

使っているものL4 が通ったあとに疑う場所進み先
自作の TCP 通信(socket)電文の区切り、バイト数のずれ、受信の途中切れPython socket の使い方
PLC(MC プロトコル系)機器側のオープン設定、ポート番号、TCP と UDP の別、ライブラリ既定のタイムアウトPLC 通信のよくあるエラー
Modbus/TCPユニット ID(スレーブ ID)、レジスタのアドレス基準(0 始まりか 1 始まりか)pymodbus で Modbus 通信
MQTTブローカーのポートと認証、トピック名、QoS、クリーンセッションMQTT で工場 IoT
OPC UAエンドポイント URL、セキュリティポリシー、証明書の信頼OPC UA を Python で扱う
CANビットレート、終端抵抗、インターフェースの指定python-can で CAN 通信
RS-232C / RS-485そもそも IP を使わない。COM ポート番号、ボーレート、結線pyserial でシリアル通信

最後の行は対比として重要です。シリアル通信には ping も IP アドレスもありません。この記事の手順はまるごと当てはまらず、確認するのは COM ポート番号・ボーレート・パリティ・結線(とくに RS-485 の A/B と終端抵抗)です。探し回る前に、その機器が何で繋がっているかを確認してください。

ライブラリ固有の落とし穴もあります。PLC 通信ライブラリには既定のタイムアウトが短いものがあり、本来 ConnectionRefusedError になる状況が TimeoutError として表面化することがあります(当サイトでは未検証です。お使いのライブラリの既定タイムアウト値はドキュメントで確認してください)。例外の種類はライブラリの設定でも変わるため、3 章の表を当てる前に生の socket で同じ宛先へ接続すると切り分けが速くなります。

7. よくある質問(FAQ)

ping は通るのにポートに繋がらないのはなぜですか?

ping が使う ICMP と、アプリが使う TCP は別のプロトコルだからです。ping が確認するのは相手の IP まで届くこと(L3)だけで、ポートが待ち受けているか(L4)やアプリが応答するか(L7)は見ていません。自宅の検証機でも、同じ相手で PingSucceeded : True と TcpTestSucceeded : False が同時に出る状態を作れました(1 章)。

WinError 10061 と 10060 の違いは何ですか?

10061(ConnectionRefusedError)は相手が接続を明示的に拒否した状態で、パケットは相手まで届いています。疑うのはポート番号違いや機器側のサービス停止です。10060 は応答そのものが返らずタイムアウトした状態で、IP の誤り・経路・フィルタを疑います。実測では 10061 は 2.04 秒で返り、タイムアウトを指定しない接続の 10060 は 21.03 秒かかりました(3 章)。

TimeoutError に winerror が付く場合と付かない場合があるのはなぜですか?

誰が打ち切ったかが違うためです。create_connection に timeout を渡すと Python 側が打ち切り、TimeoutError('timed out') で winerror と errno は None(実測 5.00 秒)。指定しないブロッキング接続では OS が諦めるまで待ち、WinError 10060 付きになります(実測 21.03〜21.05 秒)。e.winerror の有無で、どちらで打ち切られたかを判別できます(3.1)。

ping で Destination host unreachable と出るのに Lost = 0% になるのはなぜですか?

応答を返したのが相手ではなく自分自身だからです。同一サブネットの未使用 IP へ ping すると、自機の IP から「その宛先には届けられない」という ICMP メッセージが返り、統計上は受信に数えられて Lost = 0 (0% loss) と表示されます。Reply from の IP が宛先と一致しているか、本文が Destination host unreachable でないかを必ず確認してください(2.3)。

Test-NetConnection が返ってこないときは故障ですか?

待てば返ってくることがあります。自宅の検証機で同一サブネットの未使用 IP に対して -Port 付きで実行したところ、結果が出るまで 44.3 秒かかりました(1 回計測)。止まったと勘違いして中断しがちなので、1 分程度は待ってから判断してください(4.1)。

管理者権限がなくてもネットワークの切り分けはできますか?

本記事で使うコマンドは、自宅の検証機ではすべて管理者権限なしの一般ユーザーで実行できました(Test-NetConnection の出力 IsAdmin : False で確認)。対象は ping/ipconfig/arp -a/nslookup/netstat -ano/Test-NetConnection/Get-NetTCPConnection/Get-Process/Get-NetNeighbor/route print/Get-NetIPInterface/Get-NetFirewallProfile/Get-NetConnectionProfile の 13 個です。一方で、ファイアウォール規則の追加・変更やインターフェースメトリックの固定、固定 IP の設定には昇格が必要です。

有線と無線のどちらで通信しているか確認するには?

宛先を指定して Test-NetConnection -ComputerName 相手IP -Port ポート -InformationLevel Detailed を実行し、InterfaceAlias と SourceAddress を見るのが手早い方法です。全体の優先順位を見るなら Get-NetIPInterface | Sort-Object InterfaceMetric で、数字が小さいインターフェースが優先されます(4.4)。

8. 検証環境と、測れなかったこと

本記事の実測値は、自宅の検証機(Windows 11 Pro / Python 3.12.10 / 64bit)と家庭用ルーターの環境で、2026 年 9 月に測ったものです。会社支給の端末は使用していません。実行はすべて管理者権限なしの一般ユーザーで、ファイアウォール規則の追加・変更・無効化は一切していません(読み取り系のコマンドのみ)。記事中の IP アドレスは説明用アドレス帯(RFC 1918 の私設アドレスと RFC 5737 の文書用アドレス)に置き換えた値で、192.168.1.16 が検証機、192.168.1.1 が家庭用ルーター、192.168.1.99 が同一サブネットの未使用 IP、192.168.5.1 が切断中の仮想アダプターの経路に出るゲートウェイです。

次の項目は測っていません。断定的な記述は本文でも避けています。

  • ファイアウォールの受信規則でブロックしたときの挙動 — 規則の追加・変更に管理者権限が必要なため未実施。Microsoft の公式資料の記述に留めています
  • WinError 10065(No route to host)の再現 — 到達できない 3 宛先 × timeout 指定あり/なしの計 6 試行で 1 度も発生せず、timeout を指定しない 3 試行はいずれも 10060 でした。経路上のルーターが ICMP 到達不能を返す構成でのみ現れると考えられますが、未実測です
  • IP アドレス重複(イベント ID 4198)の再現 — 固定 IP の設定変更が必要なため未実施。公式のイベント定義の引用のみです
  • 管理型スイッチの VLAN・産業用スイッチ・Duplex ミスマッチ — 該当機材がないため未検証です
  • PLC の同時接続数上限 — 機種ごとに異なるため、具体的な数値は書いていません。対象機種のマニュアルで確認してください
  • UDP でオープンする機器の切り分け — Test-NetConnection -Port は TCP 専用で、該当機器を持っていないため未実測です(4.1)
  • サードパーティのセキュリティ製品による通信ブロック — 該当製品を導入していないため未検証です(5.4)
  • ライブラリ既定のタイムアウトによる例外の入れ替わり — 6 章で触れた挙動は当サイトでは未検証です
  • 社内 DNS の絡む名前解決 — .invalid ドメインでの gaierror 再現のみで、「名前は引けるが古い IP が返る」パターンは未検証です

それでも、この記事の中心にある事実は環境を選びません。ping は L3 の到達性しか見ておらず、TCP のポートが開いているかは別に確認する必要がある——ここを外さなければ、切り分けは「どの層で止まっているか」を順に潰す作業になります。例外の種類と WinError 番号をログへ残すこと、ping の次に Test-NetConnection -Port を打つこと。この 2 つで、原因調査にかかる時間はかなり変わります。

参考文献・一次情報

いずれも 2026 年 9 月 4 日に参照。本文の実測値は、自宅の検証機(Windows 11 Pro / Python 3.12.10)と家庭用ルーターの環境で 2026 年 9 月に採取したものです。本記事は 2026 年 10 月公開時点の内容です。

関連記事