Module Net::SSH::Transport::PacketStream

  1. lib/net/ssh/transport/packet_stream.rb

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.

Included modules

  1. BufferedIo

Attributes

client [R] The client state object, which encapsulates the algorithms used to build packets to send to the server.
hints [R] 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.
server [R] The server state object, which encapsulates the algorithms used to interpret packets coming from the server.

Public class methods

extended (object)
[show source]
    # File lib/net/ssh/transport/packet_stream.rb, line 19
19:     def self.extended(object)
20:       object.__send__(:initialize_ssh)
21:     end

Public instance methods

available_for_read? ()

Returns true if the IO is available for reading, and false otherwise.

[show source]
    # File lib/net/ssh/transport/packet_stream.rb, line 72
72:     def available_for_read?
73:       result = Net::SSH::Compat.io_select([self], nil, nil, 0)
74:       result && result.first.any?
75:     end
cleanup ()

Performs any pending cleanup necessary on the IO and its associated state objects. (See State#cleanup).

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 155
155:     def cleanup
156:       client.cleanup
157:       server.cleanup
158:     end
client_name ()

The name of the client (local) end of the socket, as reported by the socket.

[show source]
    # File lib/net/ssh/transport/packet_stream.rb, line 39
39:     def client_name
40:       @client_name ||= begin
41:         sockaddr = getsockname
42:         begin
43:           Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
44:         rescue
45:           begin
46:             Socket.getnameinfo(sockaddr).first
47:           rescue
48:             begin
49:               Socket.gethostbyname(Socket.gethostname).first
50:             rescue
51:               lwarn { "the client ipaddr/name could not be determined" }
52:               "unknown"
53:             end
54:           end
55:         end
56:       end
57:     end
enqueue_packet (payload)

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).

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 118
118:     def enqueue_packet(payload)
119:       # try to compress the packet
120:       payload = client.compress(payload)
121: 
122:       # the length of the packet, minus the padding
123:       actual_length = 4 + payload.length + 1
124: 
125:       # compute the padding length
126:       padding_length = client.block_size - (actual_length % client.block_size)
127:       padding_length += client.block_size if padding_length < 4
128: 
129:       # compute the packet length (sans the length field itself)
130:       packet_length = payload.length + padding_length + 1
131: 
132:       if packet_length < 16
133:         padding_length += client.block_size
134:         packet_length = payload.length + padding_length + 1
135:       end
136: 
137:       padding = Array.new(padding_length) { rand(256) }.pack("C*")
138: 
139:       unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
140:       mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
141: 
142:       encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
143:       message = encrypted_data + mac
144: 
145:       debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
146:       enqueue(message)
147: 
148:       client.increment(packet_length)
149: 
150:       self
151:     end
if_needs_rekey? () {|| ...}

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.

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 163
163:     def if_needs_rekey?
164:       if client.needs_rekey? || server.needs_rekey?
165:         yield
166:         client.reset! if client.needs_rekey?
167:         server.reset! if server.needs_rekey?
168:       end
169:     end
next_packet (mode=:nonblock)

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.

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 82
 82:     def next_packet(mode=:nonblock)
 83:       case mode
 84:       when :nonblock then
 85:         fill if available_for_read?
 86:         poll_next_packet
 87: 
 88:       when :block then
 89:         loop do
 90:           packet = poll_next_packet
 91:           return packet if packet
 92: 
 93:           loop do
 94:             result = Net::SSH::Compat.io_select([self]) or next
 95:             break if result.first.any?
 96:           end
 97: 
 98:           if fill <= 0
 99:             raise Net::SSH::Disconnect, "connection closed by remote host"
100:           end
101:         end
102: 
103:       else
104:         raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
105:       end
106:     end
peer_ip ()

The IP address of the peer (remote) end of the socket, as reported by the socket.

[show source]
    # File lib/net/ssh/transport/packet_stream.rb, line 61
61:     def peer_ip
62:       @peer_ip ||=
63:         if respond_to?(:getpeername)
64:           addr = getpeername
65:           Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
66:         else
67:           "<no hostip for proxy command>"
68:         end
69:     end
send_packet (payload)

Enqueues a packet to be sent, and blocks until the entire packet is sent.

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 110
110:     def send_packet(payload)
111:       enqueue_packet(payload)
112:       wait_for_pending_sends
113:     end

Protected instance methods

initialize_ssh ()

Called when this module is used to extend an object. It initializes the states and generally prepares the object for use as a packet stream.

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 175
175:       def initialize_ssh
176:         @hints  = {}
177:         @server = State.new(self, :server)
178:         @client = State.new(self, :client)
179:         @packet = nil
180:         initialize_buffered_io
181:       end
poll_next_packet ()

Tries to read the next packet. If there is insufficient data to read an entire packet, this returns immediately, otherwise the packet is read, post-processed according to the cipher, hmac, and compression algorithms specified in the server state object, and returned as a new Packet object.

[show source]
     # File lib/net/ssh/transport/packet_stream.rb, line 188
188:       def poll_next_packet
189:         if @packet.nil?
190:           minimum = server.block_size < 4 ? 4 : server.block_size
191:           return nil if available < minimum
192:           data = read_available(minimum)
193: 
194:           # decipher it
195:           @packet = Net::SSH::Buffer.new(server.update_cipher(data))
196:           @packet_length = @packet.read_long
197:         end
198: 
199:         need = @packet_length + 4 - server.block_size
200:         raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
201: 
202:         return nil if available < need + server.hmac.mac_length
203: 
204:         if need > 0
205:           # read the remainder of the packet and decrypt it.
206:           data = read_available(need)
207:           @packet.append(server.update_cipher(data))
208:         end
209: 
210:         # get the hmac from the tail of the packet (if one exists), and
211:         # then validate it.
212:         real_hmac = read_available(server.hmac.mac_length) || ""
213: 
214:         @packet.append(server.final_cipher)
215:         padding_length = @packet.read_byte
216: 
217:         payload = @packet.read(@packet_length - padding_length - 1)
218:         padding = @packet.read(padding_length) if padding_length > 0
219: 
220:         my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
221:         raise Net::SSH::Exception, "corrupted mac detected" if real_hmac != my_computed_hmac
222: 
223:         # try to decompress the payload, in case compression is active
224:         payload = server.decompress(payload)
225: 
226:         debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
227: 
228:         server.increment(@packet_length)
229:         @packet = nil
230: 
231:         return Packet.new(payload)
232:       end