Class: DeployAgent::ServerConnection

Inherits:
Object
  • Object
show all
Defined in:
lib/deploy_agent/server_connection.rb

Overview

The ServerConnection class deals with all communication with the Deploy server

Defined Under Namespace

Classes: ServerDisconnected

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent, server_host, nio_selector, check_certificate = true) ⇒ ServerConnection

Create a secure TLS connection to the Deploy server



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/deploy_agent/server_connection.rb', line 13

def initialize(agent, server_host, nio_selector, check_certificate=true)
  @agent = agent
  @agent.logger.info "Attempting to connect to #{server_host}"
  @destination_connections = {}
  @nio_selector = nio_selector

  # Create a TCP socket to the Deploy server
  @tcp_socket = TCPSocket.new(server_host, 7777)

  # Configure an OpenSSL context with server vertification
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.verify_mode = check_certificate ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
  # Load the agent certificate and key used to authenticate this agent
  ctx.cert = OpenSSL::X509::Certificate.new(File.read(CERTIFICATE_PATH))
  ctx.key = OpenSSL::PKey::RSA.new(File.read(KEY_PATH))
  # Load the Deploy CA used to verify the server
  ctx.ca_file = CA_PATH

  # Create the secure connection
  @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, ctx)
  @socket.connect
  # Check the remote certificate
  @socket.post_connection_check(server_host) if check_certificate
  # Create send and receive buffers
  @tx_buffer = String.new.force_encoding('BINARY')
  @rx_buffer = String.new.force_encoding('BINARY')

  @nio_monitor = @nio_selector.register(@tcp_socket, :r)
  @nio_monitor.value = self

  @agent.logger.info "Successfully connected to server"
rescue => e
  @agent.logger.info "Something went wrong connecting to server."
  # Sleep between 10 and 20 seconds
  random_sleep = rand(10) + 10
  @agent.logger.info "#{e.to_s} #{e.message}"
  @agent.logger.info "Retrying in #{random_sleep} seconds."
  sleep random_sleep
  retry
end

Instance Attribute Details

#agentObject (readonly)

Returns the value of attribute agent.



9
10
11
# File 'lib/deploy_agent/server_connection.rb', line 9

def agent
  @agent
end

#destination_connectionsObject (readonly)

Returns the value of attribute destination_connections.



9
10
11
# File 'lib/deploy_agent/server_connection.rb', line 9

def destination_connections
  @destination_connections
end

#nio_monitor=(value) ⇒ Object (writeonly)

Sets the attribute nio_monitor

Parameters:

  • value

    the value to set the attribute nio_monitor to.



10
11
12
# File 'lib/deploy_agent/server_connection.rb', line 10

def nio_monitor=(value)
  @nio_monitor = value
end

Instance Method Details

#destination_allowed?(destination) ⇒ Boolean

Returns:

  • (Boolean)


113
114
115
116
117
118
119
120
121
122
123
# File 'lib/deploy_agent/server_connection.rb', line 113

def destination_allowed?(destination)
  return false unless File.file?(ACCESS_PATH)
  DeployAgent.allowed_destinations.each do |network|
    begin
      return true if IPAddr.new(network).include?(destination)
    rescue IPAddr::InvalidAddressError
      # Not a valid IP or netmask, deny and continue
    end
  end
  false
end

#rx_dataObject

Receive and process packets from the control server



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
# File 'lib/deploy_agent/server_connection.rb', line 55

def rx_data
  # Ensure all received data is read
  @rx_buffer << @socket.readpartial(10240)
  while(@socket.pending > 0)
    @rx_buffer << @socket.readpartial(10240)
  end
  # Wait until we have a complete packet of data
  while @rx_buffer.bytesize >=2 && @rx_buffer.bytesize >= @rx_buffer[0,2].unpack('n')[0]
    length = @rx_buffer.slice!(0,2).unpack('n')[0]
    # Extract the packet from the buffered stream
    packet = @rx_buffer.slice!(0,length-2)
    # Check what type of packet we have received
    case packet.bytes[0]
    when 1
      # Process new connection request
      id = packet[1,2].unpack('n')[0]
      host, port = packet[3..-1].split('/', 2)
      @agent.logger.info "[#{id}] Connection request from server: #{host}:#{port}"
      return send_connection_error(id, "Destination address not allowed") unless destination_allowed?(host)

      begin
        # Create conenction to the final destination and save info by id
        @destination_connections[id] = DestinationConnection.new(host, port, id, @nio_selector, self)
      rescue => e
        # Something went wrong, inform the Deploy server
        @agent.logger.error "An error occurred: #{e.message}"
        @agent.logger.error e.backtrace
        send_connection_error(id, e.message)
      end
    when 3
      # Process a connection close request
      id = packet[1,2].unpack('n')[0]
      if @destination_connections[id]
        @agent.logger.info "[#{id}] Close requested by server, closing"
        @destination_connections[id].close
      else
        @agent.logger.info "[#{id}] Close requested by server, not open"
      end
    when 4
      # Data incoming, send it to the backend
      id = packet[1,2].unpack('n')[0]
      @agent.logger.debug "[#{id}] #{packet.bytesize} bytes received from server"
      @destination_connections[id].send_data(packet[3..-1])
    when 5
      # This is a shutdown request. Disconnect and don't re-attempt connection.
      @agent.logger.warn "Server rejected connection. Shutting down."
      @agent.logger.warn packet[1..-1]
      Process.exit(0)
    when 6
      # This is a shutdown request. Disconnect and don't re-attempt connection.
      @agent.logger.warn "Server requested reconnect. Closing connection."
      close
    end
  end
rescue EOFError, Errno::ECONNRESET
  close
end

#send_connection_close(id) ⇒ Object

Notify server of closed connection



136
137
138
# File 'lib/deploy_agent/server_connection.rb', line 136

def send_connection_close(id)
  send_packet([3, id].pack('Cn'))
end

#send_connection_error(id, reason) ⇒ Object

Notify server of failed connection



131
132
133
# File 'lib/deploy_agent/server_connection.rb', line 131

def send_connection_error(id, reason)
  send_packet([2, id, 1, reason].pack('CnCa*'))
end

#send_connection_success(id) ⇒ Object

Notify server of successful connection



126
127
128
# File 'lib/deploy_agent/server_connection.rb', line 126

def send_connection_success(id)
  send_packet([2, id, 0].pack('CnC'))
end

#send_data(id, data) ⇒ Object

Proxy data (coming from the backend) to the Deploy server



141
142
143
# File 'lib/deploy_agent/server_connection.rb', line 141

def send_data(id, data)
  send_packet([4, id, data].pack('Cna*'))
end

#tx_dataObject

Called by event loop to send all waiting packets to the Deploy server



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/deploy_agent/server_connection.rb', line 146

def tx_data
  bytes_sent = @socket.write_nonblock(@tx_buffer[0,1024])
  # Send as much data as possible
  if bytes_sent >= @tx_buffer.bytesize
    @tx_buffer = String.new.force_encoding('BINARY')
    @nio_monitor.interests = :r
  else
    # If we didn't manage to send all the data, leave
    # the remaining data in the send buffer
    @tx_buffer.slice!(0, bytes_sent)
  end
rescue EOFError, Errno::ECONNRESET
  close
end