tlspretense 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/.document +6 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +41 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +231 -0
  8. data/Rakefile +44 -0
  9. data/bin/makeder.sh +6 -0
  10. data/bin/tlspretense +7 -0
  11. data/bin/view.sh +3 -0
  12. data/doc/general_setup.rdoc +288 -0
  13. data/doc/linux_setup.rdoc +64 -0
  14. data/lib/certmaker.rb +61 -0
  15. data/lib/certmaker/certificate_factory.rb +106 -0
  16. data/lib/certmaker/certificate_suite_generator.rb +120 -0
  17. data/lib/certmaker/ext_core/hash_indifferent_fetch.rb +12 -0
  18. data/lib/certmaker/runner.rb +27 -0
  19. data/lib/certmaker/tasks.rb +20 -0
  20. data/lib/packetthief.rb +167 -0
  21. data/lib/packetthief/handlers.rb +14 -0
  22. data/lib/packetthief/handlers/abstract_ssl_handler.rb +249 -0
  23. data/lib/packetthief/handlers/proxy_redirector.rb +26 -0
  24. data/lib/packetthief/handlers/ssl_client.rb +87 -0
  25. data/lib/packetthief/handlers/ssl_server.rb +174 -0
  26. data/lib/packetthief/handlers/ssl_smart_proxy.rb +143 -0
  27. data/lib/packetthief/handlers/ssl_transparent_proxy.rb +225 -0
  28. data/lib/packetthief/handlers/transparent_proxy.rb +183 -0
  29. data/lib/packetthief/impl.rb +11 -0
  30. data/lib/packetthief/impl/ipfw.rb +140 -0
  31. data/lib/packetthief/impl/manual.rb +54 -0
  32. data/lib/packetthief/impl/netfilter.rb +109 -0
  33. data/lib/packetthief/impl/pf_divert.rb +168 -0
  34. data/lib/packetthief/impl/pf_rdr.rb +192 -0
  35. data/lib/packetthief/logging.rb +49 -0
  36. data/lib/packetthief/redirect_rule.rb +29 -0
  37. data/lib/packetthief/util.rb +36 -0
  38. data/lib/ssl_test.rb +21 -0
  39. data/lib/ssl_test/app_context.rb +17 -0
  40. data/lib/ssl_test/certificate_manager.rb +33 -0
  41. data/lib/ssl_test/config.rb +79 -0
  42. data/lib/ssl_test/ext_core/io_raw_input.rb +31 -0
  43. data/lib/ssl_test/input_handler.rb +35 -0
  44. data/lib/ssl_test/runner.rb +110 -0
  45. data/lib/ssl_test/runner_options.rb +68 -0
  46. data/lib/ssl_test/ssl_test_case.rb +46 -0
  47. data/lib/ssl_test/ssl_test_report.rb +24 -0
  48. data/lib/ssl_test/ssl_test_result.rb +30 -0
  49. data/lib/ssl_test/test_listener.rb +140 -0
  50. data/lib/ssl_test/test_manager.rb +116 -0
  51. data/lib/tlspretense.rb +13 -0
  52. data/lib/tlspretense/app.rb +52 -0
  53. data/lib/tlspretense/init_runner.rb +115 -0
  54. data/lib/tlspretense/skel/ca/goodcacert.pem +19 -0
  55. data/lib/tlspretense/skel/ca/goodcakey.pem +27 -0
  56. data/lib/tlspretense/skel/config.yml +523 -0
  57. data/lib/tlspretense/version.rb +3 -0
  58. data/packetthief_examples/em_ssl_test.rb +73 -0
  59. data/packetthief_examples/redirector.rb +29 -0
  60. data/packetthief_examples/setup_iptables.sh +24 -0
  61. data/packetthief_examples/ssl_client_simple.rb +27 -0
  62. data/packetthief_examples/ssl_server_simple.rb +44 -0
  63. data/packetthief_examples/ssl_smart_proxy.rb +115 -0
  64. data/packetthief_examples/ssl_transparent_proxy.rb +97 -0
  65. data/packetthief_examples/transparent_proxy.rb +56 -0
  66. data/spec/packetthief/impl/ipfw_spec.rb +98 -0
  67. data/spec/packetthief/impl/manual_spec.rb +65 -0
  68. data/spec/packetthief/impl/netfilter_spec.rb +66 -0
  69. data/spec/packetthief/impl/pf_divert_spec.rb +82 -0
  70. data/spec/packetthief/impl/pf_rdr_spec.rb +133 -0
  71. data/spec/packetthief/logging_spec.rb +78 -0
  72. data/spec/packetthief_spec.rb +47 -0
  73. data/spec/spec_helper.rb +53 -0
  74. data/spec/ssl_test/certificate_manager_spec.rb +222 -0
  75. data/spec/ssl_test/config_spec.rb +76 -0
  76. data/spec/ssl_test/runner_spec.rb +360 -0
  77. data/spec/ssl_test/ssl_test_case_spec.rb +113 -0
  78. data/spec/ssl_test/test_listener_spec.rb +199 -0
  79. data/spec/ssl_test/test_manager_spec.rb +324 -0
  80. data/tlspretense.gemspec +35 -0
  81. metadata +262 -0
@@ -0,0 +1,143 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # This SSL proxy needs a CA certificate (or chain) and private key. It then
5
+ # uses that information to automatically generate host certificates. It
6
+ # creates a host certificate by performing a pre-flight request to the
7
+ # original destination to get its certificate. It then doctors that
8
+ # certificate so that it's keypair is the supplied keypair, and so that it
9
+ # appears to be issued by the CA or last CA in the chain. Note that the
10
+ # supplied key must correspond to the last signing certificate, and that
11
+ # the key will be used as the doctored certificate's new key.
12
+ class SSLSmartProxy < SSLTransparentProxy
13
+
14
+ # How long to wait before giving up on a preflight request. Defaults to 5
15
+ # seconds.
16
+ attr_accessor :preflight_timeout
17
+
18
+ def initialize(tcpsocket, ca_chain, key, logger=nil)
19
+ super(tcpsocket, logger)
20
+
21
+ @preflight_timeout = 5
22
+ if ca_chain.kind_of? Array
23
+ @ca_chain = ca_chain
24
+ else
25
+ @ca_chain = [ca_chain]
26
+ end
27
+ @key = key
28
+
29
+ begin
30
+ # preflight the original destination.
31
+ @ctx.cert = lookup_cert
32
+ @ctx.extra_chain_cert = @ca_chain
33
+ @ctx.key = @key
34
+ rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
35
+ logerror "initialize: Failed to look up cert", :error => e
36
+ close_connection
37
+ end
38
+ end
39
+
40
+ # def client_connected
41
+ # # don't try to connect to dest here.
42
+ # end
43
+ #
44
+
45
+ def servername_cb(sslsock, hostname)
46
+ super
47
+ newctx = sslsock.context.dup
48
+ newctx.cert = lookup_cert(hostname)
49
+ newctx.extra_chain_cert = @ca_chain
50
+ newctx.key = @key
51
+ newctx
52
+ end
53
+
54
+ # Requests a certificate from the original destination.
55
+ def preflight_for_cert(hostname=nil)
56
+ logdebug "prefilight for: #{hostname}"
57
+ begin
58
+ pfctx = OpenSSL::SSL::SSLContext.new
59
+ pfctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
60
+ # Try to use a non-blocking Socket to create a short timeout.
61
+ pfs = Socket.new(:AF_INET, :SOCK_STREAM)
62
+ begin
63
+ pfs.connect_nonblock(Socket.sockaddr_in(dest_port, dest_host))
64
+ rescue Errno::EINPROGRESS
65
+ IO.select(nil, [pfs], nil, preflight_timeout) or raise Errno::ETIMEDOUT, "Connection to #{dest_host}:#{dest_port} timed out after #{preflight_timeout} seconds"
66
+ end
67
+ logdebug "preflight tcp socket connected"
68
+ pfssl = OpenSSL::SSL::SSLSocket.new(pfs, pfctx)
69
+ pfssl.hostname = hostname if hostname and pfssl.respond_to? :hostname
70
+ begin
71
+ pfssl.connect_nonblock
72
+ rescue IO::WaitReadable, IO::WaitWritable
73
+ IO.select([pfssl],[pfssl],nil,preflight_timeout) or raise Errno::ETIMEDOUT, "SSL handshake to #{dest_host}:#{dest_port} timed out #{preflight_timeout} seconds"
74
+ retry
75
+ end
76
+ logdebug "preflight complete"
77
+ return pfssl.peer_cert
78
+ rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
79
+ logerror "Error during preflight SSL connection: #{e} (#{e.class})"
80
+ raise
81
+ ensure
82
+ pfssl.close if pfssl
83
+ end
84
+ end
85
+
86
+
87
+ # Replace the issuer, the public key, and the signature.
88
+ def doctor_cert(oldcert, cacert, key)
89
+ newcert = oldcert.dup
90
+ newcert.issuer = cacert.subject
91
+ newcert.public_key = key.public_key
92
+
93
+ exts = newcert.extensions.dup
94
+ exts.each_index do |i|
95
+ if exts[i].oid == "authorityKeyIdentifier"
96
+ ef = OpenSSL::X509::ExtensionFactory.new
97
+ ef.subject_certificate = newcert
98
+ ef.issuer_certificate = cacert
99
+ newe = ef.create_ext_from_string("authorityKeyIdentifier=keyid:always")
100
+ exts[i] = newe
101
+ end
102
+ end
103
+ newcert.extensions = exts
104
+
105
+ sigalg = case newcert.signature_algorithm
106
+ when /MD5/i
107
+ OpenSSL::Digest::MD5.new
108
+ when /SHA1/i
109
+ OpenSSL::Digest::SHA1.new
110
+ when /SHA256/i
111
+ OpenSSL::Digest::SHA256.new
112
+ else
113
+ raise "Unsupported signing algorithm: #{@ctx.cert.signing_algorithm}"
114
+ end
115
+ newcert.sign(key, sigalg)
116
+ return newcert
117
+ end
118
+
119
+ # Check a class-level cache for an existing doctored certificate that
120
+ # corresponds to the requested hostname (IP address for non-SNI or
121
+ # pre-SNI lookup, and hostnames from SNI lookup).
122
+ def lookup_cert(hostname=nil)
123
+ @@certcache ||= {}
124
+
125
+ if hostname
126
+ cachekey = "#{hostname}:#{dest_port}"
127
+ else
128
+ cachekey = "#{dest_host}:#{dest_port}"
129
+ end
130
+
131
+ unless @@certcache.has_key? cachekey
132
+ logdebug "lookup_cert: cache miss, looking up and doctoring actual cert", :dest => cachekey
133
+ @@certcache[cachekey] = doctor_cert(preflight_for_cert(hostname), @ca_chain[0], @key)
134
+ else
135
+ logdebug "lookup_cert: cache hit", :dest => cachekey
136
+ end
137
+ logdebug "lookup_cert: returning", :subject => @@certcache[cachekey].subject
138
+ @@certcache[cachekey]
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,225 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # Provides a transparent proxy for any TCP connection.
5
+ class SSLTransparentProxy < SSLServer
6
+
7
+ # Represents a connection out to the original destination.
8
+ class SSLProxyConnection < SSLClient
9
+ # Boolean that represents whether this handler has started to
10
+ # close/unbind. Used to ensure there is no unbind-loop between the two
11
+ # connections that make up the proxy.
12
+ attr_accessor :closed
13
+
14
+ # Boolean that represents whether the connection has connected yet.
15
+ attr_accessor :connected
16
+
17
+ # Sets up references to the client proxy connection handler that created
18
+ # this handler.
19
+ def initialize(tcpsocket, client_conn, ctx)
20
+ super(tcpsocket)
21
+ @client = client_conn
22
+ @ctx = ctx
23
+
24
+ @connected = false
25
+ @closed = false
26
+ sni_hostname = @client.dest_hostname if @client.dest_hostname
27
+ end
28
+
29
+ # send on successful handshake instead of on post_init.
30
+ def tls_successful_handshake
31
+ @client.dest_connected
32
+ @client._send_buffer
33
+ end
34
+
35
+ def tls_failed_handshake(e)
36
+ @client.dest_handshake_failed(e)
37
+ end
38
+
39
+ # Transmit data sent by the destinaton to the client.
40
+ def receive_data(data)
41
+ @client.dest_recv(data)
42
+ end
43
+
44
+ # Start the closing process and close the other connection if it is not
45
+ # already closing.
46
+ def unbind
47
+ @client.dest_closed
48
+ self.closed = true
49
+ @client.dest = nil
50
+ @client.close_connection_after_writing if @client and not @client.closed
51
+ end
52
+
53
+ end
54
+
55
+ # This holds a reference to the connection to the destination. If it is
56
+ # null, it hasn't been created yet.
57
+ attr_accessor :dest
58
+
59
+ # This holds a reference to the connection to the client. It is actually
60
+ # self.
61
+ attr_accessor :client
62
+
63
+ attr_reader :client_host, :client_port
64
+
65
+ # Override these before connecting to dest to change the dest connection.
66
+ attr_accessor :dest_host, :dest_port
67
+
68
+ # An internal buffer of packet data received from the client. It will grow until
69
+ # the destination connection connects.
70
+ attr_accessor :buffer
71
+
72
+ # Boolean that represents whether this handler has started to
73
+ # close/unbind. Used to ensure there is no unbind-loop between the two
74
+ # connections that make up the proxy.
75
+ attr_accessor :closed
76
+
77
+ # If a client specifies a TLS hostname extension (SNI) as the hostname,
78
+ # then we can forward that fact on to the real server. We can also use it
79
+ # to choose a certificate to present.
80
+ attr_accessor :dest_hostname
81
+
82
+ # The SSLContext that will be used on the connection to the destination.
83
+ # Initially, its verify_mode is set to OpenSSL::SSL::VERIFY_NONE.
84
+ attr_accessor :dest_ctx
85
+
86
+ def initialize(tcpsocket, logger=nil)
87
+ super
88
+ @closed = false
89
+
90
+ @client = self
91
+ @dest = nil
92
+
93
+ @buffer = []
94
+ @@activeconns ||= {}
95
+
96
+ @client_port, @client_host = Socket.unpack_sockaddr_in(get_peername)
97
+ @dest_port, @dest_host = PacketThief.original_dest(self)
98
+
99
+ if @@activeconns.has_key? "#{client_host}:#{client_port}"
100
+ logwarn "loop detected! Stopping the loop."
101
+ close_connection
102
+ return
103
+ end
104
+
105
+ @dest_ctx = OpenSSL::SSL::SSLContext.new
106
+ @dest_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
107
+
108
+ end
109
+
110
+ # Just calls client_connected to keep things straightforward.
111
+ def tls_successful_handshake
112
+ client_connected
113
+ end
114
+
115
+ def receive_data(data)
116
+ client_recv data
117
+ end
118
+
119
+ # Start the closing process and close the other connection if it is not
120
+ # already closing.
121
+ def unbind
122
+ client_closed
123
+ @@activeconns.delete "#{client_host}:#{client_port}"
124
+ self.closed = true
125
+ # @dest.client = nil if @dest
126
+ @dest.close_connection_after_writing if @dest and not @dest.closed
127
+ end
128
+
129
+ # Initiate the connection to @dest_host:@dest_port.
130
+ def connect_to_dest
131
+ return if @dest
132
+ @dest = SSLProxyConnection.connect(@dest_host, @dest_port, self, @dest_ctx)
133
+ newport, newhost = Socket::unpack_sockaddr_in(@dest.get_sockname)
134
+ # Add the new connection to the list to prevent loops.
135
+ @@activeconns["#{newhost}:#{newport}"] = "#{dest_host}:#{dest_port}"
136
+ end
137
+
138
+ def _send_buffer
139
+ @buffer.each do |pkt|
140
+ @dest.send_data pkt
141
+ end
142
+ @buffer = []
143
+ end
144
+
145
+
146
+ # Queues up data to send to the remote host, only sending it if the
147
+ # connection to the remote host exists.
148
+ def send_to_dest(data)
149
+ @buffer << data
150
+ _send_buffer if @dest
151
+ end
152
+
153
+ # Sends data back to the client
154
+ def send_to_client(data)
155
+ send_data data
156
+ end
157
+
158
+ # Returns the certificate chain for the destination, or nil if the
159
+ # destination connection does not exist yet.
160
+ def dest_cert_chain
161
+ return @dest.sslsocket.peer_cert_chain if @dest
162
+ nil
163
+ end
164
+
165
+ #### Callbacks
166
+
167
+ # Set _dest_hostname_ in addition to the default behavior.
168
+ def servername_cb(sslsock, hostname)
169
+ @dest_hostname = hostname
170
+
171
+ super(sslsock, hostname)
172
+ end
173
+
174
+ # This method is called when a client connects, and the TLS handhsake has
175
+ # completed. The default behavior is to begin initating the connection to
176
+ # the original destination. Override this method to change its behavior.
177
+ def client_connected
178
+ connect_to_dest
179
+ end
180
+
181
+ # This method is called when the TLS handshake between the client and the
182
+ # proxy fails. It does nothing by default.
183
+ def client_handshake_failed
184
+ end
185
+
186
+ # This method is called when the proxy receives data from the client
187
+ # connection. The default behavior is to call send_to_dest(data) in order
188
+ # to foward the data on to the original destination. Override this method
189
+ # to analyze the data, or modify it before sending it on.
190
+ def client_recv(data)
191
+ send_to_dest data
192
+ end
193
+
194
+ # Called when the client connection closes. At present, it only provides
195
+ # informational utility.
196
+ def client_closed
197
+ end
198
+
199
+ # Called when the connection to and the TLS handshake between the proxy
200
+ # and the destination succeeds. The default behavior does nothing.
201
+ def dest_connected
202
+ end
203
+
204
+ # Called when the TLS handshake between the proxy and the destination
205
+ # fails.
206
+ def dest_handshake_failed(e)
207
+ end
208
+
209
+ # Called when the proxy receives data from the destination connection.
210
+ # The default behavior calls #dest_recv() to send the data to the client.
211
+ #
212
+ # Override it to analyze or modify the data.
213
+ def dest_recv(data)
214
+ send_to_client data
215
+ end
216
+
217
+ # Called when the original destination connection closes. At present, it only provides
218
+ # informational utility.
219
+ def dest_closed
220
+ end
221
+
222
+ end
223
+
224
+ end
225
+ end
@@ -0,0 +1,183 @@
1
+ module PacketThief
2
+ module Handlers
3
+
4
+ # Provides a transparent proxy for any TCP connection.
5
+ class TransparentProxy < ::EM::Connection
6
+ include Logging
7
+
8
+ # Represents a connection out to the original destination.
9
+ module ProxyConnection
10
+ # Boolean that represents whether this handler has started to
11
+ # close/unbind. Used to ensure there is no unbind-loop between the two
12
+ # connections that make up the proxy.
13
+ attr_accessor :closing
14
+
15
+ # Boolean that represents whether the connection has connected yet.
16
+ attr_accessor :connected
17
+
18
+ # Sets up references to the client proxy connection handler that created
19
+ # this handler.
20
+ def initialize(client_conn)
21
+ @client = client_conn
22
+
23
+ @connected = false
24
+ @closing = false
25
+ end
26
+
27
+ def post_init
28
+ @client._send_buffer
29
+ end
30
+
31
+ # Transmit data sent by the destinaton to the client.
32
+ def receive_data(data)
33
+ @client.dest_recv(data)
34
+ end
35
+
36
+ # Start the closing process and close the other connection if it is not
37
+ # already closing.
38
+ def unbind
39
+ @client.dest_closed
40
+ self.closing = true
41
+ @client.close_connection_after_writing if @client and not @client.closing
42
+ end
43
+
44
+ end
45
+
46
+ # This holds a reference to the connection to the destination. If it is
47
+ # null, it hasn't been created yet.
48
+ attr_accessor :dest
49
+
50
+ # This holds a reference to the connection to the client. It is actually
51
+ # self.
52
+ attr_accessor :client
53
+
54
+ attr_reader :client_host, :client_port
55
+
56
+ # Override these before connecting to dest to change the dest connection.
57
+ attr_accessor :dest_host, :dest_port
58
+
59
+ # An internal buffer of packet data received from the client. It will grow until
60
+ # the destination connection connects.
61
+ attr_accessor :buffer
62
+
63
+ # Boolean that represents whether this handler has started to
64
+ # close/unbind. Used to ensure there is no unbind-loop between the two
65
+ # connections that make up the proxy.
66
+ attr_accessor :closing
67
+
68
+ # When the proxy should connect to a destination.
69
+ attr_accessor :when_to_connect_to_dest
70
+
71
+ def initialize(log=nil)
72
+ @logger = log
73
+ end
74
+
75
+ def post_init
76
+ @closing = false
77
+
78
+ @client = self
79
+ @dest = nil
80
+
81
+ @buffer = []
82
+ @@activeconns ||= {}
83
+
84
+ @client_port, @client_host = Socket.unpack_sockaddr_in(get_peername)
85
+ @dest_port, @dest_host = PacketThief.original_dest(self)
86
+
87
+ logdebug "Client connected", :client_host => client_host, :client => "#{@client_host}:#{@client_port}", :orig_dest => "#{@dest_host}:#{@dest_port}"
88
+
89
+ if @@activeconns.has_key? "#{client_host}:#{client_port}"
90
+ puts "Warning: loop detected! Stopping the loop."
91
+ close_connection
92
+ return
93
+ end
94
+
95
+ client_connected
96
+ end
97
+
98
+ def receive_data(data)
99
+ client_recv data
100
+ end
101
+
102
+ # Start the closing process and close the other connection if it is not
103
+ # already closing.
104
+ def unbind
105
+ client_closed
106
+ @@activeconns.delete "#{client_host}:#{client_port}"
107
+ self.closing = true
108
+ @dest.close_connection_after_writing if @dest and not @dest.closing
109
+ end
110
+
111
+
112
+ # Initiate the connection to @dest_host:@dest_port.
113
+ def connect_to_dest
114
+ logdebug "Connecting to #{@dest_host}:#{@dest_port}"
115
+ @dest = ::EM.connect(@dest_host, @dest_port, ProxyConnection, self)
116
+ newport, newhost = Socket::unpack_sockaddr_in(@dest.get_sockname)
117
+ # Add the new connection to the list to prevent loops.
118
+ @@activeconns["#{newhost}:#{newport}"] = "#{dest_host}:#{dest_port}"
119
+ end
120
+
121
+ def _send_buffer
122
+ @buffer.each do |pkt|
123
+ @dest.send_data pkt
124
+ end
125
+ @buffer = []
126
+ end
127
+
128
+
129
+ # Queues up data to send to the remote host, only sending it if the
130
+ # connection to the remote host exists.
131
+ def send_to_dest(data)
132
+ logdebug "sending to dest", :data => data
133
+ @buffer << data
134
+ _send_buffer if @dest
135
+ end
136
+
137
+ # Sends data back to the client
138
+ def send_to_client(data)
139
+ send_data data
140
+ end
141
+
142
+
143
+ # This method is called when a client connects. The default behavior is
144
+ # to begin initating the connection to the original destination. Override
145
+ # this method to change its behavior.
146
+ def client_connected
147
+ connect_to_dest
148
+ end
149
+
150
+ # This method is called when the proxy receives data from the client
151
+ # connection. The default behavior is to call send_to_dest(data) in order
152
+ # to foward the data on to the original destination. Override this method
153
+ # to analyze the data, or modify it before sending it on.
154
+ def client_recv(data)
155
+ logdebug("received from client", :data => data)
156
+ send_to_dest data
157
+ end
158
+
159
+ # Called when the proxy receives data from the destination connection.
160
+ # The default behavior calls #dest_recv() to send the data to the client.
161
+ #
162
+ # Override it to analyze or modify the data.
163
+ def dest_recv(data)
164
+ logdebug("received from dest", :data => data)
165
+ send_to_client data
166
+ end
167
+
168
+ # Called when the client connection closes. At present, it only provides
169
+ # informational utility.
170
+ def client_closed
171
+ logdebug("client closed connection")
172
+ end
173
+
174
+ # Called when the original destination connection closes. At present, it only provides
175
+ # informational utility.
176
+ def dest_closed
177
+ logdebug("dest closed connection")
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+ end