diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 0000000..7a7c481 --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,26 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: Ruby +on: [push] +permissions: + contents: read +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: [ '2.7', '3.0', '3.1', '3.2', '3.3', '3.4' ] + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Run tests + run: bundle exec rake diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9439b92..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: ruby -rvm: -- 1.9.3 -- 2.0 -- 2.1 -- 2.2 -- 2.3 -- 2.4 -sudo: false -script: bundle exec rake test -dist: precise -install: "bundle install --jobs=3 --retry=3" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 1f35893..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,64 +0,0 @@ -PATH - remote: . - specs: - ftpd (2.1.0) - memoizer (~> 1.0) - -GEM - remote: https://rubygems.org/ - specs: - builder (3.2.3) - cucumber (2.4.0) - builder (>= 2.1.2) - cucumber-core (~> 1.5.0) - cucumber-wire (~> 0.0.1) - diff-lcs (>= 1.1.3) - gherkin (~> 4.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (1.5.0) - gherkin (~> 4.0) - cucumber-wire (0.0.1) - diff-lcs (1.3) - double-bag-ftps (0.1.4) - gherkin (4.1.3) - memoizer (1.0.3) - multi_json (1.12.1) - multi_test (0.1.2) - rake (11.3.0) - redcarpet (3.4.0) - rspec (3.6.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-its (1.2.0) - rspec-core (>= 3.0.0) - rspec-expectations (>= 3.0.0) - rspec-mocks (3.6.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-support (3.6.0) - timecop (0.9.1) - yard (0.8.7.6) - -PLATFORMS - ruby - -DEPENDENCIES - cucumber (~> 2.0) - double-bag-ftps (~> 0.1, >= 0.1.4) - ftpd! - rake (~> 11.1) - redcarpet (~> 3.1) - rspec (~> 3.1) - rspec-its (~> 1.0) - timecop (~> 0.7) - yard (~> 0.8.7) - -BUNDLED WITH - 1.15.3 diff --git a/README.md b/README.md index 489aa8e..1797686 100644 --- a/README.md +++ b/README.md @@ -256,20 +256,20 @@ See [RFC Compliance](doc/rfc-compliance.md) for details The tests pass with these Rubies: -* ruby-1.9.3 -* ruby-2.0 -* ruby-2.1 -* ruby-2.2 -* ruby-2.3 -* ruby-2.4 (but see below) +* ruby-2.7 (EOL: 2023-03-31) +* ruby-3.0 (EOL: 2024-03-31) +* ruby-3.1 (EOL: 2025-03-31) +* ruby-3.2 +* ruby-3.3 +* ruby-3.4 For Ruby 1.8, use an ftpd version before 0.8. In your Gemfile: gem 'ftpd', '<0.8' -This gem runs fine in Ruby 2.4, but the tests that use TLS are skipped -in Ruby 2.4. That is because the double_bag_ftps gem that the tests -use for TLS does not work in Ruby 2.4. +For Ruby 2.6, use ftpd version 2.1.0. In your Gemfile: + + gem 'ftpd', '2.1.0' ## OS compatability @@ -278,7 +278,7 @@ use for TLS does not work in Ruby 2.4. Ftpd runs on: * Linux -* OSX +* MacOS ## Windows @@ -302,6 +302,12 @@ major version. ### Tests +On MacOS, you need to add a loopback alias for 127.0.0.2 (used in specs): + +```bash +sudo ifconfig lo0 alias 127.0.0.2 up +``` + To run the cucumber (functional) tests: $ rake test:features @@ -371,6 +377,7 @@ Among those who have improved ftpd are: * Michael de Silva * Mike Ragalie * cransom +* Jonathan Tron If I've forgotten to add you, please remind me, or submit a merge request. diff --git a/cucumber.yml b/cucumber.yml new file mode 100644 index 0000000..fea5edc --- /dev/null +++ b/cucumber.yml @@ -0,0 +1 @@ +default: --publish-quiet diff --git a/features/ftp_server/features.feature b/features/ftp_server/features.feature index 1cbe678..d7598f9 100644 --- a/features/ftp_server/features.feature +++ b/features/ftp_server/features.feature @@ -27,12 +27,14 @@ Feature: Features Scenario: IPV6 Extensions Given the test server is started + And the client connects When the client successfully requests features Then the response should include feature "EPRT" And the response should include feature "EPSV" Scenario: RFC 3659 Extensions Given the test server is started + And the client connects When the client successfully requests features Then the response should include feature "SIZE" Then the response should include feature "MDTM" diff --git a/features/ftp_server/timeout.feature b/features/ftp_server/timeout.feature index ec6b18a..f7c8456 100644 --- a/features/ftp_server/timeout.feature +++ b/features/ftp_server/timeout.feature @@ -5,14 +5,14 @@ Feature: Port So that I can claim RFC compliance Scenario: Session idle too long - Given the test server has session timeout set to 0.5 seconds + Given the test server has session timeout set to 1 seconds And the test server is started And a successful login - When the client is idle for 0.6 seconds + When the client is idle for 1.1 seconds Then the client should not be connected Scenario: Session not idle too long - Given the test server has session timeout set to 0.5 seconds + Given the test server has session timeout set to 1 seconds And the test server is started And a successful login When the client is idle for 0 seconds diff --git a/features/step_definitions/connect.rb b/features/step_definitions/connect.rb index b90a326..83eb744 100644 --- a/features/step_definitions/connect.rb +++ b/features/step_definitions/connect.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'double_bag_ftps' require 'net/ftp' When /^the( \w+)? client connects(?: with (\w+) TLS)?$/ do @@ -17,9 +16,9 @@ end When /^the (\d+)rd client tries to connect$/ do |client_name| - client(client_name).start + client(client_name.to_s).start capture_error do - client(client_name).connect(server.host, server.port) + client(client_name.to_s).connect(server.host, server.port) end end diff --git a/features/support/test_client.rb b/features/support/test_client.rb index 81656e5..549efc0 100644 --- a/features/support/test_client.rb +++ b/features/support/test_client.rb @@ -1,8 +1,15 @@ # frozen_string_literal: true -require 'double_bag_ftps' require 'net/ftp' +if Gem::Version.new(Net::FTP::VERSION) <= Gem::Version.new("0.3.8") && defined? Net::FTP::BufferedSSLSocket + class Net::FTP::BufferedSSLSocket + def shutdown(*args) + @io.__send__(:stop) + end + end +end + class TestClient CannotTestTls = Class.new(StandardError) @@ -135,8 +142,8 @@ def connected? ftp.noop true rescue Net::FTPTempError => e - !!e.to_s =~ /^421/ - rescue EOFError + e.message !~ /^421/ + rescue EOFError, Net::FTPConnectionError false end end @@ -146,7 +153,7 @@ def set_option(option) end private - + RAW_METHOD_REGEX = /^send_(.*)$/ def ftp @@ -180,28 +187,20 @@ def make_ftp def make_tls_ftp(ftps_mode) ensure_can_test_tls - ftp = DoubleBagFTPS.new - context_opts = { - :verify_mode => OpenSSL::SSL::VERIFY_NONE + opts = { + :ssl => { + :verify_mode => OpenSSL::SSL::VERIFY_NONE, + }, + :implicit_ftps => ftps_mode==:implicit } - ftp.ssl_context = DoubleBagFTPS.create_ssl_context(context_opts) - ftp.ftps_mode = ftps_mode - ftp + Net::FTP.new nil, opts end def ensure_can_test_tls - return if can_test_tls? + return if defined?(OpenSSL::SSL) raise CannotTestTls, "Cannot test TLS with this Ruby version" end - def can_test_tls? - !double_bag_ftps_busted? - end - - def double_bag_ftps_busted? - Gem::Dependency.new('', '~> 2.4.0').match?('', RUBY_VERSION) - end - def make_non_tls_ftp Net::FTP.new end diff --git a/ftpd.gemspec b/ftpd.gemspec index 3e700f3..91fdb23 100644 --- a/ftpd.gemspec +++ b/ftpd.gemspec @@ -21,7 +21,7 @@ class Readme README_PATH = File.expand_path("README.md", File.dirname(__FILE__)) private_constant :README_PATH - + def remove_markdown_link(description) regex = %r{ \[ @@ -76,16 +76,16 @@ Gem::Specification.new do |s| s.files += Dir["lib/**/*.rb"] s.homepage = "http://github.com/wconrad/ftpd" s.licenses = ["MIT"] - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.7.8" s.rubygems_version = "2.5.1" s.summary = "Pure Ruby FTP server library" s.add_runtime_dependency("memoizer", "~> 1.0") - s.add_development_dependency("cucumber", "~> 2.0") - s.add_development_dependency("double-bag-ftps", "~> 0.1", ">= 0.1.4") - s.add_development_dependency("rake", "~> 11.1") - s.add_development_dependency("redcarpet", "~> 3.1") + s.add_development_dependency("net-ftp", "~> 0.3") + s.add_development_dependency("cucumber", "~> 9.1") + s.add_development_dependency("rake", "~> 13.1") + s.add_development_dependency("redcarpet", "~> 3.6") s.add_development_dependency("rspec", "~> 3.1") s.add_development_dependency("rspec-its", "~> 1.0") - s.add_development_dependency("timecop", "~> 0.7") - s.add_development_dependency("yard", "~> 0.8.7") + s.add_development_dependency("timecop", "~> 0.9") + s.add_development_dependency("yard", "~> 0.9") end diff --git a/insecure-test-cert.pem b/insecure-test-cert.pem index 5db7394..fab3ec2 100644 --- a/insecure-test-cert.pem +++ b/insecure-test-cert.pem @@ -1,29 +1,50 @@ -----BEGIN CERTIFICATE----- -MIICIzCCAYwCCQDPNA1ZOq8CbzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJV -UzEPMA0GA1UECAwGRGVuaWFsMRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UE -CgwDRGlzMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTIwNjIyMTMyMTI1WhcNMjIw -NjIwMTMyMTI1WjBWMQswCQYDVQQGEwJVUzEPMA0GA1UECAwGRGVuaWFsMRQwEgYD -VQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UECgwDRGlzMRIwEAYDVQQDDAlsb2NhbGhv -c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKyacIYurNopaS1tHCE5VrUb -1tFveP5kWm6kyeE42dYFMcb0wSKofKDWPju+jEwxZ/SLBnF/IvDKqfFH8A7bzdTi -mdtiWZgqMjs1QxFWF3ohoHm0bB2l0zSWufefjSjstSJanazOW4seq3Zm9ut233Mm -7h2fKgmM8mzUIKqqLCFfAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJdwBZm4BI+7y -Ul0TJtzdLuKo5lKJXX/l8v7hlvziR6Vbv0H7bdqJ+5N3DLDDHZ4DEbypP67lxf2i -cyKdbash/nJsMUVUr+MsvJ9VyRRiSyhzqCN/RgaN9nx4Z0fl5I8aQp2qcZi1t8R5 -ZLgk9oqiPOEca6i22DDBSg0cnhBH9Lk= +MIIDjTCCAnWgAwIBAgIUECO44Y4B6XglzVsHPBT80R0AjQowDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By +aW5nZmllbGQxDDAKBgNVBAoMA0RpczESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIz +MTIyOTE5MjkzMFoXDTMzMTIyNjE5MjkzMFowVjELMAkGA1UEBhMCVVMxDzANBgNV +BAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNVBAoMA0RpczES +MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtsUdUMjshetMW7os9eOO4iadPlwxBncS14PLeSb3Lc6wLvU8CTaRA/l1OR76 +p5WDMnoplC+NQ3N+opN//KuFXV5zU9h/0wRy7WynxvVtdu91Zw3ac+1cz9TBhnVf +AFaiwZ7Hz5uWj58sM9zfRiqLO5CSGV5zO7qwk3JNG1e+Tb1gr29sbWKFcPG1bIAl +oPsP7XzQcPq4M2QCdwBtBHDbzuL3IfMualW6oc1VyLcGXmZQl5ooq2yQybQNzdeR +lcVR4QfqprPNpS602DI5DkcqFpY/sIWORdWK6MmjzipWU52HNCRgQQpyoF32/KtY +K94EtDrizLhj7jugmwX57+YIrQIDAQABo1MwUTAdBgNVHQ4EFgQUL7wU5Slj9ztk +RU4ZCoBhqSBbPCowHwYDVR0jBBgwFoAUL7wU5Slj9ztkRU4ZCoBhqSBbPCowDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGTpdr7KmIajGHdngtHWh +N4z40JFbNaivfi5vsm7cLzO+aVybclgQKaaKyOWl3Tp4HtiRp8p6lPFJiwKY/3Vw +V/e7Yr9AQVvuU05Dy76WnyW4oDdbyHIRP54q7xjRTrJMcLGrd3EyrAkEu+p+KGuD +Hy+i6DVFv7SWKsTTFOSGgrq69LoS/dBt3V71vzhTqEGXQGoTEzhT9lDlCe4GnpeL +WuSOu4FLtehYLhigxzzRGagM2jxw8DGarT3ocEWO4oRTXYGyECAawm4PGQnIRYqQ +ps0+6Z1jX/q1Gs011WAIAE56x/xS+aPmrRMpI9448DUiY0lAyD/aFYvujA7U1x4L +Zg== -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCsmnCGLqzaKWktbRwhOVa1G9bRb3j+ZFpupMnhONnWBTHG9MEi -qHyg1j47voxMMWf0iwZxfyLwyqnxR/AO283U4pnbYlmYKjI7NUMRVhd6IaB5tGwd -pdM0lrn3n40o7LUiWp2szluLHqt2Zvbrdt9zJu4dnyoJjPJs1CCqqiwhXwIDAQAB -AoGAcyj/1qchsNVcVXCtCgXFskSGyWnEooa2R4gvIdPak48XrRT0H3mm3XDUSOxT -kyqLn396pxMabunpBRDoPCGvbDdphhcSKIJPRga0LJBnMVp87xeaw0JvNB1EsdzP -xbsXwSt39zjJeAE1IAOgMCHC/GRisvRnkZuKOM7XYe7UAGECQQDeawf/5Zgfcvgc -Bqxv6ZPCJxAh7FKyWDUqa8RTtD0qWuXOYnlVzaEkN8FfimrPdmQBx+eMacsqCrLG -v8hvubt5AkEAxqnzo+IU4bsUdezndEjYKOVH3qs9qO+8bneKCqD4h2k2Db2va8OG -sP44hRMvgkkRiZjHWAUeG+ytgWGU7CK1lwJAe1fvv8GbcxVW8nPg/M8T2f+/upBL -7AtusG/DGIhDw1FVT/bcQvEeA+/HlSw1v4dwPmyVxBCHUnFMY1vH0+20QQJBAI6s -4eCx7qNLM1+Z24RFCJEeUWZWfzsDqcWALnCBuNuvMPXfY8u2KdaVTUwtQjKEfYbf -ZVMOodgWO2mvBkAskVMCQE98evHiZkDEpVU89TbbClYpmGOSRjQTrXEaVePLb0Hr -GwylNdJEAClM4gK+GnXa4m57xs13eBwuXsM+77fdU2I= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2xR1QyOyF60xb +uiz1447iJp0+XDEGdxLXg8t5JvctzrAu9TwJNpED+XU5HvqnlYMyeimUL41Dc36i +k3/8q4VdXnNT2H/TBHLtbKfG9W1273VnDdpz7VzP1MGGdV8AVqLBnsfPm5aPnywz +3N9GKos7kJIZXnM7urCTck0bV75NvWCvb2xtYoVw8bVsgCWg+w/tfNBw+rgzZAJ3 +AG0EcNvO4vch8y5qVbqhzVXItwZeZlCXmiirbJDJtA3N15GVxVHhB+qms82lLrTY +MjkORyoWlj+whY5F1YroyaPOKlZTnYc0JGBBCnKgXfb8q1gr3gS0OuLMuGPuO6Cb +Bfnv5gitAgMBAAECggEABkWH/qS3L9aimxb6iHrlK5/EfD/4tn6H0O/jkuLYqNB9 +Gk6aRdZ19InO/f8Kl41DAEyRvshz8G+j4otgeq8RdDvNUsz5mk5xub5yMmeFElRf +6U5svT1U1qF345FSL9/vlU78wpDvW9QyYnQ1KcJd3tUhTxZyRCD6z3bX+QGr7moF +YV0/TdARG4SXKc02sEA7/712aMD5ruTSAikxkC3JD96XYPP09DPgOapwIFYgnzxF +LX8c96N8osEJZnJxwWazBPqP6NJCKL3l81dtuYeBw/9caGgy8bwD+aZhcqcW/yN2 +41yjIKOsdnU1AIOM1sRgCeTTwQWDvGAFZUo+edF7eQKBgQDhKcPOm0gU2gYAYUVY +DwssXRYKDZIgPt+Bvmw/9bond1wuQZPOhPvap6UkmZlblybDshyFpwtT/c5X6G0d +yGL0ZAwG7XpSxx+2JkAAkEok0Zvqpeulg1aup6N0CsmA1Wk48EwVIsGWB3uxJZdJ +FZRgDolNSPxoba0WtEXNCAVkeQKBgQDPzQrZfgzVh5ThhrLlN8PXEVqA5qbdAvUQ +RTHHdkavav34m8hHgttX0xqkDw8rTDK5qo2hEIrwQojao3ilvKfO99UFh2zJvRdB +K7gIFR57taZQoNL1PPEsYEy52pLgVpxk8Cq5J6Fi0U3umvMwQE8BEYjNdCdbe/Cg +9pkK4Pjw1QKBgQCyvY3T4E7bRqwG9zCuE9shz49kUZuTf3W8MCcMqwuossb5bVNh +WQ35aKXfJROpspayOR0NOCr09QEtpbOhNebf7N/jS/eT6MCSv1CFYgWJrt+f6TDk +hht0sF1ADCQj4sYRzigHizyz1aLqYFX1TW0ox8FVGcBNQetqn5bdiSWGuQKBgCT4 +ihGgB5CGCssFOBboEQPWpo+AefegaxF1/iFKvdC/7Geq77spYPUDLh46P6yoZe36 +ljAtzUppzAd6RiDWq9R663+MVKKf7d8+dAOcHkxMSUbXFjFjolUO3RfD95XKUxDy +WeePUXtPWdo130aaanP6Nqi3Hbl9F5bLyPbSh/fxAoGBANBxFwB1T1mBi7xRTxeV +kYp3aRq8TW/NbHVLoOzOVQ5aQCSsP1oHvtVcwzFqefB0lOI373saNseWdldjHKM3 +KMAtwCrSjULdfuYIRJNfk1WUYnA9aVVx2HXjXlFy6aX336R1/KoP045jK9LEu8bG +Stg6MZ1uwtsXNUgcY8ifRKdL +-----END PRIVATE KEY----- diff --git a/lib/ftpd/cmd_list.rb b/lib/ftpd/cmd_list.rb index 7fa4b85..1394912 100644 --- a/lib/ftpd/cmd_list.rb +++ b/lib/ftpd/cmd_list.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'command_handler' +require 'stringio' module Ftpd diff --git a/lib/ftpd/command_loop.rb b/lib/ftpd/command_loop.rb index 5802b7f..4ea66c5 100644 --- a/lib/ftpd/command_loop.rb +++ b/lib/ftpd/command_loop.rb @@ -57,8 +57,9 @@ def get_command config.log.debug s.sub(/^PASS .*/, 'PASS **FILTERED**') # Filter real password s end - + def gets_with_timeout(socket) + socket = socket.to_io if @session.tls_enabled? && !socket.encrypted? ready = IO.select([socket], nil, nil, config.session_timeout) timeout if ready.nil? ready[0].first.gets diff --git a/lib/ftpd/tls_server.rb b/lib/ftpd/tls_server.rb index a2f18d4..3cf17ac 100644 --- a/lib/ftpd/tls_server.rb +++ b/lib/ftpd/tls_server.rb @@ -99,6 +99,22 @@ def encrypt accept end + def readline + if session + super + else + to_io.readline + end + end + + def write(data) + if session + super(data) + else + to_io.write(data) + end + end + end end diff --git a/rake_tasks/cucumber.rake b/rake_tasks/cucumber.rake index a5ea191..ee7eb06 100644 --- a/rake_tasks/cucumber.rake +++ b/rake_tasks/cucumber.rake @@ -2,7 +2,7 @@ require 'cucumber/rake/task' Cucumber::Rake::Task.new 'test:features' do |t| t.fork = true - t.cucumber_opts = '--format progress' + t.cucumber_opts = ['--format progress'] end task 'test:cucumber' => ['test:features'] diff --git a/spec/protocols_spec.rb b/spec/protocols_spec.rb index d4c1c31..517843f 100644 --- a/spec/protocols_spec.rb +++ b/spec/protocols_spec.rb @@ -41,10 +41,19 @@ def initialize(bind_address, connect_address) Thread.new do queue.enq @listening_socket.accept end + _client_socket = TCPSocket.new(connect_address, port) @connected_socket = queue.deq end + def ipv6_dual_stack? + @connected_socket.local_address.ipv6? && + !@connected_socket.getsockopt( + Socket::IPPROTO_IPV6, + Socket::IPV6_V6ONLY + ).bool + end + end context 'IPV4 server' do @@ -77,13 +86,12 @@ def initialize(bind_address, connect_address) let(:bind_address) {'::1'} let(:connect_address) {'::1'} - let(:connected_socket) do - TestServer.new(bind_address, connect_address).connected_socket - end + let(:test_server) {TestServer.new(bind_address, connect_address)} + let(:connected_socket) {test_server.connected_socket} subject(:protocols) {Protocols.new(connected_socket)} it 'should not support IPV4' do - expect(protocols.supports_protocol?(Protocols::IPV4)).to be_falsey + expect(protocols.supports_protocol?(Protocols::IPV4)).to be(test_server.ipv6_dual_stack?) end it 'should support IPV6' do @@ -92,8 +100,9 @@ def initialize(bind_address, connect_address) it 'should list the supported protocols' do expect(protocols.protocol_codes).to eq [ - Protocols::IPV6, - ] + (Protocols::IPV4 if test_server.ipv6_dual_stack?), + Protocols::IPV6 + ].compact end end