USB/IPを利用して、ローカルPCのUSBデバイスをリモートPCにつなぐ方法

# はじめに
　[USB/IP](https://wiki.archlinux.jp/index.php/USB/IP)を利用して、ローカルPCのUSBデバイスをリモートのPCのUSBデバイスとして認識する方法を紹介する。リモートPCで、手元のUSBデバイスをデバッグしたい場合などに便利な方法。

# 動作確認環境

　USBデバイスを接続する側のローカルPCは、Windows 10/Ubuntu 20.04、USBデバイスを利用する側のリモートPCはUbuntu 22.04で動作確認をした。

|ローカルPC|リモートPC|
|:-:|:-:|
|Windows 10|Ubuntu 22.04|
|Ubuntu 20.04|Ubuntu 22.04|

## 接続確認済みデバイス

* コントローラ
  * [PS4コントローラ DUALSHOCK4(USB有線接続)](https://www.playstation.com/ja-jp/accessories/dualshock-4-wireless-controller/)
  * [Logicool F710](https://gaming.logicool.co.jp/ja-jp/products/gamepads/f710-wireless-gamepad.html)
* USBカメラ
  * [Logicool C270 HD WebCam](https://www.logicool.co.jp/ja-jp/products/webcams/hd-webcam-c270n.960-001265.html)


## 概要
　ローカルPCに接続されているUSBデバイスを、リモートPCに仮想USBデバイスとして接続させる。　
　ローカルPCからリモートPCは、ネットワーク経由でアクセス可能とする。ただし、リモートPCからローカルPCへはアクセスできなくても良い（VPNなどの利用を想定している）。  

```plantuml
@startuml
!include <office/Servers/application_server>
!include <office/Devices/device_laptop>
!include <office/Devices/router>

nwdiag {    
   network remote {
       address = "165.x.x.x/24"
       description = " リモートネットワーク"
       RemotePC [address = "165.x.x.20", description = "<$application_server>\n リモートPC"];
       Gateway [address = "165.x.x.1", description = "<$router>\n ゲートウェイ"];
   }

   RemotePC -- VirtualUSB;
   VirtualUSB [description = "仮想USBデバイス"];

   network local {
       address = "192.168.x.x/24"
       description = "ローカルネットワーク"
       Gateway [address = "192.168.x.1"];
       LocalPC [address = "192.168.x.10", description = "<$device_laptop>\n ローカルPC"];
   }

   LocalPC -- USB;
   USB [description = "USBデバイス"];
}
@enduml
```

# 事前準備：USB/IPのインストール方法

　ローカルPC、リモートPC共通のUSB/IPのインストール方法を示す。OSごとに方法が違うので各々以下に示す。

### USB/IPのインストール：Ubuntu (20.04/22.04)
　USB/IPは、linux-tools-genericに含まれおり、カーネルごとにインストールする必要がある。以下のコマンドでインストールする。

```bash
sudo apt update
sudo apt install linux-tools-generic linux-cloud-tools-generic
sudo apt install linux-tools-virtual hwdata
```

#### 下記のような警告が出る場合の対処方法
カーネルバージョン5.19の場合の警告
```
WARNING: usbip not found for kernel 5.19.0-41

  You may need to install the following packages for this specific kernel:
    linux-tools-5.19.0-41-generic
    linux-cloud-tools-5.19.0-41-generic

  You may also want to install one of the following packages to keep up to date:
    linux-tools-generic
    linux-cloud-tools-generic
```

　カーネルごとのバイナリをインストールする。カーネルバージョン5.19の場合は以下のコマンドでインストールする。

```bash
sudo apt update
sudo apt install linux-tools-5.19.0-41-generic inux-cloud-tools-5.19.0-41-generic
```

:::note
自身の環境のカーネルバージョンにあったパッケージをインストールすること。
(参考)カーネルバージョンの確認方法
```bash
uname -a
```
:::

#### 正しくインストールできているか確認

```bash
usbip version
```
下記が表示されれば成功。
```bash
usbip (usbip-utils 2.0)
```

### USB/IPのインストール：Windows 10
　[WSLドキュメント「USBデバイスを接続する」](https://learn.microsoft.com/ja-jp/windows/wsl/connect-usb#install-the-usbipd-win-project)を参考にしてインストールする。
　Windowsパッケージマネージャーの場合は、Powershellを管理者権限で開いて以下のコマンドでインストールする。

```powershell
winget install --interactive --exact dorssel.usbipd-win
```

# 方法：USBデバイスの共有
## 構成

　接続のイメージは以下のようになる。ローカルPCにUSBデバイスが接続されており、リモートPCでローカルPCに接続されているUSBデバイスを利用する。ローカルPCからリモートPCへのSSHでのアクセスは可能とする。

```plantuml
@startuml
!include <office/Servers/application_server>
!include <office/Devices/device_laptop>
!include <office/Devices/router>

nwdiag {    
   network remote {

       RemotePC [address = "165.x.x.20", description = "<$application_server>\n リモートPC"];
       Gateway [address = "165.x.x.1", description = "<$router>\n ゲートウェイ"];
   }

   RemotePC -- VirtualUSB
   VirtualUSB [description = "仮想USBデバイス"]

   network local {
       address = "192.168.x.x/24"
       Gateway [address = "192.168.x.1"];
       LocalPC [address = "192.168.x.10", description = "<$device_laptop>\n ローカルPC"];
   }

   LocalPC -- USB
   USB [description = "USBデバイス"]
}
@enduml
```

## USBデバイスの接続

　ローカルPCに接続されたUSBデバイスを、リモートPCに仮想USBデバイスとして接続する。

### ローカルPCでの設定： USBサーバー側

　物理的なUSBデバイスを接続する側の方法。リモートPCに接続したいUSBデバイスをローカルPCのUSBポートに接続する。USB/IPでは、USB機能を提供する側なのでUSBサーバーとなる。

#### Windowsの場合

　PowerShellを管理者権限で開いて以下を実行する。

##### USBデバイスをUSB/IPに接続

1. 接続デバイスを確認
    ```powershell
    usbpid.exe list
    ```
    例
    ```powershell
    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    2-3    054c:09cc  Wireless Controller, USB 入力デバイス                         Not shared
    2-6    1bcf:28cc  Integrated Webcam                                             Not shared
    ```
1. BUSUD(2-3)を接続したい場合
    ```powershell
    usbipd bind -b 2-3 -f
    ```
    * -f: 強制バインドオプション
      * Windowsでは、デバイスマネージャーに所有権がとられるので基本は有効にする
    
    STATEが```Shared(forcecd)```になっていれば成功
    ```powershell
    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    2-3    054c:09cc  Wireless Controller, USB 入力デバイス                         Shared (forced)
    2-6    1bcf:28cc  Integrated Webcam                                             Not shared
    
    Persisted:
    GUID                                  DEVICE
    f3624168-a2b0-4276-83c0-2ede598c910d  Wireless Controller, USB 入力デバイス
    ```
1. [SSHでUSB/IPポートの転送設定](https://qiita.com/ntrlmt/items/ec19016c2bae9b8ca3bd#windowsubuntu%E5%85%B1%E9%80%9Assh%E3%81%A7usbip%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E8%BB%A2%E9%80%81%E8%A8%AD%E5%AE%9A)


#### Ubuntuの場合

　ターミナルで以下を実行する。

1. USB/IPの有効化
    ```bash
    sudo modprobe vhci-hcd
    sudo modprobe usbip_host
    ```
1. 接続デバイスの確認
    ```bash
    sudo usbip list -l
    ```
    例
    ```bash
     - busid 1-10 (8087:0026)
       Intel Corp. : unknown product (8087:0026)
    
     - busid 1-2 (054c:09cc)
       Sony Corp. : unknown product (054c:09cc)    
    ```
1. 利用したいUSBデバイスをバインドする
    BUSID 1-2の場合
    ```
    sudo usbip bind -b 1-2
    ```
    以下のようなメッセージが出れば成功。
    ```bash
    usbip: info: bind device on busid 1-2: complete
    ```
1. USB/IPのデーモン起動
    　バインドしたUSBデバイスをネットワーク経由で共有できるようにする。
    ポート番号3240(デフォルト設定)の場合
    ```bash
    sudo usbip -D
    ```
    　もしくは、接続状態のログを確認したい場合は以下でも良い。この場合は、バックグランドでは実行されないので、ターミナルは開いたままにする。  
    ```bash
    sudo usbip list    
    ```
    以下のようなメッセージが出れば成功。
    ```bash
    usbipd: info: starting usbipd (usbip-utils 2.0)
    usbipd: info: listening on 0.0.0.0:3240
    usbipd: info: listening on :::3240
    usbipd: info: connection from 127.0.0.1:38046
    usbipd: info: received request: 0x8005(5)
    usbipd: info: exportable devices: 1
    usbipd: info: request 0x8005(5): complete
    ```
1. [SSHでUSB/IPポートの転送設定](https://qiita.com/ntrlmt/items/ec19016c2bae9b8ca3bd#windowsubuntu%E5%85%B1%E9%80%9Assh%E3%81%A7usbip%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E8%BB%A2%E9%80%81%E8%A8%AD%E5%AE%9A)

#### 【Windows/Ubuntu共通】SSHでUSB/IPポートの転送設定
　リモートPCからローカルPCに直接接続できない場合は、SSHでリモートPCへ接続し、USB/IPが公開しているポートをポートフォワーディングする。
　Windowsの場合はPowershell、Ubuntuの場合はターミナルで以下を実行する。プログラムが実行され続けるので、実行ウィンドウは開いたままにする。

USB/IPのデフォルトポート(3240)を使っている場合
```bash
ssh -N -R 3240:localhost:3240 {リモートPCのユーザ名}@{リモートPCのIPアドレス}
```

### リモートPC (Ubuntu)での設定：USBクライアント側

　USBデバイスをネットワーク経由でつなぎたい側の設定。USB/IPでは、USBデバイスを利用するのでUSBクライアントとなる。

1. USB/IPの有効化
    ```bash
    sudo modprobe vhci-hcd
    ```
1. ネットワーク上にあるデバイスを確認
    ```bash
    usbip list -r localhost
    ```
    デバイスが見えれば成功。
    ```bash
    Exportable USB devices
    ======================
     - localhost
            2-3: Sony Corp. : DualShock 4 [CUH-ZCT2x] (054c:09cc)
               : USB\VID_054C&PID_09CC\5&128B4AD4&0&3
               : (Defined at Interface level) (00/00/00)
               :  0 - Audio / Control Device / unknown protocol (01/01/00)
               :  1 - Audio / Streaming / unknown protocol (01/02/00)
               :  2 - Audio / Streaming / unknown protocol (01/02/00)
               :  3 - Human Interface Device / No Subclass / None (03/00/00)
    ```
    
    :::note info
    SSHポートフォワーディングしているので、-rオプションでは、ローカルホストを指定する。

1. 接続したいUSBデバイス接続する
    ```bash
    sudo usbip attach -r localhost -b x-x
    ```
    バスID 2-3の場合
    ```bash
    sudo usbip attach -r localhost -b 2-3
    ```
    以下のコマンドで確認
    ```bash
    sudo usbip port
    ```
    Imported USB devicesにデバイスが表示されていれば成功。
    ```bash
    Imported USB devices
    ====================
    Port 00: <Port in Use> at Full Speed(12Mbps)
           Sony Corp. : DualShock 4 [CUH-ZCT2x] (054c:09cc)
           5-1 -> usbip://localhost:3240/2-3
               -> remote bus/dev 002/003
    ```
    また、```lsusb```コマンドで、USBが認識を確認しても良い。
    ```bash
    lsusb
    ```
    ```bash
    Bus 005 Device 002: ID 054c:09cc Sony Corp. DualShock 4 [CUH-ZCT2x]
    ```


## USBデバイスの切断

　USBデバイスを利用し終わったら切断処理を行う。

### リモートPC上の設定

1. 接続済みデバイスの確認
    ```bash
    sudo usbip port
    ```
    ```bash
    Imported USB devices
    ====================
    Port 00: <Port in Use> at Full Speed(12Mbps)
           Sony Corp. : DualShock 4 [CUH-ZCT2x] (054c:09cc)
           5-1 -> usbip://localhost:3240/2-3
               -> remote bus/dev 002/003
    ```

2. 切断
    例：ポートは00を切断したい場合
    ```bash
    sudo usbip detach -p 0
    ```
    下記のメッセージが出れば成功
    ```bash
    usbip: info: Port 0 is now detached!
    ```
    
### ローカルPC上の設定
#### Windowsの場合

　PowerShellを管理者権限で開いて以下を実行する。

##### USBデバイスをUSB/IPから切断

1. 接続済みデバイスの確認
    ```powershell
    usbipd.exe list
    ```
    ```Shared```または```Shared (forced)```になっているデバイスが接続済みデバイス。
    ```powershell
    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    2-3    054c:09cc  Wireless Controller, USB 入力デバイス                         Shared (forced)
    2-6    1bcf:28cc  Integrated Webcam                                             Not shared

    Persisted:
    GUID                                  DEVICE
    f3624168-a2b0-4276-83c0-2ede598c910d  Wireless Controller, USB 入力デバイス
    ```
2. 切断
    BUSID(2-3）を切断したい場合、下記コマンドで切断する。
    ```powershell
    usbipd unbind -b 2-3
    ```
    下記コマンドで確認。
    ```powershell
    usbipd.exe list
    ```
    ```Not shared```になっていれば成功。
    ```powershell
    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    2-3    054c:09cc  Wireless Controller, USB 入力デバイス                         Not shared
    2-6    1bcf:28cc  Integrated Webcam                                             Not shared
    
    Persisted:
    GUID                                  DEVICE
    f3624168-a2b0-4276-83c0-2ede598c910d  Wireless Controller, USB 入力デバイス
    ```

#### Ubuntuの場合
1. デバイスの確認
    ```bash
    usbip list
    ```
    ```bash
     - busid 1-10 (8087:0026)
       Intel Corp. : unknown product (8087:0026)
    
     - busid 1-2 (054c:09cc)
       Sony Corp. : unknown product (054c:09cc)    
    ```
2. 切断
    BUSID(1-2)を切断したい場合
    ```bash
    sudo usbip unbind -b 1-2
    ```
    下記のメッセージが出れば成功。
    ```bash
    usbip: info: unbind device on busid 1-2: complete
    ```

# まとめ
　ローカルPCに接続したUSBデバイスをリモートPCに接続する方法を紹介した。手元のゲームコントローラーやUSBカメラなどをリモートPCに接続できるので、リモートPCでデバイスのデバッグなどをするのに便利であった。

# トラブルシューティング

## USBクライアント側
### デバイスに接続(attach)できない

#### エラー内容
```bash
libusbip: error: udev_device_new_from_subsystem_sysname failed
usbip: error: open vhci_driver (is vhci_hcd loaded?)
```

##### 原因
Ubuntuにvhciモジュールが設定されていない場合のエラー。

##### 対策：　リモートPC (Ubuntu)

下記コマンドを実行。
```bash
sudo modprobe vhci-hcd
```
下記コマンドで確認。
```bash
lsmod | grep vhci
```

vhci_hcdが表示されていれば成功。
```
vhci_hcd               57344  0
usbip_core             40960  1 vhci_hcd
```


#### usbip: import deviceエラーが発生する

```
usbip: error: import device
```
##### 原因
Ubuntuで、USB/IPを利用するアクセス権限が足りていない。

##### 対策：　リモートPC (Ubuntu)

attach時に、sudo をつける。

例
```bash
sudo usbip attach -r localhost -b 2-3
```

#### usbip: Device busyエラーが発生する

```bash
usbip: error: Attach Request for x-x failed - Device busy (exported)
```

##### 原因

Windowsの場合、デバイスマネジャーがデバイスを利用している。

##### 対策： ローカルPC (Windows)

Windowsの```usbipd```コマンド実行時に、強制バインドオプション(```-f```)を入れて状態でバインドする。
```bash
usbipd bind -b x-x -f
```
上記コマンドを実行後に、USBデバイスを一度抜いて、再度挿すと、デバイスマネージャーが表示が消えて利用可能となる。

## USBサーバ側
### 共通
#### SSHポートフォワーディングに失敗する

```bash
Warning: remote port forwarding failed for listen port 3240
```

##### 対策

* Windows
   3240ポートを使用しているプロセスを検索
    ```
    netstat -nao | findstr "3240"
    ```
    結果例
    ```
      TCP    0.0.0.0:3240           0.0.0.0:0              LISTENING       17432
      TCP    [::]:3240              [::]:0                 LISTENING       17432
    ```
    タスクの強制終了(管理者権限で実行)
    ```
    taskkill /pid 17432 /F
    ```

* Ubuntu
    ```bash
    sudo ss -lptn 'sport = :3240'
    ```
    ```
    State             Recv-Q            Send-Q                       Local Address:Port                        Peer Address:Port            Process                                       LISTEN            0                 128                              127.0.0.1:3240                             0.0.0.0:*                users:(("sshd",pid=3947789,fd=9))            LISTEN            0                 128                                  [::1]:3240                                [::]:*                users:(("sshd",pid=3947789,fd=7))  
    ```
    
    ```
    kill -9 3947789
    ```

### Winodws
#### usbipd not runningエラーが発生する

```bash
usbipd: warning: The service is currently not running; a reboot should fix that.
```

#### 対策

サービスから、「USBIP Device Host」を選択して、「右クリック⇒開始(S)」で開始する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638977/aa99256a-6699-aeb9-52c2-0fd289e8a4db.png)




# 参考

https://learn.microsoft.com/ja-jp/windows/wsl/connect-usb

https://blog.danman.eu/usbip-utils-2-0-on-ubuntu/

https://askubuntu.com/questions/1406205/install-usb-ip-on-ubuntu-22-04-on-windows-subsystem-for-linux

https://developer.mamezou-tech.com/blogs/2024/01/09/usbip/

https://www.tencentcloud.com/jp/document/product/213/35722

https://github.com/dorssel/usbipd-win/issues/122