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.
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. |
# File lib/net/ssh/transport/packet_stream.rb, line 17 17: def self.extended(object) 18: object.__send__(:initialize_ssh) 19: end
# File lib/net/ssh/transport/packet_stream.rb, line 17 17: def self.extended(object) 18: object.__send__(:initialize_ssh) 19: end
Returns true if the IO is available for reading, and false otherwise.
# 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.
# 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).
# 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).
# 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.
# 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.
# 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).
# 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).
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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
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.
# 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.
# 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.
# 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.
# 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