Module Net::SSH::Transport::PacketStream
In: lib/net/ssh/transport/packet_stream.rb
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.

Methods

Included Modules

BufferedIo BufferedIo

Attributes

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

Public Class methods

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 17
17:     def self.extended(object)
18:       object.__send__(:initialize_ssh)
19:     end

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 17
17:     def self.extended(object)
18:       object.__send__(:initialize_ssh)
19:     end

Public Instance methods

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

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 67
67:     def available_for_read?
68:       result = IO.select([self], nil, nil, 0)
69:       result && result.first.any?
70:     end

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

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 67
67:     def available_for_read?
68:       result = IO.select([self], nil, nil, 0)
69:       result && result.first.any?
70:     end

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

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 150
150:     def cleanup
151:       client.cleanup
152:       server.cleanup
153:     end

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

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 150
150:     def cleanup
151:       client.cleanup
152:       server.cleanup
153:     end

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

[Source]

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

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

[Source]

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

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

[Source]

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

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

[Source]

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

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.

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 158
158:     def if_needs_rekey?
159:       if client.needs_rekey? || server.needs_rekey?
160:         yield
161:         client.reset! if client.needs_rekey?
162:         server.reset! if server.needs_rekey?
163:       end
164:     end

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.

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 158
158:     def if_needs_rekey?
159:       if client.needs_rekey? || server.needs_rekey?
160:         yield
161:         client.reset! if client.needs_rekey?
162:         server.reset! if server.needs_rekey?
163:       end
164:     end

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.

[Source]

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

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.

[Source]

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

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

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 59
59:     def peer_ip
60:       @peer_ip ||= begin
61:         addr = getpeername
62:         Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
63:       end
64:     end

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

[Source]

    # File lib/net/ssh/transport/packet_stream.rb, line 59
59:     def peer_ip
60:       @peer_ip ||= begin
61:         addr = getpeername
62:         Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
63:       end
64:     end

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

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 105
105:     def send_packet(payload)
106:       enqueue_packet(payload)
107:       wait_for_pending_sends
108:     end

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

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 105
105:     def send_packet(payload)
106:       enqueue_packet(payload)
107:       wait_for_pending_sends
108:     end

Protected Instance methods

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.

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 170
170:       def initialize_ssh
171:         @hints  = {}
172:         @server = State.new(self, :server)
173:         @client = State.new(self, :client)
174:         @packet = nil
175:         initialize_buffered_io
176:       end

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.

[Source]

     # File lib/net/ssh/transport/packet_stream.rb, line 170
170:       def initialize_ssh
171:         @hints  = {}
172:         @server = State.new(self, :server)
173:         @client = State.new(self, :client)
174:         @packet = nil
175:         initialize_buffered_io
176:       end

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.

[Source]

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

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.

[Source]

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

[Validate]