╔══════════════════════════════════════════════════════════════════════╗
║ ║
║ ████████╗██████╗ ██╗ ██╗███╗ ██╗██╗ ██╗ ║
║ ╚══██╔══╝██╔══██╗ ██║ ██║████╗ ██║██║ ██╔╝ ║
║ ██║ ██████╔╝█████╗██║ ██║██╔██╗ ██║█████╔╝ ║
║ ██║ ██╔═══╝ ╚════╝██║ ██║██║╚██╗██║██╔═╝ ║
║ ██║ ██║ ███████╗██║██║ ╚████║██║ ║
║ ╚═╝ ╚═╝ ╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ║
║ ║
║ USB CONSOLE HACK ║
║ Linux driver for TP-Link JetStream switch console port ║
║ ║
╚══════════════════════════════════════════════════════════════════════╝
TP-Link ships their JetStream managed switches (SG3428, SG3428X, SG3428MP, SG2218, etc.) with a micro-USB console port for CLI access. One problem:
There is no Linux driver.
The USB chip inside uses VID:2357 PID:0701 with a proprietary protocol. TP-Link only provides a Windows driver (CypressUsbConsoleWindowsDriver.sys). Linux kernel modules (cp210x, ch341, ftdi_sio, cdc_acm, usbserial) all fail:
| Driver | Result |
|---|---|
cp210x |
cp210x_open - Unable to enable UART |
cdc_acm |
Rejected (device class 0xFF, not 0x02) |
ch341 |
Not detected |
ftdi_sio |
Not detected |
usbserial (generic) |
Loads, creates /dev/ttyUSB0, but cannot set baud rate |
The generic usbserial driver creates a device node but all data comes through as garbage because the baud rate is stuck at whatever the chip defaults to.
We reverse-engineered the USB protocol by brute-forcing control transfers across every known serial chip protocol:
CH341 (0x5F READ_VERSION) -> STALL
CP210x (0x04 GET_LINE_CTL) -> STALL
FTDI (0x05 MODEM_STATUS) -> STALL
PL2303 (0x8484 VENDOR_READ) -> STALL
Cypress (0xB0 GET_VERSION) -> STALL
CDC ACM (0x21 GET_LINE_CODING) -> 9600/8N1 <<<< BINGO
The chip responds to CDC ACM class requests (GET_LINE_CODING, SET_LINE_CODING) even though it declares itself as Vendor Specific (class 0xFF) in its USB descriptor.
This means we can:
- Send
SET_LINE_CODING(bmRequestType=0x21, bRequest=0x20) via libusb to set 38400/8N1 - Read/write data through bulk endpoints (OUT: 0x02, IN: 0x83)
- Get a fully functional serial console without any kernel driver
$ lsusb -v -d 2357:0701
Bus 001 Device 002: ID 2357:0701 TP-Link TP-Link USB Serial COM Port
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
idVendor 0x2357 TP-Link
idProduct 0x0701
bcdDevice 6.05
Interface Descriptor:
bInterfaceClass 255 Vendor Specific Class <-- THIS IS THE LIE
bInterfaceSubClass 0
bNumEndpoints 3
Endpoint 0x81 EP 1 IN Interrupt
Endpoint 0x02 EP 2 OUT Bulk (Host -> Switch)
Endpoint 0x83 EP 3 IN Bulk (Switch -> Host)
The key insight: bInterfaceClass = 255 prevents automatic driver binding, but the firmware actually implements CDC ACM class requests. Someone at TP-Link configured the USB descriptor to say "Vendor Specific" when the chip actually speaks a standard protocol.
# Dependencies (Debian/Ubuntu)
sudo apt install python3-usb libusb-1.0-0
# Clone
git clone https://github.com/YOUR_USERNAME/tplink-usb-console-hack.git
cd tplink-usb-console-hack
# Run (requires root for USB access)
sudo python3 tplink_console.py interactive# Create udev rule
sudo tee /etc/udev/rules.d/99-tplink-console.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="2357", ATTR{idProduct}=="0701", MODE="0666"
EOF
# Reload
sudo udevadm control --reload-rules && sudo udevadm triggersudo python3 tplink_console.py interactiveFull bidirectional terminal. Type commands, see responses. Like minicom but without needing a kernel driver. Exit with Ctrl+C.
sudo python3 tplink_console.py scanTests all common baud rates (9600, 19200, 38400, 57600, 115200) and reports which one produces readable output:
9600 baud: 3 bytes, 0% readable
19200 baud: 2 bytes, 0% readable
38400 baud: 300 bytes, 100% readable <<<< MATCH
57600 baud: 2 bytes, 0% readable
115200 baud: 2 bytes, 0% readable
sudo python3 tplink_console.py probeIdentifies which USB serial protocol the chip supports by testing vendor-specific control transfers:
[-] CH341 READ_VERSION -> STALL
[-] CP210x GET_LINE_CTL -> STALL
[-] FTDI MODEM_STATUS -> STALL
[-] PL2303 VENDOR_READ -> STALL
[-] Cypress GET_VERSION -> STALL
[+] CDC ACM GET_LINE_CODING -> 009600 / 8N1
sudo python3 tplink_console.py resetFull automated factory reset via the switch's BootUtil menu:
- Power off the switch
- Run the command
- Power on the switch when prompted
- Tool automatically sends
Ctrl+Bto intercept bootloader - Executes Password Recovery (option 6) + Factory Reset (option 2)
- Reboots the switch
Note: BootUtil's Y/N confirmation prompts reject standard \r\n line endings with "Input string contains illegal characters". The workaround (discovered through trial and error) is sending the menu option with \r immediately followed by Y\r with only a 100ms delay.
sudo python3 tplink_console.py send "show system-info"
sudo python3 tplink_console.py send "show interface vlan 1"┌──────────────────────────────────────────────────────────────┐
│ tplink_console.py │
│ │
│ 1. Find USB device (VID:2357 PID:0701) via libusb │
│ 2. Detach kernel driver (usbserial_generic) │
│ 3. Claim USB interface 0 │
│ 4. Send CDC ACM SET_LINE_CODING: │
│ bmRequestType = 0x21 (Class, Interface, Host-to-Device) │
│ bRequest = 0x20 (SET_LINE_CODING) │
│ wValue = 0 │
│ wIndex = 0 │
│ data = {38400, 0, 0, 8} (baud, stop, par, bits)│
│ 5. Read/Write via bulk endpoints: │
│ EP 0x02 OUT (Bulk) -> Commands to switch │
│ EP 0x83 IN (Bulk) -> Responses from switch │
└──────────────────────────────────────────────────────────────┘
| Parameter | Value |
|---|---|
| Baud Rate | 38400 (not 9600, not 115200) |
| Data Bits | 8 |
| Stop Bits | 1 |
| Parity | None |
| Flow Control | None |
| Default IP | 192.168.0.1 |
| Default Credentials | admin / admin |
| BootUtil Key | Ctrl+B during boot |
| BootUtil Reset | Option 2 (full reset) or Option 6 (password only) |
- TP-Link TL-SG3428 v2.20 (JetStream L2+ 28-Port Managed Switch)
- BootUtil v1.0.0 (firmware Feb 13 2025)
Should work on any TP-Link switch with USB console VID:2357 PID:0701, including:
- TL-SG3428X
- TL-SG3428MP
- TL-SG3428XMP
- TL-SG2218
- Other JetStream/Omada models with micro-USB console
For those curious about the full reverse-engineering process:
-
Initial attempt:
cp210xdriver loaded, found device, created/dev/ttyUSB0, then failed with"cp210x_open - Unable to enable UART". Dead end. -
Generic driver:
modprobe usbserial vendor=0x2357 product=0x0701created/dev/ttyUSB0butsttycouldn't configure it.picocomshowed garbledj§k§k§k§at every baud rate. The driver was reading data but couldn't change the UART speed. -
The same garbage at all baud rates was the key clue: the generic driver ignores baud rate changes for this device. It always reads at the chip's internal default.
-
Chip fingerprinting: Wrote a brute-force script that tries every known vendor-specific USB control transfer. CH341, CP210x, FTDI, PL2303, Cypress all returned STALL. But CDC ACM GET_LINE_CODING returned 9600/8N1. The chip was lying about its USB class.
-
SET_LINE_CODING to 38400: Once we could configure the baud rate via libusb, everything worked. 300 bytes of clean ASCII from the switch console.
-
BootUtil line ending bug: The switch's BootUtil menu accepts
\r\nfor numbered menu options but rejects it for Y/N confirmations with "Input string contains illegal characters". Only\rworks, and it must be sent with precise timing (100ms after the menu selection).
MIT License. Do what you want. No warranty. If you brick your switch, that's between you and TP-Link support.
- OSSAC (Osmiotech Seguridad Automatización y Control S.A.S.)
- Built with Claude Code - February 2026
- "We didn't hack the switch. We wrote the driver they didn't ship."