Module: Net::SSH::Transport::PacketStream
- Includes:
- BufferedIo
- Defined in:
- lib/net/ssh/transport/packet_stream.rb
Overview
A module that builds additional functionality onto the Net::SSH::BufferedIo module. It adds SSH encryption, compression, and packet validation, as per the SSH2 protocol. It also adds an abstraction for polling packets, to allow for both blocking and non-blocking reads.
Constant Summary collapse
- PROXY_COMMAND_HOST_IP =
rubocop:disable Metrics/ModuleLength
'<no hostip for proxy command>'.freeze
Instance Attribute Summary collapse
-
#client ⇒ Object
readonly
The client state object, which encapsulates the algorithms used to build packets to send to the server.
-
#hints ⇒ Object
readonly
The map of “hints” that can be used to modify the behavior of the packet stream.
-
#server ⇒ Object
readonly
The server state object, which encapsulates the algorithms used to interpret packets coming from the server.
Attributes included from Loggable
Class Method Summary collapse
Instance Method Summary collapse
-
#available_for_read? ⇒ Boolean
Returns true if the IO is available for reading, and false otherwise.
-
#cleanup ⇒ Object
Performs any pending cleanup necessary on the IO and its associated state objects.
-
#client_name ⇒ Object
The name of the client (local) end of the socket, as reported by the socket.
-
#enqueue_packet(payload) ⇒ Object
Enqueues a packet to be sent, but does not immediately send the packet.
-
#if_needs_rekey? ⇒ Boolean
If the IO object requires a rekey operation (as indicated by either its client or server state objects, see State#needs_rekey?), this will yield.
-
#next_packet(mode = :nonblock, timeout = nil) ⇒ Object
Returns the next full packet.
-
#peer_ip ⇒ Object
The IP address of the peer (remote) end of the socket, as reported by the socket.
-
#send_packet(payload) ⇒ Object
Enqueues a packet to be sent, and blocks until the entire packet is sent.
Methods included from BufferedIo
#available, #enqueue, #fill, #pending_write?, #read_available, #read_buffer, #send_pending, #wait_for_pending_sends, #write_buffer
Methods included from Loggable
#debug, #error, #fatal, #info, #lwarn
Instance Attribute Details
#client ⇒ Object (readonly)
The client state object, which encapsulates the algorithms used to build packets to send to the server.
36 37 38 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 36 def client @client end |
#hints ⇒ Object (readonly)
The map of “hints” that can be used to modify the behavior of the packet stream. For instance, when authentication succeeds, an “authenticated” hint is set, which is used to determine whether or not to compress the data when using the “delayed” compression algorithm.
28 29 30 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 28 def hints @hints end |
#server ⇒ Object (readonly)
The server state object, which encapsulates the algorithms used to interpret packets coming from the server.
32 33 34 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 32 def server @server end |
Class Method Details
.extended(object) ⇒ Object
20 21 22 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 20 def self.extended(object) object.__send__(:initialize_ssh) end |
Instance Method Details
#available_for_read? ⇒ Boolean
Returns true if the IO is available for reading, and false otherwise.
73 74 75 76 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 73 def available_for_read? result = IO.select([self], nil, nil, 0) result && result.first.any? end |
#cleanup ⇒ Object
Performs any pending cleanup necessary on the IO and its associated state objects. (See State#cleanup).
187 188 189 190 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 187 def cleanup client.cleanup server.cleanup end |
#client_name ⇒ Object
The name of the client (local) end of the socket, as reported by the socket.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 40 def client_name @client_name ||= begin sockaddr = getsockname begin Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first rescue StandardError begin Socket.getnameinfo(sockaddr).first rescue StandardError begin Socket.gethostbyname(Socket.gethostname).first rescue StandardError lwarn { "the client ipaddr/name could not be determined" } "unknown" end end end end end |
#enqueue_packet(payload) ⇒ Object
Enqueues a packet to be sent, but does not immediately send the packet. The given payload is pre-processed according to the algorithms specified in the client state (compression, cipher, and hmac).
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 126 def enqueue_packet(payload) # rubocop:disable Metrics/AbcSize # try to compress the packet payload = client.compress(payload) # the length of the packet, minus the padding actual_length = (client.hmac.etm || client.hmac.aead ? 0 : 4) + payload.bytesize + 1 # compute the padding length padding_length = client.block_size - (actual_length % client.block_size) padding_length += client.block_size if padding_length < 4 # compute the packet length (sans the length field itself) packet_length = payload.bytesize + padding_length + 1 if packet_length < 16 padding_length += client.block_size packet_length = payload.bytesize + padding_length + 1 end padding = Array.new(padding_length) { rand(256) }.pack("C*") if client.cipher.implicit_mac? unencrypted_data = [padding_length, payload, padding].pack("CA*A*") = client.cipher.update_cipher_mac(unencrypted_data, client.sequence_number) elsif client.hmac.etm debug { "using encrypt-then-mac" } # Encrypt padding_length, payload, and padding. Take MAC # from the unencrypted packet_length and the encrypted # data. length_data = [packet_length].pack("N") unencrypted_data = [padding_length, payload, padding].pack("CA*A*") encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher mac_data = length_data + encrypted_data mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*")) = mac_data + mac else unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*") mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*")) encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher = encrypted_data + mac end debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" } enqueue() client.increment(packet_length) self end |
#if_needs_rekey? ⇒ Boolean
If the IO object requires a rekey operation (as indicated by either its client or server state objects, see State#needs_rekey?), this will yield. Otherwise, this does nothing.
195 196 197 198 199 200 201 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 195 def if_needs_rekey? if client.needs_rekey? || server.needs_rekey? yield client.reset! if client.needs_rekey? server.reset! if server.needs_rekey? end end |
#next_packet(mode = :nonblock, timeout = nil) ⇒ Object
Returns the next full packet. If the mode parameter is :nonblock (the default), then this will return immediately, whether a packet is available or not, and will return nil if there is no packet ready to be returned. If the mode parameter is :block, then this method will block until a packet is available or timeout seconds have passed.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 83 def next_packet(mode = :nonblock, timeout = nil) case mode when :nonblock then packet = poll_next_packet return packet if packet if available_for_read? if fill <= 0 result = poll_next_packet if result.nil? raise Net::SSH::Disconnect, "connection closed by remote host" else return result end end end poll_next_packet when :block then loop do packet = poll_next_packet return packet if packet result = IO.select([self], nil, nil, timeout) raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0 end else raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}" end end |
#peer_ip ⇒ Object
The IP address of the peer (remote) end of the socket, as reported by the socket.
62 63 64 65 66 67 68 69 70 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 62 def peer_ip @peer_ip ||= if respond_to?(:getpeername) addr = getpeername Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first else PROXY_COMMAND_HOST_IP end end |
#send_packet(payload) ⇒ Object
Enqueues a packet to be sent, and blocks until the entire packet is sent.
118 119 120 121 |
# File 'lib/net/ssh/transport/packet_stream.rb', line 118 def send_packet(payload) enqueue_packet(payload) wait_for_pending_sends end |