go 複用ssh 中的session_重新認識SSH(二)
技術標籤:go 複用ssh 中的session
專欄排版存在些許問題,歡迎訪問我的部落格:emous.github.io 訪問更多博文。
在認證完畢後,客戶端和服務端之間將使用SSH連線協議進行實際的任務操作,包括開啟互動式的登入會話、遠端命令呼叫、TCP轉發、X11轉發等。在傳輸層協議之上,啟用連線協議的方式就是請求一個service name為ssh-connection服務。
Channel機制
連線協議裡的每個實際應用都是Channel,各方都有可能開啟Channel,大量的Channel複用同一個Connection(我認為這裡指的Connection應該是上文說的ssh-connection service)。一個Channel被雙方用自己的數字標識,所以每端不同的數字可能指向的並不是相同的Channel。其他任何和Channel相關的訊息都會包含對端的Channel標識。
sender:number1 -> ()=========================() <- number2:receiver
Channel是被流控
的,在被告知視窗
可用之前沒有資料可以在Channel裡傳輸。
(新建)開啟一個Channel
當任意一端想要新建一個Channel時,它首先要給Channel分配一個本端的數字標識。然後將下面的訊息傳送給對端,這個訊息包括了本(傳送)端標識、初始化視窗大小等。
byte SSH_MSG_CHANNEL_OPEN string channel type in US-ASCII only uint32 sender channel uint32 initial window size uint32 maximum packet size .... channel type specific data follows
channel type
是一個名字,符合SSH rfc命名規範,註冊命名(名稱
)、擴充套件命名(名稱@域名
);sender channel
是本地的標識;initial window size
則明確了在不調整視窗大小的情況下,對方一共可以傳送多少位元組的資料給(這個訊息的)傳送者;maximum packet size
表示對方發給 (這個訊息的)傳送者 的單次的packet的最大值是多少。
為什麼要設定window size和maximum packet size,我的理解是,有一些老舊的慢速裝置IO頻寬很低,所以如果大量的資料湧進來會導致緩衝區溢位。
對端收到訊息後需要作出決定是否同意開啟一個Channel,使用SSH_MSG_CHANNEL_OPEN_CONFIRMATION或SSH_MSG_CHANNEL_OPEN_FAILURE響應訊息。
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION
uint32 recipient channel
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
.... channel type specific data follows
其中recipient channel
為請求開啟Channel端的本地Channel標識,sender channel
則為當前訊息傳送方的本地Channel標識,其他的資料都是描述當前訊息傳送方的。或者傳送開啟失敗的訊息SSH_MSG_CHANNEL_OPEN_FAILURE。
byte SSH_MSG_CHANNEL_OPEN_FAILURE
uint32 recipient channel
uint32 reason code
string description in ISO-10646 UTF-8 encoding [RFC3629]
string language tag [RFC3066]
比如被請求開啟Channel的一方不支援標註的channel type
,那它將簡單地迴應SSH_MSG_CHANNEL_OPEN_FAILURE。請求方則或許需要顯示description
給使用者。下面是一些預定義的錯誤碼:
SSH_OPEN_ADMINISTRATIVELY_PROHIBITED 1
SSH_OPEN_CONNECT_FAILED 2
SSH_OPEN_UNKNOWN_CHANNEL_TYPE 3
SSH_OPEN_RESOURCE_SHORTAGE 4
錯誤碼0x00000005 - 0xFDFFFFFF將按照IETF CONSENSUS
的方式分配,0xFE000000 - 0xFFFFFFFF則留給個人使用。雖然IANA沒有關於0xFE000000 - 0xFFFFFFFF的控制權,但是還是將他約定成2部分使用:
- 0xFE000000 - 0xFEFFFFFF被用在本地分配的Channel上,比如channel type為”[email protected]”(帶有@符號)的Channel開啟失敗,那麼錯誤碼應該使用由IANA分配的部分( 0x00000001 - 0xFDFFFFFF)或者本地分配相關的部分(0xFE000000 - 0xFEFFFFFF)。
比如伺服器不認識這個channel type,哪怕這個type是本地定義(包含@)的,也必須使用0x00000003錯誤碼。然而如果,伺服器認識這個錯誤碼但是無法開啟,則應該使用0xFE000000 - 0xFEFFFFFF其中的一個錯誤碼。總的來說,參與者應該首先嚐試使用IANA分配的錯誤碼,然後在使用它們自定義的原因。 - 對於從0xFF開始的部分,不做限制或建議。在這個範圍內的每一個值,都不被期望有任何實際操作互動性,本質上說它們是為實驗目的而預留的。
傳輸資料
上文描述了視窗大小可以用來限制另一方傳送的資料量,同時協議規定雙方都可以通過下面的訊息對視窗作出調整。
byte SSH_MSG_CHANNEL_WINDOW_ADJUST
uint32 recipient channel
uint32 bytes to add
接收方接收到這個訊息以後,接收方可以根據這個給定的數增加視窗的大小,不論如何視窗最大為2^32-1位元組。具體的資料,則通過下面的訊息傳送。
byte SSH_MSG_CHANNEL_DATA
uint32 recipient channel
string data
單次可以傳送資料的最大量取決於對方當前視窗尺寸和對方允許接受的最大packet值的最小值。對方每接受一個訊息,視窗都會相應減少。規範期望實現可以對傳輸層packet size做出限制(任何關於接受資料的限制必須大於等於32768位元組)。所以在連線層協議:
- 不準將可接受的maximum packet size設定成大於傳輸層能接受的最大值。
- 不準生成超過傳輸層能傳送的最大值,哪怕對方的連線協議能接受這麼大的packet。
同時,協議提供了一些傳遞額外資料(比如stderr資料)的方法。
byte SSH_MSG_CHANNEL_EXTENDED_DATA
uint32 recipient channel
uint32 data_type_code
string data
目前標準定義的data_type_code型別有:
SSH_EXTENDED_DATA_STDERR 1
同時這個data_type_code的值的分發規定也與上文的channel type類似,分為IANA部分和私人使用部分。
關閉Channel
當任意一方不在往Channel傳送更多資料的時候,它應該傳送一個SSH_MSG_CHANNEL_EOF訊息。
byte SSH_MSG_CHANNEL_EOF
uint32 recipient channel
這個訊息不會有明確的響應,但是它依舊應該被髮送給對方不論對方是誰。需要注意的是,傳送完這個訊息後,Channel依舊是開啟著的(只不過自己這一邊不再發資料了),從另一個方向上還是可能過來更多的資料。這個訊息並不會消耗視窗大小,即使視窗已經不可以。
當任意一方希望結束Channel時,則應該傳送SSH_MSG_CHANNEL_CLOSE。另一方必須也傳送SSH_MSG_CHANNEL_CLOSE,除非它已經發送過SSH_MSG_CHANNEL_CLOSE(網路延遲)了。當一方既傳送了又接收到SSH_MSG_CHANNEL_CLOSE訊息,Channel就被關閉了,相關的資源可以被清理,本地的Channel number可以在下次開啟Channel的時候重用。任意一方都可以直接傳送SSH_MSG_CHANNEL_CLOSE而不需要與現傳送SSH_MSG_CHANNEL_EOF。
byte SSH_MSG_CHANNEL_CLOSE
uint32 recipient channel
同樣的,這個訊息不需要消耗視窗大小。
明確Channel資訊請求
許多的channel type包含關於該channel type的更詳細的擴充設定。比如說,為一個互動session請求一個虛擬終端。所有的明確Channel資訊請求都是如下格式。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string request type in US-ASCII characters only
boolean want reply
.... type-specific data follows
如果want reply
被設定成FALSE,不會有響應被回覆給請求端。否則,響應可能包括SSH_MSG_CHANNEL_SUCCESS、SSH_MSG_CHANNEL_FAILURE或者要求繼續提供資訊的訊息。如果接收端不認識或不支援這個擴充的明細,則返回SSH_MSG_CHANNEL_FAILURE。
這個訊息也不消耗視窗大小,request type
是自定義的。
byte SSH_MSG_CHANNEL_SUCCESS
uint32 recipient channel
byte SSH_MSG_CHANNEL_FAILURE
uint32 recipient channel
上述兩個訊息也不消耗視窗大小。
互動Session
一個Session就是一個遠端的程式的執行。這個程式或許是shell、應用程式、系統呼叫或者內建的子系統。它可能沒有繫結到虛擬終端上,又或者有或沒有涉及到X11轉發。同時間,可以有多個Session正在被執行。
開啟Session Channel
使用如下訊息開啟一個Session Channel,客戶端應該拒絕來自服務端的開啟Session Channel的請求以避免被攻擊。
byte SSH_MSG_CHANNEL_OPEN
string "session"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
請求一個虛擬終端
通過如下訊息可以讓伺服器為Session分配一個虛擬終端,character/row的優先順序相比於pixels更高,除非他們被設定成0。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "pty-req"
boolean want_reply
string TERM environment variable value (e.g., vt100)
uint32 terminal width, characters (e.g., 80)
uint32 terminal height, rows (e.g., 24)
uint32 terminal width, pixels (e.g., 640)
uint32 terminal height, pixels (e.g., 480)
string encoded terminal modes
客戶端應該拒絕來自服務端的虛擬終端明確資訊請求以避免被攻擊。
X11轉發
通過如下訊息可以為Session請求X11轉發。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "x11-req"
boolean want reply
boolean single connection
string x11 authentication protocol
string x11 authentication cookie
uint32 x11 screen number
協議推薦將x11 authentication cookie
傳送成一個虛假且隨機的cookie,知道連線訊息被接收後它將被檢驗並替換成真實的cookie。當session channel被關閉的時候,X11轉發也應該停止,但是已經開啟的轉發不應該自動被關閉。如果single connection
被設定為TRUE,那麼只有一個連線被轉發。
這個訊息對應的操作是: 客戶端向伺服器發出請求,伺服器在本地新建N個(如果single connection不為0)X11伺服器(只是純粹的監聽6000+server自定義offset(openssh為10)的TCP埠,建立相應的DISPLAY環境變數)。
在remote session上執行gedit &
時,gedit
是符合X11協議的客戶端,所以它會檢測環境變數發現存在display,就和本地的6010埠建立連線。伺服器的偽x11伺服器socket偵測到連線就很向客戶端發起SSH_MSG_CHANNEL_OPEN x11 Channel的請求。
byte SSH_MSG_CHANNEL_OPEN
string "x11"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string originator address (e.g., "192.168.7.38")
uint32 originator port
客戶端收到請求後再與本地的X11建立連線,這樣一個X11轉發的通道就完成了(我並沒有在openssh的原始碼中發現客戶端是如何使用originator address資料的)。
傳遞環境變數
在shell或command被開始時之後,或許有環境變數需要被傳遞過去。然而在特權程式裡不受控制的設定環境變數是一個很有風險的事情,所以規範推薦實現維護一個允許被設定的環境變數列表或者只有當sshd丟棄許可權後設置環境變數。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "env"
boolean want reply
string variable name
string variable value
啟動一個Shell或者一個命令
一旦一個Session被設定完畢,在遠端就會有一個程式被啟動。這個程式可以是一個Shell,也可以時一個應用程式或者是一個有著獨立域名的子系統。下面的請求每個Channel(Session)只允許設定一個。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "shell"
boolean want reply
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exec"
boolean want reply
string command
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "subsystem"
boolean want reply
string subsystem name
視窗調整訊息
當客戶端的終端視窗大小被改變時,或許需要傳送這個訊息給伺服器。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "window-change"
boolean FALSE
uint32 terminal width, columns
uint32 terminal height, rows
uint32 terminal width, pixels
uint32 terminal height, pixels
這個訊息沒有響應。
本地流控
在很多系統中,這是否可行取決於偽終端是否使用control-S/control-Q
進行流控。如果正在使用,那麼在客戶端就應該有一個流控的功能給服務端的響應提速,這還是取決於服務端裝置的(系統)實現。下面的訊息可以讓伺服器通知客戶端是否可以提供流控的功能,如果可以的話客戶端則可以使用control-S/control-Q
進行流控。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "xon-xoff"
boolean FALSE
boolean client can do
這個訊息沒有響應。
訊號
一個訊號可以被傳輸給遠端的程式或服務使用下面的訊息。有一些系統可能沒有實現訊號,所以那些系統下的服務端應該忽略這個訊息。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "signal"
boolean FALSE
string signal name (without the "SIG" prefix)
signal name
在下面的預定義名稱中有描述。
返回退出狀態
當在遠端的命令結束時,下面的訊息可以用來傳遞其退出的狀態碼。傳送或收到這個訊息後Channel將被關閉。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exit-status"
boolean FALSE
uint32 exit_status
同時遠端的程式也可能因為一個訊號而被迫關閉(exit_statue
為0的時候為正常關閉),這個情況下下面的訊息將被髮送。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exit-signal"
boolean FALSE
string signal name (without the "SIG" prefix)
boolean core dumped
string error message in ISO-10646 UTF-8 encoding
string language tag [RFC3066]
TCP/IP 埠轉發
全域性請求
協議規定了許多種可以影響遠端全域性而獨立於任何Channel的請求,一個例子就是唯一個特定的埠請求TCP/IP轉發。雙方中的任意一方都有可能在任意時間傳送全域性請求,接收方必須做出合理的響應,其格式如下。
byte SSH_MSG_GLOBAL_REQUEST
string request name in US-ASCII only
boolean want reply
.... request-specific data follows
響應如下,通常不包含response specific data
這一欄。
byte SSH_MSG_REQUEST_SUCCESS
.... response specific data
協議規定,SSH_MSG_GLOBAL_REQUESTS的響應順序必須如其傳送順序一致。有明確指示Channel的請求訊息才可以不按順序傳送。
請求埠轉發
如果其中一方期望一個發往對方埠的資料可以轉發到本地的埠,那麼他可以傳送如下的請求。
byte SSH_MSG_GLOBAL_REQUEST
string "tcpip-forward"
boolean want reply
string address to bind (e.g., "0.0.0.0")
uint32 port number to bind
address to bind
和port number to bind
用來標明哪個具體的IP地址(或域名)和埠用於轉發(另一端端開啟對哪個IP和埠的監聽並轉發到本地),address to bind
滿足以下的語法規則。
""
表示接受所有的被SSH實現方支援的協議。"0.0.0.0"
表示監聽所有IPv4埠。"::"
表示監聽所有IPv6埠。"localhost"
表示在迴環網絡卡上監聽所有被SSH實現支援的協議。"127.0.0.1"
和"::1"
分別表示監聽IPv4和IPv6的迴環網絡卡。
客戶端應該拒絕這個訊息,這個訊息一般只允許客戶端傳送。同時如果客戶端傳遞的port number
是0,且它設定了want reply
為TRUE,那麼伺服器應該分配下一個可用的非許可權埠
,並且在響應中告知客戶端。
byte SSH_MSG_REQUEST_SUCCESS
uint32 port that was bound on the server
埠轉發可以被下面的訊息取消,一個channel open請求可能會直到接收到該訊息的響應後收到。這個訊息只有客戶端可以傳送。
byte SSH_MSG_GLOBAL_REQUEST
string "cancel-tcpip-forward"
boolean want reply
string address_to_bind (e.g., "127.0.0.1")
uint32 port number to bind
TCP/IP轉發Channel
當服務端被設定需要監聽並轉發的埠收到了外部的連線時,服務端將傳送下面的訊息請求客戶端開啟一個TCP/IP轉發Channel。
byte SSH_MSG_CHANNEL_OPEN
string "forwarded-tcpip"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string address that was connected (外部連線到伺服器監聽地址的的IP地址)
uint32 port that was connected
string originator IP address (這個應該是服務端監聽的地址)
uint32 originator port
客戶端應該比對是否向伺服器請求了originator port的TCP/IP埠轉發。
當客戶端本地的TCP/IP轉發埠接收到來自外部的連線(這個監聽埠是客戶端主動開啟的)時,客戶端傳送如下訊息給伺服器轉發這個TCP/IP資料。
byte SSH_MSG_CHANNEL_OPEN
string "direct-tcpip"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string host to connect
uint32 port to connect
string originator IP address
uint32 originator port
host to connect
和port to connect
表示服務端內部需要建立一個通往host to connect
、port to connect
的連線。originator IP address
表示客戶端監聽到的連線發起於哪個外部IP地址、originator port
表示客戶端監聽到的連線發起於哪個外部主機的埠。
轉發Channel獨立於Session存在,Session關閉並不意味著轉發Channel也要被關閉。客戶端應該拒絕direct-tcpip
請求。
終端模式的編碼
在請求一個終端的時候會用到終端模式的編碼(encoded terminal modes
),它們被編碼進位元組流裡。這是為了他們可以方便地在不同環境間傳輸。位元組流包括以位元組為值地操作碼構成地引數對。1-159的操作碼時uint32型別的引數,160-255還未被定義,並且如果遇到它們應該停止解析。位元組流被操作碼TTY_OP_END(0x00)
終止。
客戶端應該儘可能的把它知道的模式操作碼加入位元組流中,而伺服器對於它不知道的模式應該忽略。至少在用類POSIX 虛擬終端的系統間,會支援一些機器無關的特性。這個協議也能支援其他的系統,但是客戶端或許需要補充一系列合理的引數數值這樣伺服器才能為偽終端提供合理的模式。具體預定義數值如下(為了可讀性,將Byte寫成了數字):
opcode mnemonic description
------ -------- -----------
0 TTY_OP_END Indicates end of options.
1 VINTR Interrupt character; 255 if none. Similarly
for the other characters. Not all of these
characters are supported on all systems.
2 VQUIT The quit character (sends SIGQUIT signal on
POSIX systems).
3 VERASE Erase the character to left of the cursor.
4 VKILL Kill the current input line.
5 VEOF End-of-file character (sends EOF from the
terminal).
6 VEOL End-of-line character in addition to
carriage return and/or linefeed.
7 VEOL2 Additional end-of-line character.
8 VSTART Continues paused output (normally
control-Q).
9 VSTOP Pauses output (normally control-S).
10 VSUSP Suspends the current program.
11 VDSUSP Another suspend character.
12 VREPRINT Reprints the current input line.
13 VWERASE Erases a word left of cursor.
14 VLNEXT Enter the next character typed literally,
even if it is a special character
15 VFLUSH Character to flush output.
16 VSWTCH Switch to a different shell layer.
17 VSTATUS Prints system status line (load, command,
pid, etc).
18 VDISCARD Toggles the flushing of terminal output.
30 IGNPAR The ignore parity flag. The parameter
SHOULD be 0 if this flag is FALSE,
and 1 if it is TRUE.
31 PARMRK Mark parity and framing errors.
32 INPCK Enable checking of parity errors.
33 ISTRIP Strip 8th bit off characters.
34 INLCR Map NL into CR on input.
35 IGNCR Ignore CR on input.
36 ICRNL Map CR to NL on input.
37 IUCLC Translate uppercase characters to
lowercase.
38 IXON Enable output flow control.
39 IXANY Any char will restart after stop.
40 IXOFF Enable input flow control.
41 IMAXBEL Ring bell on input queue full.
50 ISIG Enable signals INTR, QUIT, [D]SUSP.
51 ICANON Canonicalize input lines.
52 XCASE Enable input and output of uppercase
characters by preceding their lowercase
equivalents with "".
53 ECHO Enable echoing.
54 ECHOE Visually erase chars.
55 ECHOK Kill character discards current line.
56 ECHONL Echo NL even if ECHO is off.
57 NOFLSH Don't flush after interrupt.
58 TOSTOP Stop background jobs from output.
59 IEXTEN Enable extensions.
60 ECHOCTL Echo control characters as ^(Char).
61 ECHOKE Visual erase for line kill.
62 PENDIN Retype pending input.
70 OPOST Enable output processing.
71 OLCUC Convert lowercase to uppercase.
72 ONLCR Map NL to CR-NL.
73 OCRNL Translate carriage return to newline
(output).
74 ONOCR Translate newline to carriage
return-newline (output).
75 ONLRET Newline performs a carriage return
(output).
90 CS7 7 bit mode.
91 CS8 8 bit mode.
92 PARENB Parity enable.
93 PARODD Odd parity, else even.
128 TTY_OP_ISPEED Specifies the input baud rate in
bits per second.
129 TTY_OP_OSPEED Specifies the output baud rate in
bits per second.
預定義名稱
連線協議Channel型別
Channel type Reference
------------ ---------
session [SSH-CONNECT, Section 6.1]
x11 [SSH-CONNECT, Section 6.3.2]
forwarded-tcpip [SSH-CONNECT, Section 7.2]
direct-tcpip [SSH-CONNECT, Section 7.2]
連線協議全域性請求名
Request type Reference
------------ ---------
tcpip-forward [SSH-CONNECT, Section 7.1]
cancel-tcpip-forward [SSH-CONNECT, Section 7.1]
連線協議Channel明細請求名
Request type Reference
------------ ---------
pty-req [SSH-CONNECT, Section 6.2]
x11-req [SSH-CONNECT, Section 6.3.1]
env [SSH-CONNECT, Section 6.4]
shell [SSH-CONNECT, Section 6.5]
exec [SSH-CONNECT, Section 6.5]
subsystem [SSH-CONNECT, Section 6.5]
window-change [SSH-CONNECT, Section 6.7]
xon-xoff [SSH-CONNECT, Section 6.8]
signal [SSH-CONNECT, Section 6.9]
exit-status [SSH-CONNECT, Section 6.10]
exit-signal [SSH-CONNECT, Section 6.10]
連線協議子系統名
暫無
Reference
- OpenSSH Specifications
這是OpenSSH所展現的最直接的資料頁面,但是有很多細節部分的規格與實現沒有羅列。而且RFC文件錯綜複雜,有很多地方都引用不全必須靠“幸運”才能翻看到。 - The Secure Shell (SSH) Protocol Architecture
SSH協議架構的rfc頁面,它將SSH分為三部分,傳輸、認證和連線。 - The Secure Shell (SSH) Protocol Assigned Numbers
規定協議中各種ID(巨集)所使用的序號。 - The Secure Shell (SSH) Connection Protocol
規定SSH連線協議。 - OpenSSH Portable
OpenSSH的原始碼。