From: Tanaka Akira Date: 2005-03-02T22:24:21+09:00 Subject: [ruby-dev:25794] OpenSSL::SSL::SSLSocket and IO OpenSSL::SSL::SSLSocket と IO との一貫性を調べようと思ってちょっと調べ たところ、いろいろ気がつきました。 * SSL_ERROR_WANT_WRITE:, SSL_ERROR_WANT_READ になったときに busy loop になることがあります 少なくとも、ossl_ssl_read, ossl_ssl_write では次のようにすると busy loop になります。 ossl_start_ssl のは確認していません。 % cat sslpair.rb require 'openssl' require 'socket' module SSLPair def server host = "127.0.0.1" port = 0 key = OpenSSL::PKey::RSA.new(512) cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 0 name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) cert.subject = name cert.issuer = name cert.not_before = Time.now cert.not_after = Time.now + 3600 cert.public_key = key.public_key ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) cert.extensions = [ ef.create_extension("basicConstraints","CA:FALSE"), ef.create_extension("subjectKeyIdentifier","hash"), ef.create_extension("extendedKeyUsage","serverAuth"), ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature") ] ef.issuer_certificate = cert cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") cert.sign(key, OpenSSL::Digest::SHA1.new) ctx = OpenSSL::SSL::SSLContext.new() ctx.key = key ctx.cert = cert tcps = TCPServer.new(host, port) ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) return ssls end def client(port) host = "127.0.0.1" ctx = OpenSSL::SSL::SSLContext.new() s = TCPSocket.new(host, port) ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) ssl.connect ssl.sync_close = true ssl end def ssl_pair ssls = server th = Thread.new { ns = ssls.accept ssls.close ns } port = ssls.to_io.addr[1] c = client(port) s = th.value return c, s end end include SSLPair % strace ./ruby -I. -r sslpair.rb -e 'p $$; s1, s2 = ssl_pair; s1.sysread(10)' ... read(7, 0x81f9b60, 5) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 2508 read(7, 0x81f9b60, 5) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 2508 read(7, 0x81f9b60, 5) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 2508 ... % strace ./ruby -Iext/openssl/lib -I.ext -I.ext/i686-linux -I. -r sslpair.rb -e 'p $$; s1, s2 = ssl_pair; s1.write "a" * 200000' ... write(7, "\f\342\274\4Z\230\206]>h\344\212{\342r\227P\2111\376\0"..., 370) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 6050 write(7, "\f\342\274\4Z\230\206]>h\344\212{\342r\227P\2111\376\0"..., 370) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 6050 write(7, "\f\342\274\4Z\230\206]>h\344\212{\342r\227P\2111\376\0"..., 370) = -1 EAGAIN (Resource temporarily unavailable) getpid() = 6050 write(7, "\f\342\274\4Z\230\206]>h\344\212{\342r\227P\2111\376\0"..., 370) = -1 EAGAIN (Resource temporarily unavailable) ... * fill_rbuff で、Errno::EAGAIN は Ruby レベルには出ないようなので、 rescue するのは不要ではないでしょうか * consume_rbuff で、@eof を nil に設定し直すのは変だと思うのですが、いっ たん EOF になってからさらに通信が行われることがあるのでしょうか? * read で、(今では) read(0) は "" を返さないと、IO と一貫しません * readline, readchar で、EOFErorr という typo があります * getc で to_i ではバイトの値を得られません * eof? で、バッファが空のときは実際に読んで確認する必要があります * puts で、出力レコードセパレータとして $/ を使っています * readpartial を実装してみました Index: ext/openssl/ossl_ssl.c =================================================================== RCS file: /src/ruby/ext/openssl/ossl_ssl.c,v retrieving revision 1.19 diff -u -p -r1.19 ossl_ssl.c --- ext/openssl/ossl_ssl.c 15 Feb 2005 17:50:46 -0000 1.19 +++ ext/openssl/ossl_ssl.c 2 Mar 2005 12:44:03 -0000 @@ -434,7 +434,7 @@ ossl_ssl_setup(VALUE self) } static void -ossl_start_ssl(SSL *ssl, int (*func)()) +ossl_start_ssl(SSL *ssl, int fd, int (*func)()) { int ret; @@ -442,9 +442,11 @@ ossl_start_ssl(SSL *ssl, int (*func)()) if((ret = func(ssl)) > 0) break; switch(SSL_get_error(ssl, ret)){ case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); - continue; + rb_io_wait_readable(fd); + continue; default: ossl_raise(eSSLError, NULL); } @@ -456,12 +458,14 @@ ossl_ssl_connect(VALUE self) { SSL *ssl; VALUE cb; + OpenFile *fptr; ossl_ssl_setup(self); Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); cb = ossl_sslctx_get_verify_cb(ossl_ssl_get_ctx(self)); SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)cb); - ossl_start_ssl(ssl, SSL_connect); + ossl_start_ssl(ssl, fptr->fd, SSL_connect); return self; } @@ -471,12 +475,14 @@ ossl_ssl_accept(VALUE self) { SSL *ssl; VALUE cb; + OpenFile *fptr; ossl_ssl_setup(self); Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); cb = ossl_sslctx_get_verify_cb(ossl_ssl_get_ctx(self)); SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)cb); - ossl_start_ssl(ssl, SSL_accept); + ossl_start_ssl(ssl, fptr->fd, SSL_accept); return self; } @@ -512,8 +518,10 @@ ossl_ssl_read(int argc, VALUE *argv, VAL case SSL_ERROR_ZERO_RETURN: rb_eof_error(); case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fptr->fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); + rb_io_wait_readable(fptr->fd); continue; case SSL_ERROR_SYSCALL: if(ERR_peek_error() == 0 && nread == 0) rb_eof_error(); @@ -542,9 +550,11 @@ ossl_ssl_write(VALUE self, VALUE str) { SSL *ssl; int nwrite = 0; + OpenFile *fptr; StringValue(str); Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); if (ssl) { for (;;){ @@ -553,9 +563,11 @@ ossl_ssl_write(VALUE self, VALUE str) case SSL_ERROR_NONE: goto end; case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fptr->fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); - continue; + rb_io_wait_readable(fptr->fd); + continue; case SSL_ERROR_SYSCALL: rb_eof_error(); default: Index: ext/openssl/lib/openssl/buffering.rb =================================================================== RCS file: /src/ruby/ext/openssl/lib/openssl/buffering.rb,v retrieving revision 1.8 diff -u -p -r1.8 buffering.rb --- ext/openssl/lib/openssl/buffering.rb 15 Feb 2005 17:50:47 -0000 1.8 +++ ext/openssl/lib/openssl/buffering.rb 2 Mar 2005 12:44:04 -0000 @@ -32,8 +32,6 @@ module Buffering @rbuffer = "" unless defined? @rbuffer begin @rbuffer << self.sysread(BLOCK_SIZE) - rescue Errno::EAGAIN - retry rescue EOFError @eof = true end @@ -41,7 +39,6 @@ module Buffering def consume_rbuff(size=nil) if @rbuffer.size == 0 - @eof = nil nil else size = @rbuffer.size unless size @@ -54,6 +51,14 @@ module Buffering public def read(size=nil, buf=nil) + if size == 0 + if buf + buf.clear + return buf + else + return "" + end + end fill_rbuff unless defined? @rbuffer @eof ||= nil until @eof @@ -68,6 +73,27 @@ module Buffering (size && ret.empty?) ? nil : ret end + def readpartial(maxlen, buf=nil) + if maxlen == 0 + if buf + buf.clear + return buf + else + return "" + end + end + if !defined?(@rbuffer) || @rbuffer.size == 0 + return sysread(maxlen, buf) + end + ret = consume_rbuff(maxlen) + if buf + buf.replace(ret) + ret = buf + end + raise EOFError if ret.empty? + ret + end + def gets(eol=$/) fill_rbuff unless defined? @rbuffer idx = @rbuffer.index(eol) @@ -101,13 +127,13 @@ module Buffering end def readline(eol=$/) - raise EOFErorr if eof? + raise EOFError if eof? gets(eol) end def getc c = read(1) - c ? c.to_i : nil + c ? c[0] : nil end def each_byte @@ -117,7 +143,7 @@ module Buffering end def readchar - raise EOFErorr if eof? + raise EOFError if eof? getc end @@ -127,6 +153,7 @@ module Buffering def eof? @eof ||= nil + fill_rbuff if !@eof && (!defined?(@rbuffer) || @rbuffer.size == 0) @eof && @rbuffer.size == 0 end alias eof eof? @@ -168,8 +195,8 @@ module Buffering s = "" args.each{|arg| s << arg.to_s - unless /#{$/}\z/o =~ s - s << $/ + unless /#{Regexp.quote $\}\z/o =~ s + s << $\ end } do_write(s) Index: test/openssl/test_pair.rb =================================================================== RCS file: test/openssl/test_pair.rb diff -N test/openssl/test_pair.rb --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/openssl/test_pair.rb 2 Mar 2005 12:44:12 -0000 @@ -0,0 +1,157 @@ +begin + require "openssl" +rescue LoadError +end +require 'test/unit' + +if defined?(OpenSSL) + +require 'socket' +dir = File.expand_path(__FILE__) +2.times {dir = File.dirname(dir)} +$:.replace([File.join(dir, "ruby")] | $:) +require 'ut_eof' + +module SSLPair + def server + host = "127.0.0.1" + port = 0 + key = OpenSSL::PKey::RSA.new(512) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + 3600 + cert.public_key = key.public_key + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier","hash"), + ef.create_extension("extendedKeyUsage","serverAuth"), + ef.create_extension("keyUsage", + "keyEncipherment,dataEncipherment,digitalSignature") + ] + ef.issuer_certificate = cert + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.sign(key, OpenSSL::Digest::SHA1.new) + ctx = OpenSSL::SSL::SSLContext.new() + ctx.key = key + ctx.cert = cert + tcps = TCPServer.new(host, port) + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + return ssls + end + + def client(port) + host = "127.0.0.1" + ctx = OpenSSL::SSL::SSLContext.new() + s = TCPSocket.new(host, port) + ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) + ssl.connect + ssl.sync_close = true + ssl + end + + def ssl_pair + ssls = server + th = Thread.new { + ns = ssls.accept + ssls.close + ns + } + port = ssls.to_io.addr[1] + c = client(port) + s = th.value + if block_given? + begin + yield c, s + ensure + c.close unless c.closed? + s.close unless s.closed? + end + else + return c, s + end + end +end + +class OpenSSL::TestEOF1 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s2 << content; s2.close } + yield s1 + end +end + +class OpenSSL::TestEOF2 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s1 << content; s1.close } + yield s2 + end +end + +class OpenSSL::TestPair < Test::Unit::TestCase + include SSLPair + + def test_getc + ssl_pair {|s1, s2| + s1 << "a" + assert_equal(?a, s2.getc) + } + end + + def test_readpartial + ssl_pair {|s1, s2| + s2.write "a\nbcd" + assert_equal("a\n", s1.gets) + assert_equal("bcd", s1.readpartial(10)) + s2.write "efg" + assert_equal("efg", s1.readpartial(10)) + s2.close + assert_raise(EOFError) { s1.readpartial(10) } + assert_raise(EOFError) { s1.readpartial(10) } + assert_equal("", s1.readpartial(0)) + } + end + + def test_readall + ssl_pair {|s1, s2| + s2.close + assert_equal("", s1.read) + } + end + + def test_readline + ssl_pair {|s1, s2| + s2.close + assert_raise(EOFError) { s1.readline } + } + end + + def test_puts_meta + ssl_pair {|s1, s2| + begin + $\ = '*' + s1.puts 'a' + s1.close + assert_equal("a*", s2.read) + ensure + $\ = "\n" + end + } + end + +end + +end Index: test/ruby/ut_eof.rb =================================================================== RCS file: /src/ruby/test/ruby/ut_eof.rb,v retrieving revision 1.8 diff -u -p -r1.8 ut_eof.rb --- test/ruby/ut_eof.rb 6 Dec 2004 08:40:30 -0000 1.8 +++ test/ruby/ut_eof.rb 2 Mar 2005 12:44:13 -0000 @@ -83,6 +83,19 @@ module TestEOF } end + def test_eof_2 + open_file("") {|f| + assert_equal("", f.read) + assert(f.eof?) + } + end + + def test_eof_3 + open_file("") {|f| + assert(f.eof?) + } + end + module Seek def open_file_seek(content, pos) open_file(content) do |f| どんなもんでしょう? -- [田中 哲][たなか あきら][Tanaka Akira]