logstash-output-tcp 6.1.0 → 6.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d30cf25cfbc7e1cc2e72d5e1eb8ab8179d42b31413a549801a012d5e1be7e640
4
- data.tar.gz: 53632382865d4bade1e6d56e9872b1b5db690fd9b866cb4d0952dfa8d1156cee
3
+ metadata.gz: dd2acefd6c26058e3e2afc94abb73de74e2816ca57a6379229909434ad4c72e9
4
+ data.tar.gz: 38633ca4cf74d5360fa3644b9624cb21db997e9cda5ff8846f5ca510b811c133
5
5
  SHA512:
6
- metadata.gz: e48f3511c25df04edb79dfd0425c6a84d18808bce5f8d4910cabe024955e6c16f8b856a8ac7fac01cb7721e7628934101e14e7180497a7c6cc1096169290963e
7
- data.tar.gz: 94758e5adb6be529f4c70715aa0796d1a15bd8d17e91d35c63fdc1b29eccea58c0f36e4c29b0cf911a3691fade34c895922ca9b0384cd3df405f723a97ff642f
6
+ metadata.gz: ef785946ab821ca9347ef5fd04e17909ef573155ce790955640b875582a268363c436c3fa6a6c8c8cc19d086a6a9e6d5b8452448be59fbd80328481a7e0f292e
7
+ data.tar.gz: ac1c3b27079ecb0bb91eaa4a11eea17a1f79d4b6d935e72d82085b34f6731ee2ed6e6240250e4c18429b669d1430875d8ef958aa4c009ae71bb75d3e73f6a414
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 6.1.2
2
+ - Changed the client mode to write using the non-blocking method. [#52](https://github.com/logstash-plugins/logstash-output-tcp/pull/52)
3
+
4
+ ## 6.1.1
5
+ - Fixes an issue where payloads larger than a connection's current TCP window could be silently truncated [#49](https://github.com/logstash-plugins/logstash-output-tcp/pull/49)
6
+
1
7
  ## 6.1.0
2
8
  - Feat: ssl_supported_protocols (TLSv1.3) [#47](https://github.com/logstash-plugins/logstash-output-tcp/pull/47)
3
9
  - Fix: close server and client sockets on plugin close
@@ -56,18 +56,25 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
56
56
 
57
57
  class Client
58
58
 
59
- def initialize(socket, logger)
59
+ ##
60
+ # @param socket [Socket]
61
+ # @param logger_context [#log_warn&#log_error]
62
+ def initialize(socket, logger_context)
60
63
  @socket = socket
61
- @logger = logger
64
+ @logger_context = logger_context
62
65
  @queue = Queue.new
63
66
  end
64
67
 
65
68
  def run
66
69
  loop do
67
70
  begin
68
- @socket.write(@queue.pop)
71
+ remaining_payload = @queue.pop
72
+ while remaining_payload && remaining_payload.bytesize > 0
73
+ written_bytes_size = @socket.write(remaining_payload)
74
+ remaining_payload = remaining_payload.byteslice(written_bytes_size..-1)
75
+ end
69
76
  rescue => e
70
- log_warn 'socket write failed:', e, socket: (@socket ? @socket.to_s : nil)
77
+ @logger_context.log_warn 'socket write failed:', e, socket: (@socket ? @socket.to_s : nil)
71
78
  break
72
79
  end
73
80
  end
@@ -80,7 +87,7 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
80
87
  def close
81
88
  @socket.close
82
89
  rescue => e
83
- log_warn 'socket close failed:', e, socket: (@socket ? @socket.to_s : nil)
90
+ @logger_context.log_warn 'socket close failed:', e, socket: (@socket ? @socket.to_s : nil)
84
91
  end
85
92
  end # class Client
86
93
 
@@ -135,69 +142,82 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
135
142
  require "socket"
136
143
  require "stud/try"
137
144
  @closed = Concurrent::AtomicBoolean.new(false)
145
+ @thread_no = Concurrent::AtomicFixnum.new(0)
138
146
  setup_ssl if @ssl_enable
139
147
 
140
148
  if server?
141
- @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
142
- begin
143
- @server_socket = TCPServer.new(@host, @port)
144
- rescue Errno::EADDRINUSE
145
- @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
146
- raise
147
- end
148
- if @ssl_enable
149
- @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
150
- end # @ssl_enable
151
- @client_threads = Concurrent::Array.new
152
-
153
- @accept_thread = Thread.new(@server_socket) do |server_socket|
154
- LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
155
- loop do
156
- break if @closed.value
157
- client_socket = server_socket.accept_nonblock exception: false
158
- if client_socket == :wait_readable
159
- IO.select [ server_socket ]
160
- next
161
- end
162
- Thread.start(client_socket) do |client_socket|
163
- # monkeypatch a 'peer' method onto the socket.
164
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
165
- @logger.debug("accepted connection", client: client_socket.peer, server: "#{@host}:#{@port}")
166
- client = Client.new(client_socket, @logger)
167
- Thread.current[:client] = client
168
- LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@client_threads.size}")
169
- @client_threads << Thread.current
170
- client.run unless @closed.value
171
- end
149
+ run_as_server
150
+ else
151
+ run_as_client
152
+ end
153
+ end
154
+
155
+ def run_as_server
156
+ @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
157
+ begin
158
+ @server_socket = TCPServer.new(@host, @port)
159
+ rescue Errno::EADDRINUSE
160
+ @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
161
+ raise
162
+ end
163
+ if @ssl_enable
164
+ @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
165
+ end # @ssl_enable
166
+ @client_threads = Concurrent::Array.new
167
+
168
+ @accept_thread = Thread.new(@server_socket) do |server_socket|
169
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
170
+ loop do
171
+ break if @closed.value
172
+ client_socket = server_socket.accept_nonblock exception: false
173
+ if client_socket == :wait_readable
174
+ IO.select [ server_socket ]
175
+ next
176
+ end
177
+ Thread.start(client_socket) do |client_socket|
178
+ # monkeypatch a 'peer' method onto the socket.
179
+ client_socket.extend(::LogStash::Util::SocketPeer)
180
+ @logger.debug("accepted connection", client: client_socket.peer, server: "#{@host}:#{@port}")
181
+ client = Client.new(client_socket, self)
182
+ Thread.current[:client] = client
183
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@thread_no.increment}")
184
+ @client_threads << Thread.current
185
+ client.run unless @closed.value
172
186
  end
173
187
  end
188
+ end
174
189
 
175
- @codec.on_event do |event, payload|
176
- @client_threads.select!(&:alive?)
177
- @client_threads.each do |client_thread|
178
- client_thread[:client].write(payload)
179
- end
190
+ @codec.on_event do |event, payload|
191
+ @client_threads.select!(&:alive?)
192
+ @client_threads.each do |client_thread|
193
+ client_thread[:client].write(payload)
180
194
  end
181
- else
182
- client_socket = nil
183
- @codec.on_event do |event, payload|
184
- begin
185
- client_socket = connect unless client_socket
186
- r,w,e = IO.select([client_socket], [client_socket], [client_socket], nil)
187
- # don't expect any reads, but a readable socket might
188
- # mean the remote end closed, so read it and throw it away.
189
- # we'll get an EOFError if it happens.
190
- client_socket.sysread(16384) if r.any?
191
-
192
- # Now send the payload
193
- client_socket.syswrite(payload) if w.any?
194
- rescue => e
195
- log_warn "client socket failed:", e, host: @host, port: @port, socket: (client_socket ? client_socket.to_s : nil)
196
- client_socket.close rescue nil
197
- client_socket = nil
198
- sleep @reconnect_interval
199
- retry
195
+ end
196
+ end
197
+
198
+ def run_as_client
199
+ client_socket = nil
200
+ @codec.on_event do |event, payload|
201
+ begin
202
+ client_socket = connect unless client_socket
203
+ while payload && payload.bytesize > 0
204
+ begin
205
+ written_bytes_size = client_socket.write_nonblock(payload)
206
+ payload = payload.byteslice(written_bytes_size..-1)
207
+ rescue IO::WaitReadable
208
+ IO.select([client_socket])
209
+ retry
210
+ rescue IO::WaitWritable
211
+ IO.select(nil, [client_socket])
212
+ retry
213
+ end
200
214
  end
215
+ rescue => e
216
+ log_warn "client socket failed:", e, host: @host, port: @port, socket: (client_socket ? client_socket.to_s : nil)
217
+ client_socket.close rescue nil
218
+ client_socket = nil
219
+ sleep @reconnect_interval
220
+ retry
201
221
  end
202
222
  end
203
223
  end
@@ -219,6 +239,18 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
219
239
  end
220
240
  end
221
241
 
242
+ def log_warn(msg, e, backtrace: @logger.debug?, **details)
243
+ details = details.merge message: e.message, exception: e.class
244
+ details[:backtrace] = e.backtrace if backtrace
245
+ @logger.warn(msg, details)
246
+ end
247
+
248
+ def log_error(msg, e, backtrace: @logger.info?, **details)
249
+ details = details.merge message: e.message, exception: e.class
250
+ details[:backtrace] = e.backtrace if backtrace
251
+ @logger.error(msg, details)
252
+ end
253
+
222
254
  private
223
255
 
224
256
  def connect
@@ -235,7 +267,7 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
235
267
  raise
236
268
  end
237
269
  end
238
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
270
+ client_socket.extend(::LogStash::Util::SocketPeer)
239
271
  @logger.debug("opened connection", :client => client_socket.peer)
240
272
  return client_socket
241
273
  rescue => e
@@ -253,16 +285,4 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
253
285
  execution_context.pipeline_id || 'main'
254
286
  end
255
287
 
256
- def log_warn(msg, e, backtrace: @logger.debug?, **details)
257
- details = details.merge message: e.message, exception: e.class
258
- details[:backtrace] = e.backtrace if backtrace
259
- @logger.warn(msg, details)
260
- end
261
-
262
- def log_error(msg, e, backtrace: @logger.info?, **details)
263
- details = details.merge message: e.message, exception: e.class
264
- details[:backtrace] = e.backtrace if backtrace
265
- @logger.error(msg, details)
266
- end
267
-
268
288
  end # class LogStash::Outputs::Tcp
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-output-tcp'
4
- s.version = '6.1.0'
4
+ s.version = '6.1.2'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Writes events over a TCP socket"
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -108,6 +108,33 @@ describe LogStash::Outputs::Tcp do
108
108
  end
109
109
  end
110
110
 
111
+ context "client mode" do
112
+ before { subject.register }
113
+
114
+ let(:config) { super().merge 'mode' => 'client' }
115
+
116
+ it 'writes payload data' do
117
+ Thread.start { sleep 0.25; subject.receive event }
118
+
119
+ socket = server.accept
120
+ read = socket.sysread(100)
121
+
122
+ expect( read.size ).to be > 0
123
+ expect( read ).to eq(JSON.generate(event))
124
+ end
125
+
126
+ it 'writes payload data in multiple operations' do
127
+ full_payload = JSON.generate(event)
128
+ Thread.start { sleep 0.25; subject.receive event }
129
+
130
+ socket = server.accept
131
+ first_read = socket.sysread((full_payload.length / 2))
132
+ second_read = socket.sysread(((full_payload.length / 2) + 1))
133
+
134
+ expect( "#{first_read}#{second_read}" ).to eq(full_payload)
135
+ end
136
+ end
137
+
111
138
  context "when enabling SSL" do
112
139
  let(:config) { super().merge("ssl_enable" => true, 'codec' => 'plain') }
113
140
  context "and not providing a certificate/key pair" do
@@ -227,5 +254,58 @@ describe LogStash::Outputs::Tcp do
227
254
 
228
255
  end
229
256
  end
257
+
258
+ context "and protocol is TLSv1.3" do
259
+ let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
260
+ let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
261
+ let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
262
+
263
+ let(:secure_server) do
264
+ ssl_context = OpenSSL::SSL::SSLContext.new
265
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
266
+ ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(crt_file))
267
+ ssl_context.key = OpenSSL::PKey::RSA.new(File.read(key_file), nil)
268
+ ssl_context.min_version = OpenSSL::SSL::TLS1_3_VERSION
269
+ OpenSSL::SSL::SSLServer.new(server, ssl_context)
270
+ end
271
+
272
+ before(:each) do
273
+ subject.register
274
+ end
275
+
276
+ after(:each) do
277
+ secure_server.close rescue nil
278
+ end
279
+
280
+ let(:message) { "a" }
281
+ let(:buffer) { "" }
282
+
283
+ # This test confirms that this plugin is able to write to a TLS socket
284
+ # multiple times.
285
+ # Previous implementation performed an IO.select first and called sysread
286
+ # if select signaled the socket was ready to read.
287
+ # For TLS1_3, due to control messages it may happen that the underlying
288
+ # socket is marked as readable but there is no new data available,
289
+ # causing a read to block forever.
290
+ # This test will raise a Timeout exception with the old implementation.
291
+ it 'successfully writes two messages' do
292
+ thread = Thread.start do
293
+ expect {
294
+ client = secure_server.accept
295
+ Timeout::timeout(5) do
296
+ buffer << client.sysread(1) # read first message
297
+ subject.receive(message)
298
+ buffer << client.sysread(1) # read second message
299
+ client.close
300
+ end
301
+ }.to_not raise_error
302
+ end
303
+ sleep 0.1 until thread.status == "sleep" # wait for TCP port to open
304
+ subject.receive(message) # send first message to unblock call to `accept`
305
+ thread.join(2)
306
+
307
+ expect(buffer).to eq(message * 2)
308
+ end
309
+ end
230
310
  end
231
311
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-tcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-06 00:00:00.000000000 Z
11
+ date: 2023-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
171
  - !ruby/object:Gem::Version
172
172
  version: '0'
173
173
  requirements: []
174
- rubygems_version: 3.1.6
174
+ rubygems_version: 3.2.33
175
175
  signing_key:
176
176
  specification_version: 4
177
177
  summary: Writes events over a TCP socket