โปรโตคอลการสื่อสาร
เอกสารนี้ระบุรูปแบบการโยงที่สมบูรณ์ระหว่าง agent, exchange และ cli คำจำกัดความอยู่ใน crates/proto และทั้งสามใช้รหัสเดียวกัน: ฟิลด์ถูกกำหนดไว้ในที่เดียว ดังนั้นความไม่สอดคล้องกันจึงปรากฏขึ้นในเวลาคอมไพล์ แทนที่จะเป็นในระหว่างเซสชันการใช้งานจริง
ให้บริการแก่ผู้ให้บริการตรวจสอบการรับส่งข้อมูลเครือข่ายเพื่อวินิจฉัยปัญหา และนักพัฒนาเปลี่ยนโปรโตคอลหรือเขียนไคลเอนต์บุคคลที่สาม
สารบัญ
- ภาพรวม
- การขนส่งและจุดสิ้นสุด
- เวอร์ชันโปรโตคอล
- ข้อความควบคุม
- เฟรมข้อมูลไบนารี
- Session ID การแปลระหว่างสองเนมสเปซ
- ลำดับการจับมือ
- Eการจัดการข้อผิดพลาดและความล้มเหลวหมวดหมู่
- Keepalive และหมดเวลา
- แรงดันย้อนกลับ
- Android การควบคุมหน้าจอ
- Eเซสชันที่ปิดผนึกตั้งแต่ต้นทางถึงปลายทาง
ภาพรวม
โปรโตคอลมีสองชั้นที่ใช้การเชื่อมต่อ WebSocket ร่วมกัน:
| เลเยอร์ | ผู้ให้บริการ | การเข้ารหัส | สารบัญ |
|---|---|---|---|
| ควบคุมข้อความ | WebSocket ข้อความ เฟรม | เจสัน | การลงทะเบียน ฮาร์ทบีท ช่วงเปิด/ปิด การแจ้งเตือนผล |
| กรอบข้อมูล | WebSocket binary เฟรม | ส่วนหัวคงที่ 9 ไบต์ + ไบต์ดิบ | สตรีมไบต์ PTY คำสั่ง stdout/stderr |
การฝังข้อมูลสตรีมมิ่งใน JSON เป็น base64 จะเพิ่มขนาดขึ้นประมาณ 33% การรับส่งข้อมูล PTY ประกอบด้วยแพ็กเก็ตขนาดเล็กบ่อยครั้งซึ่งสะท้อนการกดแป้นพิมพ์แต่ละครั้ง ดังนั้นโอเวอร์เฮดนี้จึงส่งผลโดยตรงต่อเวลาแฝงของการโต้ตอบ ข้อความควบคุมไม่บ่อยนัก ทำให้การอ่าน JSON มีคุณค่ามากขึ้น: การจับแพ็กเก็ตสามารถเข้าใจได้ทันที
ตามแบบแผน แต่ละเฟรมไบนารี WebSocket ** จะมีหนึ่งเฟรมข้อมูล** เท่านั้น ไบต์ที่เกินหรือหายไปถือเป็นข้อผิดพลาด
การขนส่งและจุดสิ้นสุด
| จุดสิ้นสุด | ลูกค้า | การรับรองความถูกต้อง |
|---|---|---|
GET /ws/device |
agent | เฟรมแรก หลังจากสร้างการเชื่อมต่อต้องเป็นข้อความควบคุม register ที่มี device_id และ token |
GET /ws/operator |
remote-cli | ส่วนหัวคำขอ HTTP Authorization: Bearer <运维令牌> ในการจับมือ WebSocket |
ทั้งสองเส้นทางใช้ประเภทข้อมูลรับรองแยกกัน โทเค็นอุปกรณ์ไม่สามารถเข้าถึงจุดสิ้นสุดของผู้ให้บริการ และในทางกลับกัน
ข้อมูลประจำตัวของผู้ให้บริการใช้ส่วนหัวของคำขอแทนที่จะเป็นสตริงการสืบค้น URL เนื่องจาก URL ปรากฏในบันทึกพร็อกซีย้อนกลับ ประวัติเบราว์เซอร์ และส่วนหัวของผู้อ้างอิง โทเค็นอุปกรณ์ใช้เฟรมแรกแทนที่จะเป็นส่วนหัวคำขอ เนื่องจากไคลเอ็นต์แบบฝังบางตัวไม่สามารถปรับแต่งส่วนหัว handshake ของ WebSocket ได้อย่างง่ายดาย วิธีเฟรมแรกยังอนุญาตให้รุ่นโปรโตคอลไม่ตรงกันและโทเค็นที่ไม่ถูกต้องเพื่อใช้ข้อความตอบกลับเดียวกัน
การใช้งานจริงต้องใช้ wss://: ข้อมูลประจำตัวทั้งสองประเภทจะปรากฏเป็นข้อความธรรมดาระหว่างการจับมือกัน
เวอร์ชันโปรโตคอล
pub const PROTOCOL_VERSION: u16 = 1;
มีการเปรียบเทียบเวอร์ชันทั้งสองทิศทางระหว่างการจับมือกัน ความไม่ตรงกันทำให้เกิดการปฏิเสธทันทีพร้อมคำอธิบายที่ชัดเจน วิธีนี้จะป้องกันไม่ให้เวอร์ชันที่ไม่ตรงกันปรากฏเป็นความล้มเหลวในการแยกวิเคราะห์ที่อธิบายไม่ได้ในข้อความในภายหลัง ซึ่งอาจเป็นเรื่องยากที่จะวินิจฉัย
- ฝั่งอุปกรณ์: agent มีเวอร์ชันอยู่ใน
registerในกรณีที่ไม่ตรงกัน เซิร์ฟเวอร์ส่งคืนregister_ackด้วยok: falseอธิบายทั้งสองเวอร์ชันในerrorและปิดการเชื่อมต่อ - ฝั่งผู้ดำเนินการ: เซิร์ฟเวอร์จะส่ง
welcomeในเชิงรุกหลังจากสร้างการเชื่อมต่อ หาก CLI ตรวจพบความไม่ตรงกัน ระบบจะรายงานข้อผิดพลาดและแนะนำให้ผู้ใช้อัปเกรดเป็นเวอร์ชันที่ตรงกับserver_version
ข้อความที่ไม่รู้จัก ค่า type จะถูกปฏิเสธเสมอ ไม่เคยตีความว่าเป็นประเภทที่รู้จักโดยไม่โต้ตอบ
ควบคุมข้อความ
ข้อความควบคุมทั้งหมดเป็นอ็อบเจ็กต์ JSON ที่แท็กโดยฟิลด์ type (serde enum ที่แท็กภายใน) ค่าแท็กและชื่อฟิลด์ใช้ snake_case session_id ได้รับการซีเรียลไลซ์อย่างโปร่งใสเป็นตัวเลขธรรมดา
อุปกรณ์ขึ้น (agent → exchange)
type |
เขตข้อมูล | วัตถุประสงค์ |
|---|---|---|
register |
protocol_version: u16device_id: stringtoken: stringinfo: DeviceInfo |
ต้องเป็นข้อความ first หลังการเชื่อมต่อ เซิร์ฟเวอร์ไม่ยอมรับข้อความอื่นก่อนหน้านั้น โดยมีกำหนดเวลา 15 วินาที |
heartbeat |
metrics: Metrics |
ตัวชี้วัดการดำเนินงานเป็นระยะ ความสดของการเชื่อมต่อได้รับการจัดการแยกกันโดยปิงปอง/ปิงปอง WebSocket ดั้งเดิม: ฮาร์ตบีทที่หายไปไม่ได้หมายความถึงการเชื่อมต่อที่ขาด หรือในทางกลับกัน |
exec_started |
session_idjob_idholdtimeout_secs |
คำสั่งเริ่มต้น; job_id ใช้ในรายการพักและสำหรับการติดตั้งใหม่หลังจากเชื่อมต่อใหม่ |
exec_exit |
session_idcode: i32 | nulltimed_out: bool |
คำสั่งเสร็จสิ้น code คือ null เมื่อสัญญาณยุติกระบวนการ timed_out บ่งชี้ถึงการบังคับยุติเนื่องจากการหมดเวลา |
exec_list_resp |
session_idjobs: ExecHoldInfo[] |
รายการงานค้าง |
ubus_resp |
session_idcode: i32data: any |
ผลการโทร ubus code คือโค้ดส่งคืน ubus 0 หมายถึงความสำเร็จ |
android_resp |
session_idresult: AndroidResult |
การทำงานของหน้าจอ Android เสร็จสมบูรณ์ ข้อความนี้จะสิ้นสุดเซสชัน |
pty_closed |
session_idcode: i32 | null |
เซสชัน PTY สิ้นสุดแล้ว code คือโค้ดทางออกของเชลล์ |
session_error |
session_idmessage: string |
ข้อผิดพลาดระดับเซสชัน อุปกรณ์จะส่งสิ่งนี้เมื่อไม่สามารถดำเนินการตามคำขอได้ แทนที่จะส่งไปโดยไม่ตั้งใจ |
register ตัวที่สองบนการเชื่อมต่อที่ลงทะเบียนไว้แล้วจะถือเป็นการใช้โปรโตคอลในทางที่ผิดและบันทึกเป็นคำเตือน
ตัวอย่าง:
{"type":"register","protocol_version":1,"device_id":"lab-router","token":"a1b2….c2Vj…","info":{"hostname":"OpenWrt","model":"ath79/generic","openwrt_version":"OpenWrt 23.05.5","kernel":"5.15.137","arch":"mips","agent_version":"0.1.0"}}
{"type":"exec_exit","session_id":31,"code":0,"timed_out":false}
อุปกรณ์ลง (exchange → agent)
type |
เขตข้อมูล | วัตถุประสงค์ |
|---|---|---|
register_ack |
ok: boolerror: string | nullheartbeat_interval_secs: u64 |
ผลการลงทะเบียน ด้วย ok: false นั้น error จะจัดเตรียมเหตุผล จากนั้นเซิร์ฟเวอร์จะปิดการเชื่อมต่อ เซิร์ฟเวอร์ระบุช่วงเวลาการเต้นของหัวใจเพื่อให้สามารถลดความถี่จากส่วนกลางได้เมื่อฟลีตเติบโตขึ้นโดยไม่ต้องกำหนดค่าอุปกรณ์แต่ละเครื่องใหม่ agent หนีบ 0 ถึงอย่างน้อย 1 วินาทีเพื่อป้องกันไม่ให้ตัวจับเวลาไม่ว่างทำให้ CPU อิ่มตัว |
device_access |
managers: DeviceManager[]updated_at: u64 (มิลลิวินาที) |
ใครได้รับอนุญาตในอุปกรณ์นี้ DeviceManager = operator_id, username, email?, role, level (manage/view), source (role_default/grant) ส่งหลังจากการลงทะเบียนและการอนุญาตเปลี่ยนแปลงเฉพาะกับตัวแทนที่โฆษณาความสามารถ device_access เท่านั้น ดูที่เก็บ docs/permissions.md |
exec_req |
session_idcommand: stringtimeout_secs: u64hold: bool (ค่าเริ่มต้น true)hold_timeout_secs?: u64 |
ดำเนินการคำสั่ง ด้วย hold=true (ค่าเริ่มต้น) กระบวนการสามารถดำเนินต่อไปได้หลังจากที่ตัวดำเนินการยกเลิกการเชื่อมต่อ การหมดเวลาใช้ hold_timeout_secs (ขาด/0 → 3600, ฮาร์ดสูงสุด 86400) hold=false ยกเลิกคำสั่งเมื่อตัวดำเนินการยกเลิกการเชื่อมต่อ |
exec_cancel |
session_id |
ยุติการดำเนินการคำสั่ง (ฆ่า) ก่อนกำหนด |
exec_detach |
session_id |
หยุดการสตรีมเอาต์พุตไปยังตัวดำเนินการปัจจุบันในขณะที่ยังคงกระบวนการพักไว้ |
exec_list_req |
session_id |
สอบถามงานระงับปัจจุบัน |
exec_attach_req |
session_idjob_id |
แนบงานการระงับเข้ากับเซสชันนี้อีกครั้ง และเล่นซ้ำเอาต์พุตที่บัฟเฟอร์ |
exec_hold_kill |
session_idjob_id |
ยุติงานการระงับโดย job_id |
exec_hold_tail |
session_idjob_id |
ติดตามเอาต์พุตใหม่เท่านั้น โดยไม่ต้องเล่นบัฟเฟอร์ซ้ำ |
exec_hold_cat |
session_idjob_id |
ดัมพ์บัฟเฟอร์ปัจจุบันและจบด้วย exec_hold_cat_done |
ubus_req |
session_idobject: stringmethod: stringparams: any |
โทรออกรถอูบัส เมื่อ params คือ null จะไม่มีการส่งผ่านอาร์กิวเมนต์ตำแหน่งที่สามไปยัง ubus |
pty_open |
session_idrows: u16cols: u16 |
เปิดเซสชัน PTY ขนาดศูนย์ถอยกลับไปที่ 24×80 |
pty_resize |
session_idrows: u16cols: u16 |
ขนาดของหน้าต่างเทอร์มินัลมีการเปลี่ยนแปลง |
pty_close |
session_id |
ปิดเซสชัน PTY |
android_req |
session_idrequest: AndroidAction |
การทำงานของหน้าจอ Android หนึ่งหน้าจอ |
android_cancel |
session_id |
ยกเลิกคำร้องขอ Android ที่ยังไม่เสร็จสิ้น |
OperatorUp (cli → exchange)
การตรวจสอบความถูกต้องเกิดขึ้นระหว่างการจับมือ WebSocket ดังนั้นจึงไม่มีข้อความเข้าสู่ระบบ
type |
เขตข้อมูล | วัตถุประสงค์ |
|---|---|---|
open_exec |
session_iddevice_idcommandtimeout_secshold (ค่าเริ่มต้นเป็นจริง) hold_timeout_secs? |
ดำเนินการคำสั่งบนอุปกรณ์เป้าหมาย การระงับถูกเปิดใช้งานตามค่าเริ่มต้น hold=true พร้อมระยะหมดเวลา 0 → 3600 วินาที hold=false พร้อมการหมดเวลา 0 → Exchange ค่าเริ่มต้น 60 วินาที |
list_exec_holds |
session_iddevice_id |
ค้นหางานผู้บริหารและการดาวน์โหลด การอัพโหลด และการสืบค้นไดเร็กทอรีที่ใช้งานอยู่ |
list_device_sessions |
session_iddevice_id |
ค้นหาตารางเส้นทาง Exchange โดยตรงโดยไม่ต้องใช้สล็อตเซสชันของอุปกรณ์ |
kill_device_session |
session_iddevice_idtarget_session_id |
ยกเลิกโดย target_session_id ฝั่งอุปกรณ์ และปล่อยเส้นทางในเครื่อง รวมการถือครอง exec และไม่ใช้ช่องพิเศษ |
attach_exec |
session_iddevice_idjob_id |
แนบไปกับงานพักและรับเอาต์พุตต่อ |
exec_hold_kill / exec_hold_tail / exec_hold_cat |
session_iddevice_idjob_id |
ฆ่า / หาง / แมว สำหรับงานค้าง |
detach_session |
session_id |
หยุดการรอในเบื้องหน้าและแยกออกโดยไม่ทำให้กระบวนการระงับหยุดลง |
open_ubus |
session_iddevice_id: stringobject: stringmethod: stringparams: any |
โทรออกรถอูบัส |
open_pty |
session_iddevice_id: stringrows: u16cols: u16 |
เปิดเซสชัน PTY แบบโต้ตอบ |
open_android |
session_iddevice_id: stringrequest: AndroidAction |
เริ่มต้นการทำงานของหน้าจอ Android ห้ามใช้บัญชีแบบอ่านอย่างเดียว |
pty_resize |
session_idrows: u16cols: u16 |
ขนาดของหน้าต่างเทอร์มินัลมีการเปลี่ยนแปลง ไม่มี device_id: มีเซสชันอยู่แล้ว ดังนั้นเซิร์ฟเวอร์จึงสามารถค้นหาอุปกรณ์เป้าหมายได้ |
close_session |
session_id |
สิ้นสุดเซสชันในเชิงรุก: การออก PTY ปกติหรือการขัดจังหวะการดำเนินการคำสั่งของผู้ใช้ |
session_id ในข้อความ open_* ทั้งสี่ข้อความคือ ** จัดสรรโดย CLI เอง** (ดู Session ID Translation ระหว่างสอง Namespaces) อนุญาตให้ใช้เฟรมที่ตามมาทันทีหลังจากการร้องขอโดยไม่ต้องรอการตอบรับ ID
ตัวดำเนินการลง (exchange → cli)
type |
เขตข้อมูล | วัตถุประสงค์ |
|---|---|---|
welcome |
protocol_version: u16server_version: string |
ส่งเชิงรุกโดยเซิร์ฟเวอร์หลังจากสร้างการเชื่อมต่อเพื่อตรวจสอบเวอร์ชันโปรโตคอลที่ตรงกัน |
device_sessions |
session_idsessions: DeviceSessionInfo[]limitcount |
เซสชันฝั่งอุปกรณ์ปัจจุบันใน Exchange |
device_session_killed |
session_idtarget_session_idreleased: bool |
เผยแพร่ในประเทศ เท็จถ้ามันไม่มีอยู่ |
exec_exit |
session_idcode: i32 | nulltimed_out: bool |
คำสั่งสิ้นสุดลง แปลจากข้อความฝั่งอุปกรณ์ที่มีชื่อเดียวกัน |
ubus_resp |
session_idcode: i32data: any |
ผลการโทร ubus |
android_resp |
session_idresult: AndroidResult |
ผลลัพธ์ Android พร้อม ID เซสชันที่แปลเป็นเนมสเปซของตัวดำเนินการ |
pty_closed |
session_idcode: i32 | null |
เซสชัน PTY สิ้นสุดแล้ว |
session_failed |
session_idreason: SessionFailuremessage: string |
เซสชันล้มเหลว reason แยกสาเหตุเพื่อให้ CLI สามารถตัดสินใจได้ว่าการลองใหม่นั้นคุ้มค่าหรือไม่ |
โครงสร้างข้อมูลที่ใช้ร่วมกัน
ExecHoldInfo: job_id, kind (exec / download / upload / file), age_secs, session_id?, command_preview, status, timeout_secs, started_at_unix_ms, exit_code?, timed_out. เพื่อความเข้ากันได้ kind ที่หายไปจะมีค่าเริ่มต้นเป็น exec และไม่มี age_secs ที่มีค่าเริ่มต้นเป็น 0 รายการคำตอบจะคำนวณอายุจากนาฬิกาปัจจุบัน สรุปพาธคำสั่ง/ไฟล์จำกัดอยู่ที่ประมาณ 120 อักขระ งานที่ไม่ใช่ exec รองรับการ kill เท่านั้น ไม่ใช่ tail/cat/attach
DeviceSessionInfo: session_id: u32 (ID ฝั่งอุปกรณ์), operator_session_id: u32, kind: string, hold: bool, age_secs: u64, deadline_unix_ms: u64 Exchange ตอบกลับรายการเซสชันและคำขอการยกเลิกที่บังคับโดยตรงโดยไม่ต้องเปิดเซสชันอุปกรณ์อื่น บัญชีแบบอ่านอย่างเดียวจะได้รับ forbidden
DeviceInfo
ข้อมูลอุปกรณ์แบบคงที่ รายงานหนึ่งครั้งใน register และจัดเก็บไว้ใน devices.info ทุกอย่างยกเว้น agent_version เป็นทางเลือก: แหล่งที่มาส่วนใหญ่จะไม่พร้อมใช้งานเมื่อ agent ทำงานภายนอก OpenWrt เช่น บนเครื่องพัฒนาหรือในคอนเทนเนอร์ การลงทะเบียนควรจะสำเร็จมากกว่าการเริ่มต้นล้มเหลว ช่องที่ขาดหายไปจะถูกละเว้นระหว่างการทำให้เป็นอนุกรม
| สนาม | ประเภท | แหล่งที่มา |
|---|---|---|
hostname |
string? |
/proc/sys/kernel/hostname |
model |
string? |
DISTRIB_TARGET ใน /etc/openwrt_release ถอยกลับไปเป็น model จาก ubus call system board ถ้าไม่พร้อมใช้งาน |
openwrt_version |
string? |
DISTRIB_DESCRIPTION หรือ ubus release.description |
kernel |
string? |
/proc/sys/kernel/osrelease |
arch |
string? |
ค่าคงที่เวลาคอมไพล์ เชื่อถือได้มากกว่าการตรวจจับรันไทม์ |
agent_version |
string |
เวอร์ชัน agent ใช้เพื่อวินิจฉัยเวอร์ชันที่ไม่ตรงกัน |
Metrics
รวบรวมอีกครั้งสำหรับแต่ละฮาร์ตบีต โดยใช้เฉพาะค่าที่มีอยู่โดยตรงจาก /proc โดยไม่ต้องฟอร์กกระบวนการย่อย ความล้มเหลวในการอ่านรายการเดียวไม่ได้ทำให้คอลเลกชันทั้งหมดล้มเหลว: การสูญเสียการวัดหนึ่งรายการจะดีกว่าความล้มเหลวของฮาร์ทบีททั้งหมด และการรายงานอุปกรณ์ออฟไลน์อย่างไม่ถูกต้อง
| สนาม | ประเภท | คำอธิบาย |
|---|---|---|
uptime_secs |
u64 |
/proc/uptime |
load |
[f32; 3] |
โหลดเฉลี่ย 1/5/15 นาทีจาก /proc/loadavg |
mem_total_kb |
u64 |
/proc/meminfo |
mem_available_kb |
u64 |
ใช้ MemAvailable แทนที่จะเป็น MemFree: อย่างหลังไม่รวมแคชที่เรียกคืนได้และประเมินหน่วยความจำที่มีอยู่บนเราเตอร์ต่ำเกินไปอย่างมาก ทำให้เกิดการแจ้งเตือนที่ผิดพลาด |
cpu_usage_pct |
f32? |
คำนวณจากความแตกต่างระหว่างสองตัวอย่าง /proc/stat ละเว้นในการเต้นของหัวใจครั้งแรกเนื่องจากไม่มีตัวอย่างอ้างอิง |
interfaces |
InterfaceStats[] |
/proc/net/dev. รายงาน cumulative รับ/ส่งไบต์ แทนที่จะเป็นอัตรา ปล่อยให้เซิร์ฟเวอร์มีอิสระในการคำนวณอัตราในช่วงเวลาใดก็ได้ ละเว้นเมื่อว่างเปล่า |
InterfaceStats: { name: string, rx_bytes: u64, tx_bytes: u64 }
เฟรมข้อมูลไบนารี
0 1 5 9
+--------+--------------------+--------------------+
| kind | session_id | payload_len | payload (payload_len 字节)
| u8 | u32 (大端) | u32 (大端) |
+--------+--------------------+--------------------+
└──────────── 9 字节定长头 ────────────┘
| ออฟเซ็ต | ความยาว | สนาม | คำอธิบาย |
|---|---|---|---|
| 0 | 1 | kind |
ประเภทสตรีม; ดูด้านล่าง |
| 1 | 4 | session_id |
บิ๊กเอนด์ u32. 0 ถูกสงวนไว้เป็น Sentinel ซึ่งหมายความว่าไม่มีเซสชัน |
| 5 | 4 | payload_len |
Big-endian u32 จำนวนไบต์ของเพย์โหลด สูงสุด 1 MiB |
| 9 | payload_len |
payload |
ไบต์ดิบโดยไม่มีการเข้ารหัสหรือการหลบหนี |
ประเภทสตรีม
| ความคุ้มค่า | ชื่อ | ทิศทาง | คำอธิบาย |
|---|---|---|---|
| 1 | ExecStdout |
อุปกรณ์ → ตัวดำเนินการ | เอาต์พุตมาตรฐานคำสั่ง Remote |
| 2 | ExecStderr |
อุปกรณ์ → ตัวดำเนินการ | ข้อผิดพลาดมาตรฐาน ส่งแยกจาก stdout เพื่อให้ CLI สามารถแสดงผลแตกต่างออกไป เช่น เป็นสีแดง |
| 3 | PtyData |
สองทิศทาง | สตรีมไบต์ PTY ต้นน้ำนำอินพุตคีย์บอร์ด ปลายน้ำนำเอาต์พุตหน้าจอ ประเภทเดียวกันให้บริการทั้งสองทิศทาง |
หาก agent ได้รับ ExecStdout / ExecStderr ดาวน์สตรีม ระบบจะบันทึกคำเตือนและละทิ้งเฟรม ซึ่งบ่งชี้ถึงข้อผิดพลาดในการใช้งานที่เพียร์
หมายเหตุการออกแบบ
**payload_len นั้นซ้ำซ้อนกับฟิลด์ความยาว WebSocket ** มันยังคงอยู่ด้วยเหตุผลสองประการ: การถอดรหัสสามารถตรวจจับการตัดทอน (การประกาศ 11 ไบต์โดยมีเพียง 5 ที่ให้มาจะต้องถูกปฏิเสธ แทนที่จะยอมรับโดยไม่แจ้งให้ทราบว่าเป็นแพ็กเก็ตที่สั้นกว่า) และรูปแบบเฟรมจะยังคงไม่เปลี่ยนแปลงหากจำเป็นต้องใช้ TCP แบบดิบในภายหลัง
ขีดจำกัดเพย์โหลด 1 MiB ไม่ได้มีไว้สำหรับการรับส่งข้อมูลปกติ: PTY และเอาต์พุตคำสั่งเป็นสตรีมส่วนเพิ่มที่เล็กกว่านั้นมาก ป้องกันไม่ให้เพียร์ใช้หน่วยความจำเซิร์ฟเวอร์จนหมดด้วยส่วนหัวที่อ้างว่ามีเพย์โหลด 2 GiB
เพย์โหลด Empty ถูกต้อง: การอ่านแบบศูนย์ไบต์บางครั้งเกิดขึ้นในเซสชัน PTY เฟรมดังกล่าวประกอบด้วยส่วนหัวขนาด 9 ไบต์เพียงอย่างเดียว
เพย์โหลดมีไบต์ที่กำหนดเอง รวมถึงลำดับที่ไม่ใช่ UTF-8 และต้องได้รับการเก็บรักษาไว้อย่างถูกต้อง
ข้อผิดพลาดในการถอดรหัส
| เกิดข้อผิดพลาด | ทริกเกอร์ |
|---|---|
FrameTooShort { len } |
ความยาวรวมต่ำกว่า 9 ไบต์ |
UnknownStreamKind(v) |
ไบต์แรกไม่ใช่ 1/2/3 |
PayloadTooLarge { len } |
ประกาศ payload_len เกิน 1 MiB |
LengthMismatch { declared, actual } |
ความยาวที่ประกาศแตกต่างจากจำนวนไบต์ที่เหลืออยู่จริง |
ความล้มเหลวในการถอดรหัส data-frame ส่งผลต่อเฟรมนั้นเท่านั้น: exchange บันทึกคำเตือนและละทิ้งโดยไม่ยกเลิกการเชื่อมต่อ
การแปลรหัสเซสชันระหว่างสองเนมสเปซ
นี่คือส่วนหนึ่งของโปรโตคอลที่เข้าใจผิดได้ง่ายที่สุด
ในประโยคเดียว
initiator จัดสรร session_id ซึ่งเป็น unique ภายในการเชื่อมต่อของตัวเองเท่านั้น การเชื่อมต่อระหว่างโอเปอเรเตอร์และอุปกรณ์จะรักษาเนมสเปซ ID อิสระ และ exchange จะแปลระหว่างทั้งสอง เซสชันโลจิคัลหนึ่งเซสชันมี ID ที่แตกต่างกันสองรหัส
remote-cli exchange remote-agent
(设备 lab-router)
┌────────────┐ ┌────────────────────┐ ┌────────────┐
│ 运维编号 │ │ 路由表 │ │ 设备编号 │
│ 空间 │ │ │ │ 空间 │
│ │ │ lab-router: │ │ │
│ session 7 │───────▶│ 31 → (op-A, 7) │───────────▶│ session 31 │
│ │◀───────│ │◀───────────│ │
└────────────┘ │ op-A: │ └────────────┘
│ 7 → (lab-router,│
由 CLI 自己的 │ 31) │ 由 exchange
SessionIdGenerator └────────────────────┘ 的 DeviceHandle
分配(1,2,3…) 分配(1,2,3…)
ทำไมต้องออกแบบนี้
**Aหลีกเลี่ยงการเดินทางไปกลับ ** หากเซิร์ฟเวอร์จัดสรร ID ทั้งหมด CLI จะต้องรอการตอบรับโดยแจ้งว่า ID เซสชันคือ 31 ก่อนที่จะส่งอินพุตคีย์บอร์ดหลังจากเปิด PTY การจัดสรร CLI อนุญาตเฟรมข้อมูลทันทีหลังจาก open_pty
**Aหลีกเลี่ยงการชนกันของ ID ระหว่างตัวดำเนินการ ** ตัวดำเนินการสองคนอาจใช้ ID ท้องถิ่น 1 บนอุปกรณ์เดียวกันพร้อมกัน อุปกรณ์จะต้องแยกแยะความแตกต่างเหล่านี้เพื่อหลีกเลี่ยงการส่งเอาต์พุตของบุคคลหนึ่งไปยังอีกบุคคลหนึ่ง exchange จัดสรร ID ฝั่งอุปกรณ์แยกกันสำหรับการเชื่อมต่ออุปกรณ์แต่ละรายการ โดยจับคู่อินสแตนซ์ session 1 สองตัวกับ 31 ฝั่งอุปกรณ์และ 32
**ป้องกันพฤติกรรมของผู้ปฏิบัติงานไม่ให้ส่งผลต่อ ID ฝั่งอุปกรณ์ ** exchange ควบคุม ID ฝั่งอุปกรณ์ทั้งหมด ไม่มีผู้ปฏิบัติงานสามารถระบุหรือคาดเดาได้
สองโต๊ะ
exchange รักษาหนึ่งตารางต่อการเชื่อมต่อ โดยทั้งสองตารางจะอัปเดตร่วมกันเสมอ:
| ที่ตั้ง | ตาราง | คีย์ → ค่า |
|---|---|---|
DeviceHandle.sessions |
โต๊ะข้างหน้าฝั่งอุปกรณ์ | ID เซสชั่นอุปกรณ์ → (ตัวดำเนินการส่ง, ID เซสชั่นของโอเปอเรเตอร์, ID บัญชีของโอเปอเรเตอร์, ประเภทเซสชั่น, ID แถวการตรวจสอบ) |
OperatorHandle.sessions |
ดัชนีย้อนกลับด้านตัวดำเนินการ | รหัสเซสชันของผู้ปฏิบัติงาน → (รหัสอุปกรณ์ รหัสเซสชันอุปกรณ์) |
ดัชนีย้อนกลับช่วยให้สามารถล้างข้อมูลได้: เมื่อผู้ปฏิบัติงานตัดการเชื่อมต่อ เซิร์ฟเวอร์จะต้องค้นหาและยุติเซสชันบนอุปกรณ์ e ทุกเครื่อง ไม่เช่นนั้น กระบวนการและ PTY ที่ไม่มีผู้รับเอาต์พุตจะยังคงอยู่
การแปลเกิดขึ้นที่ไหน
| ทิศทาง | ข้อความ | การแปล |
|---|---|---|
ผู้ดำเนินการ → อุปกรณ์ (open_*) |
ข้อความควบคุม | Allocate ID ใหม่ในเนมสเปซอุปกรณ์ ลงทะเบียนในทั้งสองตาราง และใช้ ID ใหม่ในข้อความที่ส่งไปยังอุปกรณ์ |
ผู้ดำเนินการ → อุปกรณ์ (pty_resize / close_session) |
ข้อความควบคุม | ค้นหารหัสเซสชันของอุปกรณ์ผ่านดัชนีย้อนกลับ แทนที่และส่งต่อ |
| ผู้ประกอบการ → อุปกรณ์ | กรอบข้อมูล | ค้นหารหัสเซสชันของอุปกรณ์ผ่านดัชนีแบบย้อนกลับและเขียนทับไบต์ของส่วนหัวทั้ง 4 ไบต์ โดยไม่เปลี่ยนแปลงเพย์โหลด |
| อุปกรณ์ → ตัวดำเนินการ | ข้อความควบคุม | ค้นหารหัสเซสชันของผู้ปฏิบัติงานและการเชื่อมต่อเป้าหมายในตารางส่งต่อ แทนที่รหัส และส่งมอบ |
| อุปกรณ์ → ตัวดำเนินการ | กรอบข้อมูล | ค้นหารหัสเซสชันของโอเปอเรเตอร์ในตารางไปข้างหน้าและเขียนทับส่วนหัวแทน โดยไม่เปลี่ยนแปลงเพย์โหลด |
การแปลเฟรมข้อมูลคือการเขียนใหม่ only: exchange จะไม่แยกวิเคราะห์ บัฟเฟอร์ หรือเปลี่ยนเพย์โหลดไบต์ใดๆ DataFrame::with_session_id แทนที่ ID ที่มีอยู่โดยไม่ต้องจัดสรรเพย์โหลดใหม่
กฎการจัดสรร
ตัวจัดสรรทั้งสองเพิ่มขึ้นจาก 1 และ skip 0 ซึ่งเป็น no-session sentinel ที่ใช้ใน ตัวอย่างเช่น ในข้อความแสดงข้อผิดพลาดระดับการเชื่อมต่อ
- CLI (
SessionIdGenerator): wraps บนโอเวอร์โฟลว์และข้าม 0 การใช้สี่พันล้านเซสชันในการเชื่อมต่อเดียวนั้นไม่สมจริง แต่การ wraps เหมาะกับกระบวนการที่ใช้เวลานานมากกว่าการตื่นตระหนก - อุปกรณ์ (
DeviceHandle::open_session): หลังจากสรุปแล้ว หาก ID อยู่ในเซสชันสด ให้ดำเนินการต่อไปยังช่องว่างถัดไป
exchange ปฏิเสธ ID ตัวดำเนินการ 0 และการนำ ID เซสชันที่ใช้งานอยู่กลับมาใช้ใหม่ ความถูกต้องของการเชื่อมต่ออุปกรณ์ ขีดจำกัดการทำงานพร้อมกัน และการลงทะเบียนในดัชนีทั้งสองจะได้รับการตรวจสอบภายในส่วนสำคัญเดียวกัน ป้องกันไม่ให้เซสชันที่เปิดพร้อมกันจากผู้ให้บริการหลายรายเกินขีดจำกัด
เมื่อไม่มีเส้นทาง
** ทิ้งอย่างเงียบๆ โดยไม่มีข้อผิดพลาด ** เพียร์ที่ยังคงส่งข้อมูลต่อท้ายหลังจากปิดเซสชันถือเป็นการแข่งขันปกติ สิ่งนี้ใช้ได้กับทั้งสองทิศทาง:
- อุปกรณ์ตอบกลับสำหรับเซสชันที่ไม่มีอยู่อีกต่อไป → บันทึกการแก้ไขข้อบกพร่อง ผู้ดำเนินการอาจตัดการเชื่อมต่อไปแล้ว
- ผู้ดำเนินการส่งข้อมูลสำหรับเซสชันที่ไม่ได้รับอนุญาต → ติดตามบันทึกและละทิ้ง
เส้นทางการล้างข้อมูลเซสชัน
| ทริกเกอร์ | การล้างข้อมูล |
|---|---|
อุปกรณ์สิ้นสุดเซสชันตามปกติ (exec_exit / ubus_resp / android_resp / pty_closed / session_error) |
ลบเส้นทางไปข้างหน้าและดัชนีย้อนกลับของการเชื่อมต่อที่เป็นเจ้าของพร้อมกัน อัปเดตบันทึกการตรวจสอบ และส่งข้อความที่แปลแล้วไปยังผู้ปฏิบัติงาน |
โอเปอเรเตอร์ close_session |
ลบดัชนีทั้งสองออก แจ้งให้การเชื่อมต่ออุปกรณ์เดิมยุติ รับทราบการปิดตัวต่อผู้ปฏิบัติงานทันที และอัปเดตบันทึกการตรวจสอบการยกเลิก การปิดซ้ำไม่ทำอะไรเลย |
| อุปกรณ์ตัดการเชื่อมต่อ | ใช้เซสชันทั้งหมดสำหรับการเชื่อมต่อนั้น ส่ง session_failed / device_disconnected ไปยังผู้ให้บริการแต่ละราย และอัปเดตบันทึกการตรวจสอบด้วย {"outcome":"device_disconnected"} |
| ตัวดำเนินการตัดการเชื่อมต่อ | สำรวจดัชนีย้อนกลับเพื่อค้นหาเซสชันบนอุปกรณ์แต่ละเครื่อง ส่งคำแนะนำในการยุติสำหรับเซสชันที่ไม่ระงับ สำหรับ Hold exec ส่งเฉพาะ exec_detach เพื่อให้กระบวนการทำงานต่อไป ล้างเฉพาะเซสชันของตัวเอง; ผู้ให้บริการรายอื่นบนอุปกรณ์เดียวกันจะไม่ได้รับผลกระทบ |
| อุปกรณ์เชื่อมต่อใหม่และแทนที่การเชื่อมต่อเก่า | ทำให้เซสชันทั้งหมดในการเชื่อมต่อเก่าใช้ไม่ได้ และแจ้งให้ผู้ให้บริการแต่ละรายทราบด้วย device_disconnected |
ดัชนีย้อนกลับเชื่อมโยงกับการเชื่อมต่ออุปกรณ์เฉพาะ จะไม่เปลี่ยนเส้นทางตามรหัสอุปกรณ์ไปยังการเชื่อมต่อใหม่หลังจากการเชื่อมต่อใหม่ การเชื่อมต่อของผู้ให้บริการหลายรายสำหรับบัญชีหนึ่งแต่ละบัญชีเป็นเจ้าของดัชนี ดังนั้นการยกเลิกการเชื่อมต่อจะไม่ยกเลิกเซสชันของการเชื่อมต่ออื่น
การรับทราบสำหรับการยกเลิกอย่างชัดเจน: exec ส่งคืน exec_exit (code: null, timed_out: false), PTY ส่งคืน pty_closed (code: null) และ ubus ส่งคืน session_failed (reason: device_error) นี่เป็นการยืนยันว่า exchange ได้ปิดเซสชันแล้ว ไม่ใช่ว่ากระบวนการระยะไกลได้ออกจากไปแล้ว ข้อมูลต่อท้ายและข้อความออกที่ตามมาจากอุปกรณ์จะถูกละทิ้ง
ที่ขีดจำกัดการทำงานพร้อมกัน Exchange จะเรียกคืนเส้นทางที่ตัวดำเนินการหายไปหรือเกินกำหนดเวลาภายใต้การล็อกเซสชันเดียวกันก่อน จากนั้นจึงตรวจสอบขีดจำกัดอีกครั้ง กำหนดเวลาของผู้บริหารคือขีดจำกัดเวลาจัดส่งจริงบวกกับระยะเวลาผ่อนผัน 15 วินาที ไฟล์ใช้เวลา 3600 วินาทีบวก 15 วินาที ประเภทอื่นใช้งานได้ 24 ชม. Hold exec ภายในกำหนดเวลาจะไม่ถูกฆ่าเมื่อผู้ปฏิบัติงานออกไป การเรียกคืน/อย่างชัดเจน kill_device_session จะส่งการยกเลิกที่เกี่ยวข้องไปยังอุปกรณ์และลบดัชนีทั้งสองออกทันทีโดยไม่ต้องรอการตอบสนองของอุปกรณ์ ตัวดำเนินการที่พร้อมใช้งานจะได้รับ exec_exit (timed_out:true) สำหรับ exec หรือ session_failed (device_error, session reclaimed) สำหรับประเภทอื่น การอัปเดตการตรวจสอบเป็นแบบอะซิงโครนัสและพยายามอย่างเต็มที่
การเชื่อมต่ออุปกรณ์ใหม่และยุค
การเชื่อมต่ออุปกรณ์แต่ละรายการจะได้รับ epoch (UUID) แบบสุ่มระหว่างการลงทะเบียน เมื่ออุปกรณ์เดียวกันเชื่อมต่อใหม่ ** การเชื่อมต่อใหม่จะแทนที่อันเก่า** แทนที่จะถูกปฏิเสธ เป็นไปไม่ได้ที่จะแยกแยะการเชื่อมต่อที่ซ้ำกันจากการเชื่อมต่อที่ไม่ทำงานซึ่งเซิร์ฟเวอร์ตรวจไม่พบ (การเชื่อมต่อแบบเปิดครึ่งหนึ่ง) ได้อย่างน่าเชื่อถือ การปฏิเสธอันใหม่จะป้องกันการเชื่อมต่อใหม่จนกว่าอันเก่าจะหมดเวลา
การล้างข้อมูลสำหรับการเชื่อมต่อเก่าอาจดำเนินการลงทะเบียน after ของการเชื่อมต่อใหม่ การยกเลิกการลงทะเบียนจึงต้องตรวจสอบยุค: ลบรายการตารางการเชื่อมต่อเฉพาะเมื่อยุคปัจจุบันตรงกับการเชื่อมต่อที่กำลังล้างข้อมูล หากไม่มีการตรวจสอบนี้ การล้างข้อมูลการเชื่อมต่อเก่าจะทำเครื่องหมายอุปกรณ์ที่เชื่อมต่อใหม่เป็นออฟไลน์ทันที
ลำดับการจับมือ
ด้านอุปกรณ์
remote-agent exchange
│ │
│ ① WebSocket 握手(wss://host/ws/device) │
│────────────────────────────────────────────────▶│
│◀────────────────────────────────────────────────│
│ 101 Switching Protocols │
│ │
│ ┌──────┴──────┐
│ │ 启动 15 秒 │
│ │ 注册计时器 │
│ └──────┬──────┘
│ ② {"type":"register","protocol_version":1, │
│ "device_id":"…","token":"…","info":{…}} │
│────────────────────────────────────────────────▶│
│ │
│ ┌──────────────────┴──────────────────┐
│ │ a. 比对 protocol_version │
│ │ b. split_token → 前缀查 devices 表 │
│ │ c. 校验 enabled │
│ │ d. argon2 verify(前缀不符也照做, │
│ │ 避免用耗时区分前缀是否存在) │
│ └──────────────────┬──────────────────┘
│ │
│ ③ {"type":"register_ack","ok":true, │
│ "error":null,"heartbeat_interval_secs":30} │
│◀────────────────────────────────────────────────│
│ │
│ ┌──────┴──────┐
│ │ 登记连接表 │
│ │ 顶替旧连接 │
│ │ 回写 info 与│
│ │ last_seen_at│
│ └──────┬──────┘
│ │
│ ④ 退避曲线复位,写状态文件 state=connected │
│ │
│══════════════ 转发循环 ═══════════════════════════│
│ │
│ heartbeat(每 30 秒,携带 Metrics) │
│────────────────────────────────────────────────▶│
│ WebSocket Ping(随每次 heartbeat 发出) │
│────────────────────────────────────────────────▶│
│ WebSocket Pong
│◀────────────────────────────────────────────────│
│ WebSocket Ping(每 30 秒)
│◀────────────────────────────────────────────────│
│ Pong(tungstenite 排队,agent 显式 flush) │
│────────────────────────────────────────────────▶│
เส้นทางความล้มเหลว:
- เฟรมแรกไม่ใช่ข้อความ เป็น JSON ไม่ถูกต้อง หรือไม่ใช่
register→ บันทึกคำเตือนและปิดทันทีโดยไม่มีแอก - ไม่มี
registerภายใน 15 วินาที → ปิด - เวอร์ชันไม่ตรงกัน → ส่งคืน
register_ack{ok:false, error:"协议版本不匹配:设备为 X,服务端为 Y"}และปิด - โทเค็นไม่ถูกต้อง / อุปกรณ์ไม่มีอยู่ / อุปกรณ์ที่ปิดใช้งาน → ส่งคืน
"设备 ID 或令牌无效"เดิมเสมอและปิด อย่าแยกแยะสาเหตุ: การทำเช่นนั้นจะทำให้สามารถนับรายการอุปกรณ์ได้
agent จัดการหมวดหมู่ความล้มเหลวทั้งสองนี้แยกกัน:
| หมวด agent | ทริกเกอร์ | กลยุทธ์การเชื่อมต่อใหม่ |
|---|---|---|
Rejected |
รับ ack ด้วย ok: false |
แก้ไขการรอ 300 วินาที การลองอีกครั้งไม่สามารถแก้ไขโทเค็นที่ไม่ถูกต้องได้ ลองใหม่บ่อยครั้งโดยสิ้นเปลืองทรัพยากรทั้งสองด้านและบันทึกเซิร์ฟเวอร์ท่วม การรอที่นานกว่าจะทำให้ผู้อื่นมีเวลาอัปเดตการกำหนดค่า |
Transport |
การเชื่อมต่อล้มเหลว หมดเวลาการจับมือ (45 วินาที) การตอบสนองมีรูปแบบไม่ถูกต้อง การเชื่อมต่อถูกขัดจังหวะ | Exponential Backoff: เริ่มต้นที่ 1 วินาที เพิ่มเป็นสองเท่าในแต่ละครั้งสูงสุด 300 วินาที คูณด้วยความกระวนกระวายใจแบบสุ่มใน [0.5, 1.5) |
**Backoff จะรีเซ็ตหลังจากการลงทะเบียนสำเร็จแล้วเท่านั้น ** ไม่ใช่เฉพาะการสร้าง TCP เท่านั้น มิฉะนั้น การปฏิเสธทันทีหลังจากการเชื่อมต่อจะลดลงเป็นการลองใหม่อย่างหนาแน่นในช่วงเวลามิลลิวินาที
**Jitter เป็นสิ่งจำเป็น ** เมื่อเซิร์ฟเวอร์รีสตาร์ท อุปกรณ์หลายร้อยหรือหลายพันเครื่องจะตรวจพบการตัดการเชื่อมต่อพร้อมกัน ด้วยกราฟการลองซ้ำตามที่กำหนดที่เหมือนกัน พวกมันยังคงซิงโครไนซ์และโจมตีเซิร์ฟเวอร์ที่เพิ่งเริ่มต้นใหม่เป็นระลอก เปลี่ยนการรีสตาร์ทเป็นความล้มเหลวแบบเรียงซ้อนอย่างต่อเนื่อง
ฝั่งโอเปอเรเตอร์
remote-cli exchange
│ │
│ ① WebSocket 握手(wss://host/ws/operator) │
│ Authorization: Bearer <prefix>.<secret> │
│────────────────────────────────────────────────▶│
│ │
│ ┌──────────────────┴──────────────────┐
│ │ 在 upgrade 之前完成鉴权: │
│ │ split_token → 前缀查 operators 表 │
│ │ → argon2 verify → 校验 enabled │
│ └──────────────────┬──────────────────┘
│ │
│ 鉴权失败 → HTTP 401「凭证无效」(不升级) │
│◀────────────────────────────────────────────────│
│ │
│◀────────────────────────────────────────────────│
│ 101 Switching Protocols │
│ │
│ ② {"type":"welcome","protocol_version":1, │
│ "server_version":"0.1.0"} │
│◀────────────────────────────────────────────────│
│ │
│ ③ 比对 protocol_version(15 秒超时) │
│ 不一致 → 报错退出,提示升级 CLI │
│ │
│══════════════ 可以发起会话 ═══════════════════════│
│ │
│ {"type":"open_pty","session_id":1, │
│ "device_id":"lab-router","rows":40,"cols":120}│
│────────────────────────────────────────────────▶│
│ │──▶ 查设备在线
│ │──▶ 查并发上限
│ │──▶ 查 readonly 角色
│ │──▶ 写审计(先于下发)
│ │──▶ 分配设备侧编号
│ 二进制帧(PtyData, session 1, 键盘输入) │
│────────────────────────────────────────────────▶│──▶ 改写为设备侧编号
│ │
│ 二进制帧(PtyData, session 1, 屏幕输出) │
│◀────────────────────────────────────────────────│◀── 改写为运维侧编号
CLI สามารถส่งเฟรมข้อมูลถัดไป ** โดยไม่ต้องรอการตอบรับใดๆ** นี่เป็นสาเหตุที่แน่ชัดว่าเหตุใดผู้ริเริ่มจึงจัดสรร ID เซสชัน
หมวดหมู่การจัดการข้อผิดพลาดและความล้มเหลว
SessionFailure
ใช้ในฟิลด์ reason ของ session_failed; ค่า JSON ใช้ Snake_case
| ความคุ้มค่า | ความหมาย | ผู้ผลิต | ลองใหม่ได้ |
|---|---|---|---|
device_offline |
ไม่มีอุปกรณ์อยู่หรือออฟไลน์อยู่ในขณะนี้ | exchange ไม่พบอุปกรณ์ในตารางการเชื่อมต่อระหว่าง open_* |
✅ |
device_disconnected |
อุปกรณ์ถูกตัดการเชื่อมต่อระหว่างเซสชัน | อุปกรณ์ถูกตัดการเชื่อมต่อ / แทนที่ด้วยการเชื่อมต่อใหม่ / คิวการส่งปิดไปแล้วระหว่างการจัดส่ง | ✅ |
device_error |
อุปกรณ์ปฏิเสธคำขอหรือการดำเนินการล้มเหลว | agent ส่งคืน session_error (การสร้าง PTY การเริ่มต้นคำสั่ง หรือการเรียก ubus ล้มเหลว) หรือเพียร์กินช้าเกินไปในระหว่างการส่งต่อ |
❌ |
rate_limited |
เกินขีดจำกัดเซสชันที่เกิดขึ้นพร้อมกัน | exchange ค้นหาเซสชันที่ใช้งานของอุปกรณ์ถึง --max-sessions-per-device (ค่าเริ่มต้น 32) ระหว่าง open_* |
✅ |
forbidden |
ข้อมูลประจำตัวของผู้ให้บริการไม่สามารถเข้าถึงอุปกรณ์ได้ | บัญชีแบบอ่านอย่างเดียว (role = readonly) พยายามเริ่มต้นเซสชันใดๆ |
❌ |
is_retryable() จงใจยกเว้น forbidden และ device_error: การลองปัญหาการอนุญาตอีกครั้งหรือข้อผิดพลาดของอุปกรณ์ภายในไม่มีจุดหมายและทำให้เกิดกิจกรรม CLI ที่ไม่จำเป็นเท่านั้น
CLI ผนวกคำใบ้ที่สามารถดำเนินการได้ต่อท้าย message ดั้งเดิม ตัวอย่างเช่น device_offline แนะนำให้ตรวจสอบสถานะอุปกรณ์ด้วย remote-cli device list device_error เป็นข้อยกเว้น: ข้อผิดพลาดของอุปกรณ์เองเป็นคำอธิบายที่ถูกต้องที่สุด และไม่ควรเจือจางด้วยแผ่นสำเร็จรูป
ความหมายความล้มเหลวตามเลเยอร์
| เลเยอร์ | พฤติกรรม | ขอบเขต |
|---|---|---|
| การถอดรหัสเฟรมล้มเหลว | เข้าสู่ระบบและทิ้งเฟรม | หนึ่งเฟรม |
| ควบคุมการแยกวิเคราะห์ข้อความล้มเหลว | เตือนและเพิกเฉยต่อข้อความ | หนึ่งข้อความ; การเชื่อมต่อดำเนินต่อไป |
| ความล้มเหลวระดับเซสชัน | session_failed / session_error |
หนึ่งเซสชั่น; การเชื่อมต่อดำเนินต่อไป |
| การลงทะเบียนล้มเหลว | ปิดหลัง register_ack{ok:false} |
การเชื่อมต่อทั้งหมด |
| เวอร์ชันไม่ตรงกัน | ปิดหลังจากข้อความแสดงข้อผิดพลาดที่ชัดเจน | การเชื่อมต่อทั้งหมด |
| คิวล่าช้า | ปิดการเชื่อมต่อเชิงรุก | การเชื่อมต่อทั้งหมด |
หลักการ: รักษาความล้มเหลวในเครื่องทุกครั้งที่เป็นไปได้ เนื่องจากการเชื่อมต่ออุปกรณ์อาจมีเซสชันพร้อมกันสำหรับผู้ให้บริการหลายราย
Keepalive และหมดเวลา
| กลไก | ความคุ้มค่า | คำอธิบาย |
|---|---|---|
| exchange WebSocket ปิง | ทุก ๆ --heartbeat-secs (ค่าเริ่มต้น 30 วินาที) |
exchange ส่งไปยังทั้งอุปกรณ์และผู้ให้บริการ ไลบรารีไคลเอนต์จัดคิว Pong โดยอัตโนมัติ agent ต้องล้าง sink อย่างชัดเจนหลังจากได้รับ Ping เพื่อส่ง Pong ทันที |
| agent WebSocket ปิง | ทุก heartbeat_interval_secs (มีให้ในเซิร์ฟเวอร์ ack) |
agent ส่ง Ping ดั้งเดิมเพิ่มเติมจาก JSON heartbeat แต่ละตัว หลีกเลี่ยงขอบเขตการยกเลิกการเชื่อมต่อที่ไม่ได้ใช้งานประมาณ 100 วินาทีของ Cloudflare หาก Ping ของ exchange ล่าช้า |
| หมดเวลาความเงียบของการเชื่อมต่อ | heartbeat × --missed-pings (ค่าเริ่มต้น 30 × 3 = 90 วินาที) |
การเชื่อมต่ออุปกรณ์หรือผู้ให้บริการที่ไม่มีข้อความขาเข้าในช่วงเวลานี้จะถือว่าสูญหายและถูกตัดการเชื่อมต่อ โป่งถือเป็นกิจกรรม --missed-pings ตั้งค่าเป็น 0 ย้อนกลับไปที่ช่วงการเต้นของหัวใจอย่างน้อยหนึ่งช่วง |
| หมดเวลาการเขียน exchange | 10 วินาที | ข้อมูล WebSocket ใด ๆ หรือการหมดเวลาการเขียน Ping จะสิ้นสุดการเชื่อมต่อ ป้องกันไม่ให้การเชื่อมต่อที่ช้าบล็อกการล้างข้อมูล |
| การเต้นของหัวใจธุรกิจ | ทุก heartbeat_interval_secs (มีให้ในเซิร์ฟเวอร์ ack) |
agent ส่ง JSON heartbeat พร้อมด้วย Metrics และ WebSocket Ping แยกต่างหาก การรายงานตัวชี้วัดและปิงปอง/ปิงปองดั้งเดิมมีจุดประสงค์ที่แยกจากกัน จังหวะทางธุรกิจเพียงอย่างเดียวไม่สามารถระบุสถานะการเชื่อมต่อได้ |
| กิจกรรมออนไลน์/ออฟไลน์ | tick_count สำหรับทริกเกอร์การแจ้งเตือน online / offline (ค่าเริ่มต้น 3, ช่วง 1–100); การออกอากาศ/การตรวจสอบทางเลือก REMOTE_MISSED_THRESHOLD / REMOTE_ONLINE_THRESHOLD |
ผู้ปฏิบัติงานกำหนดค่าทริกเกอร์สองตัว แทนที่จะนับสองครั้งในทริกเกอร์ตัวเดียว offline แจ้งเตือนหลังจาก tick_count พลาดติดต่อกัน online แจ้งเตือนหลังจาก tick_count สำเร็จติดต่อกัน device_ids ของทริกเกอร์แต่ละตัวสามารถเป็น ID อุปกรณ์ (สตริง), ชุด ID (อาร์เรย์) หรือละไว้ / null สำหรับอุปกรณ์พื้นที่ทำงานทั้งหมด ทริกเกอร์ไม่แจ้งเตือนซ้ำในขณะที่อยู่ในสถานะรายงานแล้ว ก่อนอื่นจะต้องทำการจัดเรียงใหม่ผ่านผลลัพธ์ตรงกันข้ามที่ติดต่อกันของ tick_count การออกอากาศสถานะและการตรวจสอบสถานะในกระบวนการไม่อ่านทริกเกอร์เหล่านี้และถอยกลับไปเป็น REMOTE_MISSED_THRESHOLD / REMOTE_ONLINE_THRESHOLD (ยังมีค่าเริ่มต้นเป็น 3) ซ็อกเก็ตจะยังคงอยู่ในตารางการเชื่อมต่อหรือไม่นั้นจะถูกกำหนดแยกต่างหากโดย --missed-pings |
| หมดเวลาการลงทะเบียนอุปกรณ์ | 15 วินาที | ต้องส่ง register ภายในช่วงเวลานี้หลังจากสร้างการเชื่อมต่อ |
| หมดเวลาการจับมือ agent | 45 วินาทีต่อเฟส (HANDSHAKE_TIMEOUT) |
connect_device_ws (TCP, ตัวเลือก HTTP CONNECT, TLS, WebSocket handshake) และรอ register_ack หลังจากส่ง register แต่ละรายการจะได้รับขีดจำกัดนี้ 45 วินาทีไม่ใช่ขีดจำกัดรวม |
| agent เขียน / หมดเวลาล้าง Pong | 30 วินาที (WRITE_TIMEOUT) |
การส่งแต่ละครั้ง (การลงทะเบียน, ฮาร์ทบีท JSON, Ping, เอาต์พุตเซสชัน) และ Pong flush หลังการลงทะเบียนแต่ละครั้งจะมีกำหนดเวลาของตัวเอง การเขียนล้มเหลวหรือการหมดเวลาจะสิ้นสุดการเชื่อมต่อและทริกเกอร์การเชื่อมต่อใหม่ โป่งฟลัชขณะรอการลงทะเบียนจะถูกควบคุมโดยการรอ 45 วินาทีด้านบน |
| agent TCP Keepalive | ตรวจสอบหลังจากไม่ได้ใช้งานเป็นเวลา 30 วินาที | เปิดใช้งาน TCP_NODELAY และ TCP Keepalive หลังจากการเชื่อมต่อโดยตรงหรือ HTTP CONNECT การตั้งค่า TCP ไม่ได้แทนที่ WebSocket Ping/Pong |
| หมดเวลาต้อนรับ CLI | 15 วินาที | เวลาที่ CLI รอ welcome หลังจากการเชื่อมต่อ |
| หมดเวลาดำเนินการคำสั่ง | ระบุโดย timeout_secs ใน open_exec |
agent หนีบไว้อย่างน้อย 1 วินาที เมื่อหมดเวลา SIGTERM กลุ่มกระบวนการทั้งหมด จากนั้น SIGKILL หลังจากช่วงเวลาผ่อนผัน 3 วินาที |
| หมดเวลาการโทรของ ubus | 15 วินาที (คงที่ภายใน agent) | ubus คือ RPC ท้องถิ่น โดยทั่วไปแล้วการเกินช่วงเวลานี้บ่งชี้ว่าบริการที่เกี่ยวข้องนั้นค้างอยู่ |
ไคลเอ็นต์เชลล์ที่ไม่ได้ใช้งานยังคงตอบกลับด้วย Pong agent ล้างการตอบสนองโดยอัตโนมัติซึ่งจัดคิวโดยไลบรารีอย่างชัดเจน โดยไม่ต้องรอจังหวะการเต้นของหัวใจทางธุรกิจครั้งถัดไป ดังนั้น การไม่มีอินพุตคีย์บอร์ดจึงไม่ทำให้เกิดการตัดการเชื่อมต่อที่ผิดพลาด (#42) --heartbeat-secs ต้องมากกว่า 0; ค่าที่ไม่ถูกต้องจะถูกปฏิเสธในระหว่างการแยกวิเคราะห์อาร์กิวเมนต์เริ่มต้น
แรงดันย้อนกลับ
exchange รักษาคิวการส่งแบบมีขอบเขตของข้อความ 512 สำหรับการเชื่อมต่อแต่ละรายการ
คิวเต็มหมายความว่าเพียร์ไม่สามารถตามได้ โดยปกติแล้วเนื่องจากการเชื่อมต่อ PTY หยุดทำงานหรือไคลเอนต์ถูกระงับ การตอบสนองคือ ** ยกเลิกการเชื่อมต่อนั้นในเชิงรุก** แทนที่จะเก็บบัฟเฟอร์ไว้: การบัฟเฟอร์ที่ไม่จำกัดจะทำให้ไคลเอ็นต์ที่ค้างอยู่หนึ่งเครื่องกลายเป็นหน่วยความจำรั่วซึ่งส่งผลต่อบริการทั้งหมด ข้อความ 512 มีพื้นที่ว่างเพียงพอสำหรับการรับส่งข้อมูล PTY แบบโต้ตอบ (โดยปกติความยาวของคิวจะเป็นตัวเลขหลักเดียว) โดยทั่วไปการถึงขีดจำกัดหมายความว่าเพื่อนหยุดบริโภคแล้ว
การนำส่งใช้ try_send ที่ไม่ปิดกั้นเสมอ เส้นทางการเราต์มีการล็อคตารางการเชื่อมต่อ await ใดๆ จะผูกระยะเวลาการล็อกกับความเร็วในการใช้เพียร์ ซึ่งช่วยให้ไคลเอ็นต์ที่ช้าสามารถหยุดทั้งตารางได้
ความล้มเหลวในการจัดส่งทั้งสองรายการได้รับการจัดการที่แตกต่างกัน:
| ผลลัพธ์ | ความหมาย | การจัดการ |
|---|---|---|
Lagging |
คิวเต็ม. | ขัดจังหวะการเชื่อมต่อโดยใช้สัญญาณปิดที่แยกจากกัน ทำความสะอาดเซสชัน และใช้ความพยายามอย่างดีที่สุดเพื่อแจ้งจุดสิ้นสุดอื่น ๆ ของความล้มเหลว |
Closed |
รีซีฟเวอร์ออกแล้ว | การยกเลิกการเชื่อมต่อตามปกติ บันทึกการแก้ไขข้อบกพร่องเท่านั้น |
สัญญาณการปิดการเชื่อมต่อจะข้ามคิวการส่ง ช่วยให้สามารถล้างข้อมูลได้แม้ว่าคิวจะเต็มหรือการเขียนถูกบล็อกก็ตาม การเปลี่ยนอุปกรณ์หลังจากเชื่อมต่อใหม่และการหมุนโทเค็นจะใช้กลไกเดียวกัน
agent ยังมีคิวขาออกที่มีความจุ 256 คิวเต็มหมายความว่าเครือข่ายไม่สามารถส่งข้อมูลได้เลย ดังนั้นการสะสมเพิ่มเติมจึงไม่มีประโยชน์ ตัดการเชื่อมต่อและเชื่อมต่อใหม่ เมื่อคิวอินพุต PTY (64) เต็ม การกดแป้นพิมพ์จะถูกละทิ้งไป: การสะสมอินพุตโต้ตอบขนาดเล็กนั้นไม่ได้มีวัตถุประสงค์
การควบคุมหน้าจอ Android
กำหนดใน crates/proto/src/android.rs โดยใช้การกำหนดเส้นทางเซสชันสองเนมสเปซที่มีอยู่ การรับรองความถูกต้อง ขีดจำกัดการทำงานพร้อมกัน และการตรวจสอบ request ใน open_android / android_req เป็นอ็อบเจ็กต์ JSON ที่ถูกแยกโดย action:
action |
สาขาอื่นๆ | พฤติกรรม |
|---|---|---|
status |
ไม่มี | ค้นหาการอนุญาตท้องถิ่นและเวอร์ชัน SDK ไม่จำเป็นต้องเปิดใช้งานสวิตช์ควบคุมหน้าจอ แต่ต้องเรียกใช้บริการระยะไกล |
tree |
ไม่มี | โครงสร้างโหนดการเข้าถึงของแอปที่ใช้งานอยู่ในปัจจุบัน |
screenshot |
ไม่มี | ภาพหน้าจอ JPEG หนึ่งภาพ |
tap |
x, y: u32 |
แตะพิกัดพิกเซลของหน้าจอดั้งเดิม |
swipe |
x1, y1, x2, y2, duration_ms: u32 |
ปัดนิ้ว ระยะเวลา 50–5000 ms |
swipe_path |
points: [{x, y}] (2–64 จุด), duration_ms: u32 |
การปัดแบบหลายส่วน: การกดเพียงครั้งเดียวจะเคลื่อนที่ผ่านทุกจุดอย่างต่อเนื่อง โดยสอดแทรกตามความยาวอย่างสม่ำเสมอ ระยะเวลา 50–5000 ms |
global |
key: string |
back / home / recents / notifications / quick_settings |
input_text |
text: string |
แทนที่ฟิลด์ที่แก้ไขได้ซึ่งมีขนาดสูงสุด 8192 UTF-8 ไบต์ ไม่มี NUL |
AndroidResult ถูกเลือกปฏิบัติโดย kind; ต้องระบุข้อมูลทุกช่อง:
kind |
เขตข้อมูล |
|---|---|
status |
controls_enabled, accessibility_enabled, screen_capture_enabled: bool; sdk: u32 |
tree |
nodes: AndroidNode[]; screen_width, screen_height: u32; truncated: bool |
screenshot |
mime_type: "image/jpeg"; data_base64: string; width, height, screen_width, screen_height: u32 |
ok |
ไม่มี; บ่งบอกถึงความสมบูรณ์ของการป้อนข้อมูล ท่าทาง หรือการทำงานของระบบ |
โหนดประกอบด้วยสตริง class_name, package, view_id, text, description, bounds: [left, top, right, bottom] (พิกัดพิกเซลของหน้าจอที่ลงนาม) และบูลีน clickable, editable, focused, password สูงสุด 200 โหนด โดยแต่ละสตริงจำกัดไว้ที่ 256 UTF-16 ยูนิต truncated ระบุเฉพาะขีดจำกัดของโหนด ค่ารหัสผ่านและคำอธิบายว่างเปล่า และละเว้นแผนผังย่อยการกำหนดค่าของ agent เอง
ขอบที่ยาวที่สุดของภาพหน้าจอจำกัดไว้ที่ 1600 พิกเซล width/height คือขนาดภาพ screen_width/screen_height คือขนาดก่อนการปรับขนาด พิกัดท่าทางใช้ขนาดดั้งเดิม JSON ที่ส่งคืนโดย Java Bridge ถูกจำกัดไว้ที่ 2 MiB โดยอนุญาตให้มีไบต์เพิ่มเติม 1024 ไบต์สำหรับซองจดหมาย WebSocket ไม่มีการเพิ่มประเภทเฟรมไบนารีใหม่
{"type":"open_android","session_id":1,"device_id":"phone","request":{"action":"input_text","text":"Hello\nWorld"}}
{"type":"android_req","session_id":31,"request":{"action":"input_text","text":"Hello\nWorld"}}
{"type":"android_resp","session_id":31,"result":{"kind":"ok"}}
{"type":"android_resp","session_id":1,"result":{"kind":"ok"}}
screen_capture_enabled บ่งชี้ถึงการอนุญาตภาพหน้าจอที่มีอยู่ในปัจจุบัน (MediaProjection หรือการเข้าถึง Android 11+) การใช้งานหน้าจอจริงทั้งหมดยังต้องใช้ controls_enabled สิทธิ์ไม่เพียงพอ หน้าต่างที่ไม่สามารถเข้าถึงได้ และการดำเนินการที่ล้มเหลวจะส่งคืน agent session_error ซึ่งแปลโดย Exchange เป็น session_failed เอเจนต์ที่ไม่ใช่ Android ส่งคืนข้อผิดพลาดที่ไม่สนับสนุนอย่างชัดเจน
Agent จัดการคำขอ Android ได้สูงสุด 4 รายการพร้อมกัน และเข้ารหัสภาพหน้าจอเพียงครั้งละหนึ่งรายการเท่านั้น การดำเนินการ Java รอสูงสุด 10 วินาที (สูงสุด 5 วินาทีสำหรับเฟรมหน้าจอ) การหมดเวลาคำขอ agent คือ 15 วินาที, การหมดเวลาของ CLI 20 วินาที ในการยกเลิกหรือยกเลิกการเชื่อมต่อของผู้ดำเนินการ Exchange จะส่ง android_cancel; agent ปิดซ็อกเก็ตในเครื่องและ APK จะยกเลิกงานที่อยู่ในคิวซึ่งยังไม่ได้ทำงาน ท่าทางหรือการเปลี่ยนแปลงข้อความที่เสร็จสมบูรณ์จะไม่ถูกย้อนกลับ
การดำเนินการตรวจสอบคือ android; รายละเอียดเก็บการกระทำและพิกัด input_text เก็บเฉพาะ characters (จำนวนอักขระ Unicode) และผลลัพธ์ที่สำเร็จจะบันทึกเฉพาะ outcome: "completed" เท่านั้น เนื้อหาข้อความ แผนผัง UI และรูปภาพไม่ได้ถูกเขียนลงในบันทึกการตรวจสอบ บัญชี readonly ไม่สามารถเริ่มต้นได้แม้แต่ status, tree หรือภาพหน้าจอ
ส่วนขยายเซสชันของไฟล์
เอเจนต์ที่สนับสนุนการดำเนินการกับไฟล์โฆษณาความสามารถ files Exchange แปล open_file {session_id, device_id, request} เป็น file_req {session_id, request} request.action คือ list, download หรือ upload; คำจำกัดความของฟิลด์อยู่ใน crates/proto/src/files.rs
ข้อความควบคุมแบบสองทิศทางใช้ JSON: ตัวควบคุมจะส่ง file_input {session_id, input} และอุปกรณ์ส่งคืน file_event {session_id, event} input.kind คือ chunk {offset, data}, ack {offset} หรือ finish; event.kind คือ ready {size}, chunk {offset, data}, progress {received}, complete {size} หรือ list {path, entries, truncated} เนื้อหาแบบก้อนใช้ Base64 มาตรฐาน แต่ละก้อนมีขนาดสูงสุด 64 KiB และผู้ส่งจะรอการตอบรับก่อนดำเนินการต่อ
ready เริ่มต้นของการดาวน์โหลดต้องใช้ ack {offset: 0}; ส่วนต่อมาจะรับทราบจำนวนไบต์สะสม การอัปโหลดเริ่มต้นด้วย ready และ progress แต่ละตัวจะขับเคลื่อนส่วนถัดไป หลังจากส่งทุกอย่างแล้ว finish จะคอมมิตมัน complete และ list เป็นข้อความเทอร์มินัล ข้อผิดพลาดใช้ session_error / session_failed ที่มีอยู่ close_session ส่ง file_cancel; การยกเลิกและการตัดการเชื่อมต่อทั้งลบเส้นทางเซสชัน ข้อความไฟล์จะถูกส่งต่อเฉพาะเซสชันไฟล์ โดยคงการแยกระหว่างรหัสโอเปอเรเตอร์และเซสชันอุปกรณ์
เวอร์ชันโปรโตคอลยังคงเป็น 1; ข้อความใหม่จำเป็นต้องมีการอัพเกรด Exchange และ Agent ที่ตรงกัน สำหรับขีดจำกัดการทำงานและอินเทอร์เฟซ LAN โปรดดูที่ การถ่ายโอนไฟล์และการเชื่อมต่อ LAN โดยตรง
ส่วนขยายหลายแพลตฟอร์ม
เวอร์ชันโปรโตคอลยังคงอยู่ 1. การลงทะเบียน info เพิ่มตัวเลือก platform (openwrt / linux / freebsd / netbsd / windows / android / macos) os_version และ capabilities (exec / pty / ubus / android_control / files / sensors / net) OpenWrt รายงาน openwrt_version
Heartbeat metrics.unavailable เป็นอาร์เรย์สตริงทางเลือกที่ระบุเมทริก uptime, load, memory, cpu และ interfaces ที่ไม่พร้อมใช้งานในปัจจุบัน หากไม่มี ให้แยกวิเคราะห์โดยใช้รูปแบบเก่า ค่าที่สอดคล้องกับเมตริกที่แสดงเป็นเพียงตัวยึดตำแหน่งที่เข้ากันได้เท่านั้น ลูกค้าควรแสดงว่าไม่พร้อมใช้งาน Windows ไม่ได้จัดเตรียมค่าเฉลี่ยโหลด Unix และ Android อาจจำกัดแหล่งที่มาของ /proc บางส่วนผ่านการอนุญาตของระบบ
คำสั่งถูกตีความโดยเชลล์ของอุปกรณ์เป้าหมาย: /bin/sh บน Linux/OpenWrt/macOS, PowerShell บน Windows และ /system/bin/sh บน Android รูปแบบเฟรม PTY และข้อความควบคุมเหมือนกันในทั้งสามระบบ Windows ใช้ ConPTY อุปกรณ์ที่ไม่ใช่ OpenWrt ส่งคืน session_error สำหรับคำขอ ubus
เซสชันที่ปิดผนึกตั้งแต่ต้นทางถึงปลายทาง
เวอร์ชันโปรโตคอลยังคงอยู่ 1. ข้อความใหม่: OperatorUp open_sealed {session_id, device_id, hello}, sealed_input {session_id, packet}; อุปกรณ์ลง sealed_open {session_id, hello}, sealed_input, sealed_close {session_id, detach}; อุปกรณ์ขึ้น / ตัวดำเนินการลง sealed_accept {session_id, accept}, sealed_event {session_id, packet, last} เฟรมไบนารีใหม่ประเภท 4 = Sealed (เพย์โหลด counter ‖ AEAD(inner_kind ‖ bytes)), DeviceInfo.seal_key และความสามารถ sealed
สำหรับเซสชันที่ปิดผนึก Exchange ดำเนินการเฉพาะการแปล ID การจำกัดการทำงานพร้อมกัน และการกำหนดเส้นทาง ไม่มีการแยกวิเคราะห์หรือตรวจสอบเพย์โหลด (บันทึกการตรวจสอบมีเพียงกุญแจสาธารณะ ระดับการอนุญาตและการหมดอายุ และผลลัพธ์สุดท้าย) และไม่ได้ตัดสินการอนุญาตนอกเหนือจากการตรวจสอบว่าบัญชีสามารถมองเห็นอุปกรณ์ได้หรือไม่ เส้นทางจะถูกเผยแพร่บน last:true เส้นทางที่ปิดผนึกยอมรับเฉพาะเฟรม Sealed เท่านั้น เส้นทางธรรมดาปฏิเสธเฟรม Sealed agent ละทิ้งข้อความธรรมดาใดๆ ที่ส่งไปยัง ID เซสชันที่ปิดผนึก สำหรับการอนุญาต การจับมือกัน การรับคีย์ โมเดลภัยคุกคาม โปรดดูที่ End-to-End Sealed Sessions