From 985f91eed7bf7e467f4d918e4922e6d51da2176b Mon Sep 17 00:00:00 2001 From: D062794 Date: Thu, 7 Apr 2022 16:37:21 +0200 Subject: [PATCH 1/3] Bump netaddr gem due to CVE-2019-17383 --- src/Gemfile.lock | 4 ++-- src/bosh-director/bosh-director.gemspec | 2 +- src/vendor/cache/netaddr-1.5.1.gem | Bin 41472 -> 0 bytes src/vendor/cache/netaddr-2.0.5.gem | Bin 0 -> 23552 bytes 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 src/vendor/cache/netaddr-1.5.1.gem create mode 100644 src/vendor/cache/netaddr-2.0.5.gem diff --git a/src/Gemfile.lock b/src/Gemfile.lock index 2b74591989a..7d7f62df6b1 100644 --- a/src/Gemfile.lock +++ b/src/Gemfile.lock @@ -43,7 +43,7 @@ PATH logging (~> 2.2.2) membrane (~> 1.1.0) nats-pure (~> 0.6.2) - netaddr (~> 1.5.0) + netaddr (~> 2.0.5) openssl prometheus-client (~> 1.0.0) puma @@ -175,7 +175,7 @@ GEM mysql2 (0.5.3) nats-pure (0.6.2) net-ssh (5.2.0) - netaddr (1.5.1) + netaddr (2.0.5) netrc (0.11.0) nio4r (2.5.8) openssl (3.0.0) diff --git a/src/bosh-director/bosh-director.gemspec b/src/bosh-director/bosh-director.gemspec index 32b7a99b47b..7c03ebc594e 100644 --- a/src/bosh-director/bosh-director.gemspec +++ b/src/bosh-director/bosh-director.gemspec @@ -49,7 +49,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'membrane', '~>1.1.0' spec.add_dependency 'nats-pure', '~>0.6.2' spec.add_dependency 'openssl' - spec.add_dependency 'netaddr', '~>1.5.0' + spec.add_dependency 'netaddr', '~>2.0.5' spec.add_dependency 'prometheus-client','~>1.0.0' spec.add_dependency 'puma' spec.add_dependency 'rack-test', '~>0.6.2' # needed for console diff --git a/src/vendor/cache/netaddr-1.5.1.gem b/src/vendor/cache/netaddr-1.5.1.gem deleted file mode 100644 index c7d3e4dec1365967cb8ed8efbd5575b73438851b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41472 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 F{sB#fRP6u& diff --git a/src/vendor/cache/netaddr-2.0.5.gem b/src/vendor/cache/netaddr-2.0.5.gem new file mode 100644 index 0000000000000000000000000000000000000000..0e288ae7bd2d2c16054230009a07a0bbba1d1e03 GIT binary patch literal 23552 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 G;J*NUQGt>G literal 0 HcmV?d00001 From d40d9b737781d39331d91771beb5cd07f830b656 Mon Sep 17 00:00:00 2001 From: D062794 Date: Fri, 1 Apr 2022 18:55:57 +0200 Subject: [PATCH 2/3] Incorporate netaddr v2 changes --- .../lib/bosh/director/cidr_range_combiner.rb | 10 +- .../deployment_plan/ip_provider/ip_repo.rb | 26 ++-- .../deployment_plan/manual_network.rb | 12 +- .../deployment_plan/manual_network_subnet.rb | 59 ++++---- .../network_parser/name_servers_parser.rb | 11 +- .../deployment_plan/stages/create_network.rb | 30 ++-- .../director/deployment_plan/vip_network.rb | 2 +- .../lib/bosh/director/ip_util.rb | 135 ++++++++++++++---- .../lib/bosh/director/metrics_collector.rb | 2 +- .../lib/bosh/director/models/ip_address.rb | 9 +- .../lib/bosh/director/network_reservation.rb | 3 +- src/bosh-director/lib/cloud/dummy.rb | 2 +- src/bosh-director/spec/blueprints.rb | 2 +- .../spec/unit/cidr_range_combiner_spec.rb | 38 ++--- ...ange_address_to_be_string_for_ipv6_spec.rb | 6 +- .../compilation_instance_pool_spec.rb | 2 +- .../instance_group_networks_parser_spec.rb | 2 +- .../instance_network_reservations_spec.rb | 6 +- .../instance_plan_factory_spec.rb | 2 +- .../deployment_plan/instance_plan_spec.rb | 4 +- .../deployment_plan/instance_planner_spec.rb | 2 +- .../ip_provider/database_ip_repo_ipv6_spec.rb | 2 +- .../ip_provider/ip_provider_spec.rb | 10 +- .../ip_provider/ip_repo_spec.rb | 2 +- .../deployment_plan/manual_network_spec.rb | 4 +- .../manual_network_subnet_spec.rb | 36 ++--- .../name_servers_parser_spec.rb | 2 +- .../network_planner/planner_spec.rb | 2 +- .../reservation_reconciler_spec.rb | 24 ++-- .../availability_zone_picker_spec.rb | 2 +- .../networks_to_static_ips_spec.rb | 2 +- .../placement_planner/plan_spec.rb | 6 +- ...tatic_ips_availability_zone_picker_spec.rb | 4 +- .../stages/create_network_spec.rb | 27 ++++ .../unit/deployment_plan/vip_network_spec.rb | 2 +- src/bosh-director/spec/unit/ip_util_spec.rb | 12 +- .../spec/unit/jobs/vm_state_spec.rb | 18 +-- .../spec/unit/metrics_collector_spec.rb | 4 +- .../spec/unit/models/ip_address_spec.rb | 4 +- src/bosh-director/spec/unit/models/vm_spec.rb | 4 +- .../spec/unit/orphaned_vm_deleter_spec.rb | 12 +- .../global_networking/failing_deploy_spec.rb | 2 +- .../support/networking_manifest_helper.rb | 8 +- 43 files changed, 336 insertions(+), 218 deletions(-) diff --git a/src/bosh-director/lib/bosh/director/cidr_range_combiner.rb b/src/bosh-director/lib/bosh/director/cidr_range_combiner.rb index 34e9e15abe9..ff09b3ad032 100644 --- a/src/bosh-director/lib/bosh/director/cidr_range_combiner.rb +++ b/src/bosh-director/lib/bosh/director/cidr_range_combiner.rb @@ -10,18 +10,18 @@ def combine_ranges(cidr_ranges) private def stringify_tuples(cidr_tuples) - cidr_tuples.map { |tuple| [tuple[0].ip, tuple[1].ip] } + cidr_tuples.map { |tuple| [tuple[0].to_s, tuple[1].to_s] } end def sort_ranges(reserved_ranges) reserved_ranges.sort do |e1, e2| - e1.to_i <=> e2.to_i + e1.network.addr <=> e2.network.addr end end def min_max_tuples(sorted_reserved_ranges) sorted_reserved_ranges.map do |r| - [r.first(Objectify: true), r.last(Objectify: true)] + [r.nth(0), r.nth(r.len - 1)] end end @@ -40,11 +40,11 @@ def combine_adjacent_ranges(range_tuples) can_combine = false break end - if (range_tuple[1].succ == next_range_tuple[0]) + if (range_tuple[1].next.addr == next_range_tuple[0].addr) range_tuple[1] = next_range_tuple[1] i += 1 # does not cover all cases: 10/32, 10/8 - elsif ((range_tuple[0] < next_range_tuple[0]) && (range_tuple[1] > next_range_tuple[1])) + elsif ((range_tuple[0].addr < next_range_tuple[0].addr) && (range_tuple[1].addr > next_range_tuple[1].addr)) i += 1 else can_combine = false diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb index 6d4a705942a..cc8849343e0 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb @@ -11,7 +11,7 @@ def initialize(logger) def delete(ip) cidr_ip = CIDRIP.new(ip) - ip_address = Bosh::Director::Models::IpAddress.first(address_str: cidr_ip.to_i.to_s) + ip_address = Bosh::Director::Models::IpAddress.first(address_str: cidr_ip.stringify) if ip_address @logger.debug("Releasing ip '#{cidr_ip}'") @@ -50,8 +50,8 @@ def allocate_dynamic_ip(reservation, subnet) retry end - @logger.debug("Allocated dynamic IP '#{ip_address.ip}' for #{reservation.network.name}") - ip_address.to_i + @logger.debug("Allocated dynamic IP '#{ip_address}' for #{reservation.network.name}") + ip_address.addr end def allocate_vip_ip(reservation, subnet) @@ -68,8 +68,8 @@ def allocate_vip_ip(reservation, subnet) retry end - @logger.debug("Allocated vip IP '#{ip_address.ip}' for #{reservation.network.name}") - ip_address.to_i + @logger.debug("Allocated vip IP '#{ip_address}' for #{reservation.network.name}") + ip_address.addr end private @@ -77,20 +77,16 @@ def allocate_vip_ip(reservation, subnet) def try_to_allocate_dynamic_ip(reservation, subnet) addresses_in_use = Set.new(all_ip_addresses) - first_range_address = subnet.range.first(Objectify: true).to_i - 1 + first_range_address = subnet.range.network.addr - 1 addresses_we_cant_allocate = addresses_in_use addresses_we_cant_allocate << first_range_address addresses_we_cant_allocate.merge(subnet.restricted_ips.to_a) unless subnet.restricted_ips.empty? addresses_we_cant_allocate.merge(subnet.static_ips.to_a) unless subnet.static_ips.empty? addr = find_first_available_address(addresses_we_cant_allocate, first_range_address) - if subnet.range.version == 6 - ip_address = NetAddr::CIDRv6.new(addr) - else - ip_address = NetAddr::CIDRv4.new(addr) - end + ip_address = CIDRIP.new(addr) - unless subnet.range == ip_address || subnet.range.contains?(ip_address) + unless subnet.range.contains(ip_address.netaddr) raise NoMoreIPsAvailableAndStopRetrying end @@ -115,7 +111,7 @@ def try_to_allocate_vip_ip(reservation, subnet) raise NoMoreIPsAvailableAndStopRetrying if available_ips.empty? - ip_address = NetAddr::CIDRv4.new(available_ips.first) + ip_address = CIDRIP.new(available_ips.first) save_ip(ip_address, reservation, false) @@ -130,7 +126,7 @@ def reserve_with_instance_validation(instance_model, ip, reservation, is_static) # try to save IP first before validating its instance to prevent race conditions save_ip(ip, reservation, is_static) rescue IpFoundInDatabaseAndCanBeRetried - ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip.to_i.to_s) + ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip.stringify) retry unless ip_address @@ -161,7 +157,7 @@ def validate_instance_and_update_reservation_type(instance_model, ip, ip_address def save_ip(ip, reservation, is_static) ip_address = Bosh::Director::Models::IpAddress.new( - address_str: ip.to_i.to_s, + address_str: ip.stringify, network_name: reservation.network.name, task_id: Bosh::Director::Config.current_job.task_id, static: is_static, diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb index 8eee955925f..8f1f04c23d1 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb @@ -20,6 +20,7 @@ def self.parse(network_spec, availability_zones, logger) end subnets << new_subnet end + validate_all_subnets_use_azs(subnets, name) new(name, subnets, logger, managed) end @@ -46,7 +47,7 @@ def network_settings(reservation, default_properties = REQUIRED_DEFAULTS, availa "Can't generate network settings without an IP" end - ip = ip_to_netaddr(reservation.ip) + ip = format_ip(reservation.ip) subnet = find_subnet_containing(reservation.ip) unless subnet raise NetworkReservationInvalidIp, "Provided IP '#{ip}' does not belong to any subnet" @@ -54,7 +55,7 @@ def network_settings(reservation, default_properties = REQUIRED_DEFAULTS, availa config = { "type" => "manual", - "ip" => ip.ip, + "ip" => ip, "netmask" => subnet.netmask, "cloud_properties" => subnet.cloud_properties } @@ -64,13 +65,13 @@ def network_settings(reservation, default_properties = REQUIRED_DEFAULTS, availa end config["dns"] = subnet.dns if subnet.dns - config["gateway"] = subnet.gateway.ip if subnet.gateway + config["gateway"] = subnet.gateway.to_s if subnet.gateway config end def ip_type(cidr_ip) static_ips = @subnets.map { |subnet| subnet.static_ips.to_a }.flatten - static_ips.include?(cidr_ip.to_i) ? :static : :dynamic + static_ips.include?(cidr_ip.addr) ? :static : :dynamic end def find_az_names_for_ip(ip) @@ -87,7 +88,8 @@ def manual? # @param [Integer, NetAddr::CIDR, String] ip # @yield the subnet that contains the IP. def find_subnet_containing(ip) - @subnets.find { |subnet| subnet.range.contains?(ip) } + ip = CIDRIP.parse(ip) + @subnets.find { |subnet| subnet.range.contains(ip) } end private diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb index 28af7959ef8..3c5f085928e 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb @@ -1,8 +1,11 @@ +require 'netaddr' + module Bosh::Director module DeploymentPlan class ManualNetworkSubnet < Subnet extend ValidationHelper extend IpUtil + include IpUtil attr_reader :network_name, :name, :dns, :availability_zone_names, :netmask_bits @@ -25,38 +28,44 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) end if range_property - range = NetAddr::CIDR.create(range_property) + range_wrapper = CIDR.new(range_property) + range = range_wrapper.netaddr - if range.size <= 1 + if range.len <= 1 raise NetworkInvalidRange, "Invalid network range '#{range_property}', " \ 'should include at least 2 IPs' end - netmask = range.wildcard_mask - network_id = range.network(Objectify: true) - broadcast = range.version == 6 ? range.last(Objectify: true) : range.broadcast(Objectify: true) + netmask = range_wrapper.netmask + network_id = range.network + broadcast = range.nth(range.len - 1) if gateway_property - gateway = NetAddr::CIDR.create(gateway_property) - invalid_gateway(network_name, 'must be a single IP') unless gateway.size == 1 - invalid_gateway(network_name, 'must be inside the range') unless range.contains?(gateway) - invalid_gateway(network_name, "can't be the network id") if gateway == network_id - invalid_gateway(network_name, "can't be the broadcast IP") if gateway == broadcast + begin + gateway = CIDRIP.parse(gateway_property) + rescue NetAddr::ValidationError + invalid_gateway(network_name, 'not a valid IP format') + end + + invalid_gateway(network_name, 'must be inside the range') unless range.contains(gateway) + invalid_gateway(network_name, "can't be the network id") if gateway.addr == network_id.addr + invalid_gateway(network_name, "can't be the broadcast IP") if gateway.addr == broadcast.addr end static_property = safe_property(subnet_spec, 'static', optional: true) - restricted_ips.add(gateway.to_i) if gateway - restricted_ips.add(network_id.to_i) - restricted_ips.add(broadcast.to_i) + restricted_ips.add(gateway.addr) if gateway + restricted_ips.add(network_id.addr) + restricted_ips.add(broadcast.addr) - each_ip(reserved_property) do |ip| - unless range.contains?(ip) - raise NetworkReservedIpOutOfRange, "Reserved IP '#{format_ip(ip)}' is out of " \ + each_ip(reserved_property) do |ip_int| + ip = CIDRIP.parse(ip_int) + unless range.contains(ip) + raise NetworkReservedIpOutOfRange, "Reserved IP '#{ip.to_s}' is out of " \ "network '#{network_name}' range" end - restricted_ips.add(ip) + restricted_ips.add(ip_int) end Config.director_ips&.each do |cidr| @@ -70,7 +79,7 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) raise NetworkStaticIpOutOfRange, "Static IP '#{format_ip(ip)}' is in network '#{network_name}' reserved range" end - unless range.contains?(ip) + unless range.contains(CIDRIP.parse(ip)) raise NetworkStaticIpOutOfRange, "Static IP '#{format_ip(ip)}' is out of network '#{network_name}' range" end @@ -115,17 +124,19 @@ def initialize(network_name, range, gateway, name_servers, cloud_properties, net def overlaps?(subnet) return false unless range && subnet.range + return false unless range.version == subnet.range.version - range == subnet.range || - range.contains?(subnet.range) || - subnet.range.contains?(range) - rescue NetAddr::VersionError + ! range.rel(subnet.range).nil? + rescue NetAddr::ValidationError false end def is_reservable?(ip) - range.contains?(ip) && !restricted_ips.include?(ip.to_i) - rescue NetAddr::VersionError + ip = CIDRIP.parse(ip) # TODO NETADDR: according to test should not be neccessary, ip should already be a IPv4 object, however method is sometimes used differently + return false unless ip.version == range.version + + range.contains(ip) && !restricted_ips.include?(ip.addr) + rescue NetAddr::ValidationError false end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/network_parser/name_servers_parser.rb b/src/bosh-director/lib/bosh/director/deployment_plan/network_parser/name_servers_parser.rb index e0658404b72..31fddef960e 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/network_parser/name_servers_parser.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/network_parser/name_servers_parser.rb @@ -2,8 +2,8 @@ module Bosh::Director module DeploymentPlan module NetworkParser class NameServersParser - include ValidationHelper + include IpUtil def initialize() dns_config = Config.dns || {} @@ -19,13 +19,14 @@ def parse(network, subnet_properties) if dns_spec servers = [] dns_spec.each do |dns| - dns = NetAddr::CIDR.create(dns) - unless dns.size == 1 + begin + dns = CIDRIP.parse(dns) + rescue NetAddr::ValidationError => e raise NetworkInvalidDns, - "Invalid DNS for network '#{network}': must be a single IP" + "Invalid DNS for network '#{network}': #{e}" end - servers << dns.ip + servers << dns.to_s end end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb index b0ac4f0dcd9..1349aafed29 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb @@ -121,7 +121,7 @@ def create_subnet(subnet, network_model, rollback) network_cloud_properties = network_create_results[2] range = subnet.range ? subnet.range.to_s : network_address_properties['range'] - gw = subnet.gateway ? subnet.gateway.ip : network_address_properties['gateway'] + gw = subnet.gateway ? subnet.gateway.to_s : network_address_properties['gateway'] reserved_ips = network_address_properties.fetch('reserved', []) rollback[network_cid] = cpi @@ -146,28 +146,30 @@ def fetch_cpi_input(subnet, az_cloud_props) } cpi_input['cloud_properties'] = az_cloud_props.merge(subnet.cloud_properties) if subnet.cloud_properties cpi_input['range'] = subnet.range.to_s if subnet.range - cpi_input['gateway'] = subnet.gateway.ip if subnet.gateway + cpi_input['gateway'] = subnet.gateway.to_s if subnet.gateway cpi_input['netmask_bits'] = subnet.netmask_bits if subnet.netmask_bits cpi_input end def populate_subnet_properties(subnet, db_subnet) + cidr = CIDR.new(db_subnet.range) subnet.cloud_properties = JSON.parse(db_subnet.cloud_properties) - subnet.range = NetAddr::CIDR.create(db_subnet.range) - subnet.gateway = NetAddr::CIDR.create(db_subnet.gateway) - subnet.netmask = subnet.range.wildcard_mask - network_id = subnet.range.network(Objectify: true) - broadcast = subnet.range.version == 6 ? subnet.range.last(Objectify: true) : subnet.range.broadcast(Objectify: true) - subnet.restricted_ips.add(subnet.gateway.to_i) if subnet.gateway - subnet.restricted_ips.add(network_id.to_i) - subnet.restricted_ips.add(broadcast.to_i) - each_ip(JSON.parse(db_subnet.reserved)) do |ip| - unless subnet.range.contains?(ip) + subnet.gateway = CIDRIP.parse(db_subnet.gateway) + subnet.range = cidr.netaddr + subnet.netmask = cidr.netmask + network_id = subnet.range.network + broadcast = subnet.range.nth(subnet.range.len - 1) + subnet.restricted_ips.add(subnet.gateway.addr) if subnet.gateway + subnet.restricted_ips.add(network_id.addr) + subnet.restricted_ips.add(broadcast.addr) + each_ip(JSON.parse(db_subnet.reserved)) do |ip_int| + ip = CIDRIP.parse(ip_int) + unless subnet.range.contains(ip) raise NetworkReservedIpOutOfRange, - "Reserved IP '#{format_ip(ip)}' is out of " \ + "Reserved IP '#{ip.to_s}' is out of " \ "subnet '#{subnet.name}' range" end - subnet.restricted_ips.add(ip) + subnet.restricted_ips.add(ip_int) end end end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb index 646c9c5e7a1..796021e5302 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb @@ -46,7 +46,7 @@ def network_settings(reservation, default_properties = REQUIRED_DEFAULTS, _avail { 'type' => 'vip', - 'ip' => ip_to_netaddr(reservation.ip).ip, + 'ip' => format_ip(reservation.ip), 'cloud_properties' => @cloud_properties, } end diff --git a/src/bosh-director/lib/bosh/director/ip_util.rb b/src/bosh-director/lib/bosh/director/ip_util.rb index 44ffaea75d7..5e1f4af5a61 100644 --- a/src/bosh-director/lib/bosh/director/ip_util.rb +++ b/src/bosh-director/lib/bosh/director/ip_util.rb @@ -15,26 +15,17 @@ def each_ip(ranges, &block) end def ip_to_i(ip) - unless ip.kind_of?(Integer) - unless ip.kind_of?(NetAddr::CIDR) - ip = NetAddr::CIDR.create(ip) - end - ip = ip.to_i - end - ip + CIDRIP.parse(ip).addr end def ip_to_netaddr(ip) - unless ip.kind_of?(NetAddr::CIDR) - ip = NetAddr::CIDR.create(ip) - end - ip + CIDRIP.parse(ip).netaddr end # @param [Integer] ip Integer IP representation # @return [String] Human-readable IP representation def format_ip(ip) - ip_to_netaddr(ip).ip + CIDRIP.parse(ip).to_s end def ip_address?(ip) @@ -49,20 +40,20 @@ def ip_address?(ip) def process_range(range) parts = range.split("-") parts.each { |part| part.strip! } - if parts.size == 1 - range = NetAddr::CIDR.create(parts[0]) - first_ip = range.first(:Objectify => true).to_i - last_ip = range.last(:Objectify => true).to_i + if parts.size == 1 && parts[0].include?('/') + range = CIDR.parse(parts[0]) + first_ip = range.nth(0).addr + last_ip = range.nth(range.len - 1).addr (first_ip .. last_ip).each do |ip| yield ip end + elsif parts.size == 1 + ip = CIDRIP.parse(parts[0]).addr + yield ip elsif parts.size == 2 - first_ip = NetAddr::CIDR.create(parts[0]) - last_ip = NetAddr::CIDR.create(parts[1]) - unless first_ip.size == 1 && last_ip.size == 1 - raise NetworkInvalidIpRangeFormat, "Invalid IP range format: #{range}" - end - (first_ip.to_i .. last_ip.to_i).each do |ip| + first_ip = CIDRIP.parse(parts[0]) + last_ip = CIDRIP.parse(parts[1]) + (first_ip.addr .. last_ip.addr).each do |ip| yield ip end else @@ -70,23 +61,109 @@ def process_range(range) "Invalid IP range format: #{range}" end rescue ArgumentError, NetAddr::ValidationError => e - raise NetworkInvalidIpRangeFormat, e.message + raise NetworkInvalidIpRangeFormat, "Invalid IP range format: #{range} (#{e.message})" end + + class CIDR + + def CIDR.parse(ip) + CIDR.new(ip).netaddr + end + + def initialize(cidr) + if cidr.kind_of?(NetAddr::IPv4Net) || cidr.kind_of?(NetAddr::IPv6Net) + @cidr = cidr + else + @cidr = parse(cidr) + end + @version = @cidr.version + end + + def netaddr + @cidr + end + + def netmask + if @version == 4 + @cidr.netmask.extended + else + NetAddr::IPv6.new(@cidr.netmask.mask).to_s + end + end + + def to_s + @cidr.to_s + end + + private + + def parse(cidr) + NetAddr::IPv4Net.parse(cidr) + rescue NetAddr::ValidationError => e_v4 + begin + NetAddr::IPv6Net.parse(cidr) + rescue NetAddr::ValidationError => e_v6 + raise NetAddr::ValidationError, "IP CIDR format #{ip} is neither a valid IPv4 nor IPv6 format: #{e_v4} / #{e_v6}" + end + end + end + class CIDRIP + + def CIDRIP.parse(ip) + CIDRIP.new(ip).netaddr + end + def initialize(ip) - if ip.kind_of?(NetAddr::CIDR) - @cidr = ip + if ip.kind_of?(NetAddr::IPv4) || ip.kind_of?(NetAddr::IPv6) + @ip = ip else - @cidr = NetAddr::CIDR.create(ip) + @ip = parse(ip) end end - def to_i - @cidr.to_i + def netaddr + @ip + end + + def addr + @ip.addr end def to_s - @cidr.ip.to_s + @ip.to_s + end + + def stringify + @ip.addr.to_s + end + + private + + def parse(ip) + parse_ip_v4(ip) + rescue NetAddr::ValidationError => e_v4 + begin + parse_ip_v6(ip) + rescue NetAddr::ValidationError => e_v6 + raise NetAddr::ValidationError, "IP format #{ip} is neither a valid IPv4 nor IPv6 format: #{e_v4} / #{e_v6}" + end + end + + def parse_ip_v6(ip) + if ip.kind_of?(Integer) + NetAddr::IPv6.new(ip) + else ip.kind_of?(String) + NetAddr::IPv6.parse(ip) + end + end + + def parse_ip_v4(ip) + if ip.kind_of?(Integer) + NetAddr::IPv4.new(ip) + else ip.kind_of?(String) + NetAddr::IPv4.parse(ip) + end end end end diff --git a/src/bosh-director/lib/bosh/director/metrics_collector.rb b/src/bosh-director/lib/bosh/director/metrics_collector.rb index 509ab1f3cf8..19260ccd6a8 100644 --- a/src/bosh-director/lib/bosh/director/metrics_collector.rb +++ b/src/bosh-director/lib/bosh/director/metrics_collector.rb @@ -157,7 +157,7 @@ def calculate_network_metrics(network) network.subnets.each do |subnet| total_static += subnet.static_ips.size total_restricted += subnet.restricted_ips.size - total_available += subnet.range.size + total_available += subnet.range.len end total_available -= total_static diff --git a/src/bosh-director/lib/bosh/director/models/ip_address.rb b/src/bosh-director/lib/bosh/director/models/ip_address.rb index 242cfb4e6c0..d0c58d899a2 100644 --- a/src/bosh-director/lib/bosh/director/models/ip_address.rb +++ b/src/bosh-director/lib/bosh/director/models/ip_address.rb @@ -21,13 +21,14 @@ def before_create def info instance_info = "#{instance.deployment.name}.#{instance.job}/#{instance.index}" - formatted_ip = NetAddr::CIDR.create(address_str.to_i).ip + formatted_ip = format_ip(address_str.to_i) "#{instance_info} - #{network_name} - #{formatted_ip} (#{type})" end - def formatted_ip - NetAddr::CIDR.create(address).ip - end + # TODO NETADDR: not used since 4a35c4b -> to be removed + # def formatted_ip + # NetAddr::IPv4.new(address).to_s + # end def type static ? 'static' : 'dynamic' diff --git a/src/bosh-director/lib/bosh/director/network_reservation.rb b/src/bosh-director/lib/bosh/director/network_reservation.rb index 52501abe8d1..55cd4e132b1 100644 --- a/src/bosh-director/lib/bosh/director/network_reservation.rb +++ b/src/bosh-director/lib/bosh/director/network_reservation.rb @@ -21,7 +21,7 @@ def dynamic? private def formatted_ip - @ip.nil? ? nil : ip_to_netaddr(@ip).ip + @ip.nil? ? nil : format_ip(@ip) end end @@ -49,6 +49,7 @@ def to_s end class DesiredNetworkReservation < NetworkReservation + def self.new_dynamic(instance_model, network) new(instance_model, network, nil, :dynamic) end diff --git a/src/bosh-director/lib/cloud/dummy.rb b/src/bosh-director/lib/cloud/dummy.rb index befde67673e..ee13a9b589e 100644 --- a/src/bosh-director/lib/cloud/dummy.rb +++ b/src/bosh-director/lib/cloud/dummy.rb @@ -96,7 +96,7 @@ def create_vm(agent_id, stemcell_id, cloud_properties, networks, disk_cids, env) elsif cloud_properties['az_name'] ip_address = cmd.ip_address_for_az(cloud_properties['az_name']) else - ip_address = NetAddr::CIDRv4.new(rand(0..4294967295)).ip #collisions? + ip_address = NetAddr::IPv4.new(rand(0..4294967295)).to_s #collisions? end if ip_address diff --git a/src/bosh-director/spec/blueprints.rb b/src/bosh-director/spec/blueprints.rb index 21da5fb9e43..6b343da32d7 100644 --- a/src/bosh-director/spec/blueprints.rb +++ b/src/bosh-director/spec/blueprints.rb @@ -110,7 +110,7 @@ module Bosh::Director::Models end IpAddress.blueprint do - address_str { NetAddr::CIDR.create(Sham.ip).to_i.to_s } + address_str { NetAddr::IPv4.parse(Sham.ip).addr.to_s } vm { nil } instance static { false } diff --git a/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb b/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb index ad221207730..824d863faa9 100644 --- a/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb +++ b/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb @@ -7,13 +7,13 @@ module Bosh::Director describe 'chaos' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.8/31'), - NetAddr::CIDR.create('192.168.0.6/32'), - NetAddr::CIDR.create('192.168.0.13/32'), - NetAddr::CIDR.create('192.168.1.1/24'), - NetAddr::CIDR.create('192.168.0.10/32'), - NetAddr::CIDR.create('192.168.2.1/24'), - NetAddr::CIDR.create('192.168.0.14/31'), + NetAddr::IPv4Net.parse('192.168.0.8/31'), + NetAddr::IPv4Net.parse('192.168.0.6/32'), + NetAddr::IPv4Net.parse('192.168.0.13/32'), + NetAddr::IPv4Net.parse('192.168.1.1/24'), + NetAddr::IPv4Net.parse('192.168.0.10/32'), + NetAddr::IPv4Net.parse('192.168.2.1/24'), + NetAddr::IPv4Net.parse('192.168.0.14/31'), ] end @@ -30,8 +30,8 @@ module Bosh::Director describe 'when the a range is length 1' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.8/32'), - NetAddr::CIDR.create('192.168.0.6/32'), + NetAddr::IPv4Net.parse('192.168.0.8/32'), + NetAddr::IPv4Net.parse('192.168.0.6/32'), ] end @@ -45,8 +45,8 @@ module Bosh::Director describe 'when the ranges do not overlap' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.8/31'), - NetAddr::CIDR.create('192.168.0.6/32'), + NetAddr::IPv4Net.parse('192.168.0.8/31'), + NetAddr::IPv4Net.parse('192.168.0.6/32'), ] end @@ -60,8 +60,8 @@ module Bosh::Director describe 'when a range is a subset of another range' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.0/24'), # 0-255 - NetAddr::CIDR.create('192.168.0.10/30'), # 8-11 + NetAddr::IPv4Net.parse('192.168.0.0/24'), # 0-255 + NetAddr::IPv4Net.parse('192.168.0.10/30'), # 8-11 ] end @@ -75,8 +75,8 @@ module Bosh::Director describe 'when ranges are adjacent' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.8/30'), # 8-11 - NetAddr::CIDR.create('192.168.0.12/30'), # 12-15 + NetAddr::IPv4Net.parse('192.168.0.8/30'), # 8-11 + NetAddr::IPv4Net.parse('192.168.0.12/30'), # 12-15 ] end @@ -90,10 +90,10 @@ module Bosh::Director describe 'when ranges are ipv4 and ipv6' do let(:cidr_ranges) do [ - NetAddr::CIDR.create('192.168.0.8/30'), - NetAddr::CIDR.create('fd7a:eeed:e696:968f:0000:0000:0000:0005/128'), - NetAddr::CIDR.create('fd7a:eeed:e696:968f:0000:0000:0000:0005/64'), - NetAddr::CIDR.create('192.168.0.20/32'), + NetAddr::IPv4Net.parse('192.168.0.8/30'), + NetAddr::IPv6Net.parse('fd7a:eeed:e696:968f:0000:0000:0000:0005/128'), + NetAddr::IPv6Net.parse('fd7a:eeed:e696:968f:0000:0000:0000:0005/64'), + NetAddr::IPv4Net.parse('192.168.0.20/32'), ] end diff --git a/src/bosh-director/spec/unit/db/migrations/director/20170825141953_change_address_to_be_string_for_ipv6_spec.rb b/src/bosh-director/spec/unit/db/migrations/director/20170825141953_change_address_to_be_string_for_ipv6_spec.rb index 72817ab97fd..49f1f5bde83 100644 --- a/src/bosh-director/spec/unit/db/migrations/director/20170825141953_change_address_to_be_string_for_ipv6_spec.rb +++ b/src/bosh-director/spec/unit/db/migrations/director/20170825141953_change_address_to_be_string_for_ipv6_spec.rb @@ -9,14 +9,14 @@ module Bosh::Director before { DBSpecHelper.migrate_all_before(migration_file) } it 'allows instance_id to be null' do - db[:ip_addresses] << {id: 1, instance_id: nil, address: NetAddr::CIDR.create("192.168.50.6").to_i} + db[:ip_addresses] << {id: 1, instance_id: nil, address: NetAddr::IPv4.parse('192.168.50.6').addr} DBSpecHelper.migrate(migration_file) - expect(db[:ip_addresses].first[:address_str]).to eq(NetAddr::CIDR.create("192.168.50.6").to_i.to_s) + expect(db[:ip_addresses].first[:address_str]).to eq(NetAddr::IPv4.parse('192.168.50.6').addr.to_s) expect { - db[:ip_addresses] << {id: 2, instance_id: nil, address_str: NetAddr::CIDR.create("192.168.50.6").to_i.to_s} + db[:ip_addresses] << {id: 2, instance_id: nil, address_str: NetAddr::IPv4.parse('192.168.50.6').addr.to_s} }.to raise_error(Sequel::UniqueConstraintViolation, /ip_addresses.address/) expect { diff --git a/src/bosh-director/spec/unit/deployment_plan/compilation_instance_pool_spec.rb b/src/bosh-director/spec/unit/deployment_plan/compilation_instance_pool_spec.rb index afbc53bdc1f..af2a1ceb7df 100644 --- a/src/bosh-director/spec/unit/deployment_plan/compilation_instance_pool_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/compilation_instance_pool_spec.rb @@ -52,7 +52,7 @@ module Bosh::Director end let(:subnet) do - instance_double('Bosh::Director::DeploymentPlan::ManualNetworkSubnet', range: NetAddr::CIDR.create('192.168.0.0/24')) + instance_double('Bosh::Director::DeploymentPlan::ManualNetworkSubnet', range: NetAddr::IPv4Net.parse('192.168.0.0/24')) end let(:stemcell) do diff --git a/src/bosh-director/spec/unit/deployment_plan/instance_group_networks_parser_spec.rb b/src/bosh-director/spec/unit/deployment_plan/instance_group_networks_parser_spec.rb index 23e5319a3c6..ec41e01ced2 100644 --- a/src/bosh-director/spec/unit/deployment_plan/instance_group_networks_parser_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/instance_group_networks_parser_spec.rb @@ -90,7 +90,7 @@ module Bosh::Director::DeploymentPlan RSpec::Matchers.define :be_an_instance_group_network do |expected| match do |actual| actual.name == expected.name && - actual.static_ips == expected.static_ips.map { |ip_to_i| NetAddr::CIDR.create(ip_to_i) } && + actual.static_ips == expected.static_ips.map { |ip_to_i| NetAddr::IPv4.parse(ip_to_i).addr } && actual.deployment_network == expected.deployment_network end end diff --git a/src/bosh-director/spec/unit/deployment_plan/instance_network_reservations_spec.rb b/src/bosh-director/spec/unit/deployment_plan/instance_network_reservations_spec.rb index 11619c6fc60..fd4d1c2f32e 100644 --- a/src/bosh-director/spec/unit/deployment_plan/instance_network_reservations_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/instance_network_reservations_spec.rb @@ -54,8 +54,8 @@ module Bosh::Director describe 'create_from_db' do context 'when there are IP addresses in db' do - let(:ip1) { NetAddr::CIDR.create('192.168.0.1').to_i } - let(:ip2) { NetAddr::CIDR.create('192.168.0.2').to_i } + let(:ip1) { NetAddr::IPv4.parse('192.168.0.1').addr } + let(:ip2) { NetAddr::IPv4.parse('192.168.0.2').addr } let(:ip_model1) do Models::IpAddress.make(address_str: ip1.to_s, instance: instance_model, network_name: 'fake-network') @@ -218,7 +218,7 @@ module Bosh::Director it 'creates reservations for dynamic networks' do reservations = DeploymentPlan::InstanceNetworkReservations.create_from_db(instance_model, deployment, logger) expect(reservations.first).to_not be_nil - expect(reservations.first.ip).to eq(NetAddr::CIDR.create('10.10.0.10').to_i) + expect(reservations.first.ip).to eq(NetAddr::IPv4.parse('10.10.0.10').addr) end end end diff --git a/src/bosh-director/spec/unit/deployment_plan/instance_plan_factory_spec.rb b/src/bosh-director/spec/unit/deployment_plan/instance_plan_factory_spec.rb index 642aebef596..3d25b969dde 100644 --- a/src/bosh-director/spec/unit/deployment_plan/instance_plan_factory_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/instance_plan_factory_spec.rb @@ -43,7 +43,7 @@ module DeploymentPlan Models::Deployment.make(manifest: YAML.dump(Bosh::Spec::Deployments.minimal_manifest)) end - let(:range) { NetAddr::CIDR.create('192.168.1.1/24') } + let(:range) { NetAddr::IPv4Net.parse('192.168.1.1/24') } let(:manual_network_subnet) { ManualNetworkSubnet.new('name-7', range, nil, nil, nil, nil, nil, [], []) } let(:network) { BD::DeploymentPlan::ManualNetwork.new('name-7', [manual_network_subnet], logger) } let(:ip_repo) { BD::DeploymentPlan::IpRepo.new(logger) } diff --git a/src/bosh-director/spec/unit/deployment_plan/instance_plan_spec.rb b/src/bosh-director/spec/unit/deployment_plan/instance_plan_spec.rb index a7b6c8c06ee..c321c8da7a8 100644 --- a/src/bosh-director/spec/unit/deployment_plan/instance_plan_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/instance_plan_spec.rb @@ -2024,8 +2024,8 @@ module Bosh::Director::DeploymentPlan let(:network_plans) { [plan1, plan2, plan3, plan4] } - let(:ip1) { NetAddr::CIDR.create('192.168.1.25').to_i } - let(:ip2) { NetAddr::CIDR.create('192.168.1.26').to_i } + let(:ip1) { NetAddr::IPv4.parse('192.168.1.25').addr } + let(:ip2) { NetAddr::IPv4.parse('192.168.1.26').addr } let(:ip_address1) { Bosh::Director::Models::IpAddress.make(address_str: ip1.to_s) } let(:ip_address2) { Bosh::Director::Models::IpAddress.make(address_str: ip2.to_s) } diff --git a/src/bosh-director/spec/unit/deployment_plan/instance_planner_spec.rb b/src/bosh-director/spec/unit/deployment_plan/instance_planner_spec.rb index ca923fc4c75..68c947418cb 100644 --- a/src/bosh-director/spec/unit/deployment_plan/instance_planner_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/instance_planner_spec.rb @@ -756,7 +756,7 @@ def make_instance_with_existing_model(existing_instance_model) let(:subnet) do BD::DeploymentPlan::ManualNetworkSubnet.new( 'fake-network', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, ['foo-az'], [], [] ) diff --git a/src/bosh-director/spec/unit/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb b/src/bosh-director/spec/unit/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb index a5853144275..bcebfaaacc8 100644 --- a/src/bosh-director/spec/unit/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb @@ -56,7 +56,7 @@ module Bosh::Director::DeploymentPlan before { fake_job } def cidr_ip(ip) - NetAddr::CIDR.create(ip).to_i + NetAddr::IPv6.parse(ip).addr end context :add do diff --git a/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_provider_spec.rb b/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_provider_spec.rb index 55b9c5912a3..957fbf5d733 100644 --- a/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_provider_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_provider_spec.rb @@ -89,7 +89,7 @@ module Bosh::Director::DeploymentPlan allocate_dynamic_ip: ip, ) end - let(:ip) { NetAddr::CIDR.create('1.1.1.1') } + let(:ip) { NetAddr::IPv4.parse('1.1.1.1') } let(:ip_provider) { IpProvider.new(ip_repo, networks, logger) } describe :release do @@ -278,7 +278,7 @@ module Bosh::Director::DeploymentPlan reservation.instance_model.update(availability_zone: 'az-1') ip_provider.reserve(reservation) - expect(reservation.ip).to eq(NetAddr::CIDR.create('192.168.1.6').to_i) + expect(reservation.ip).to eq(NetAddr::IPv4.parse('192.168.1.6').addr) end context 'when that IP is now in the reserved range' do @@ -289,7 +289,7 @@ module Bosh::Director::DeploymentPlan it 'raises an error' do reservation = BD::DesiredNetworkReservation.new_dynamic(instance_model, manual_network) - reservation.resolve_ip(NetAddr::CIDR.create('192.168.1.11').to_i) + reservation.resolve_ip(NetAddr::IPv4.parse('192.168.1.11').addr) expect do ip_provider.reserve(reservation) end.to raise_error Bosh::Director::NetworkReservationIpReserved, @@ -468,7 +468,7 @@ module Bosh::Director::DeploymentPlan it 'adds the ip address to the ip repository' do ip_provider.reserve(reservation) - expect(reservation.ip).to eq(NetAddr::CIDR.create('1.1.1.1').to_i) + expect(reservation.ip).to eq(NetAddr::IPv4.parse('1.1.1.1').addr) end end @@ -477,7 +477,7 @@ module Bosh::Director::DeploymentPlan it 'allocates an ip address for the reservation' do ip_provider.reserve(reservation) - expect(reservation.ip).to eq(NetAddr::CIDR.create('1.1.1.1').to_i) + expect(reservation.ip).to eq(NetAddr::IPv4.parse('1.1.1.1').addr) end context 'and there are no available vips' do diff --git a/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_repo_spec.rb b/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_repo_spec.rb index fd6a58ba686..376e258d59a 100644 --- a/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_repo_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/ip_provider/ip_repo_spec.rb @@ -56,7 +56,7 @@ module Bosh::Director::DeploymentPlan before { fake_job } def cidr_ip(ip) - NetAddr::CIDR.create(ip).to_i + NetAddr::IPv4.parse(ip).addr end context :add do diff --git a/src/bosh-director/spec/unit/deployment_plan/manual_network_spec.rb b/src/bosh-director/spec/unit/deployment_plan/manual_network_spec.rb index 342c2807f94..738e99a82f7 100644 --- a/src/bosh-director/spec/unit/deployment_plan/manual_network_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/manual_network_spec.rb @@ -77,7 +77,7 @@ expect(subnet).to be_an_instance_of BD::DeploymentPlan::ManualNetworkSubnet expect(subnet.network_name).to eq(manual_network.name) expect(manual_network.managed?).to eq(false) - expect(subnet.range).to eq(NetAddr::CIDR.create('192.168.1.0/24')) + expect(subnet.range.cmp(NetAddr::IPv4Net.parse('192.168.1.0/24'))).to eq(0) end context 'when network is managed' do @@ -95,7 +95,7 @@ subnet = manual_network.subnets.first expect(subnet).to be_an_instance_of BD::DeploymentPlan::ManualNetworkSubnet expect(subnet.network_name).to eq(manual_network.name) - expect(subnet.range).to eq(NetAddr::CIDR.create('192.168.1.0/24')) + expect(subnet.range.cmp(NetAddr::IPv4Net.parse('192.168.1.0/24'))).to eq(0) end end diff --git a/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb b/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb index 8733137f50e..14f151d153e 100644 --- a/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb @@ -15,12 +15,12 @@ def make_managed_subnet(properties, availability_zones) let(:instance) { instance_double(BD::DeploymentPlan::Instance, model: BD::Models::Instance.make) } def create_static_reservation(ip) - BD::StaticNetworkReservation.new(instance, @network, NetAddr::CIDR.create(ip)) + BD::StaticNetworkReservation.new(instance, @network, NetAddr::IPv4.parse(ip)) end def create_dynamic_reservation(ip) reservation = BD::DynamicNetworkReservation.new(instance, @network) - reservation.resolve_ip(NetAddr::CIDR.create(ip)) + reservation.resolve_ip(NetAddr::IPv4.parse(ip)) reservation end @@ -37,10 +37,10 @@ def create_dynamic_reservation(ip) [], ) - expect(subnet.range.ip).to eq('192.168.0.0') - subnet.range.ip.size == 255 + expect(subnet.range.network.to_s).to eq('192.168.0.0') + # subnet.range.ip.size == 255 # TODO NETADDR: bug? / what was tested here? expect(subnet.netmask).to eq('255.255.255.0') - expect(subnet.gateway).to eq('192.168.0.254') + expect(subnet.gateway.to_s).to eq('192.168.0.254') # TODO NETADDR: not sure how it worked before (was previously an object as well) expect(subnet.dns).to eq(nil) end @@ -55,10 +55,10 @@ def create_dynamic_reservation(ip) [], ) - expect(subnet.range.ip).to eq('192.168.0.0') - subnet.range.ip.size == 255 + expect(subnet.range.network.to_s).to eq('192.168.0.0') + # subnet.range.ip.size == 255 # TODO NETADDR: bug? / what was tested here? expect(subnet.netmask).to eq('255.255.255.0') - expect(subnet.gateway).to eq('192.168.0.254') + expect(subnet.gateway.to_s).to eq('192.168.0.254') # TODO NETADDR: not sure how it worked before (was previously an object as well) expect(subnet.dns).to eq(nil) end @@ -191,7 +191,7 @@ def create_dynamic_reservation(ip) [] ) - expect(subnet.gateway.ip).to eq('192.168.0.254') + expect(subnet.gateway.to_s).to eq('192.168.0.254') end it 'should make sure gateway is a single ip' do @@ -205,7 +205,7 @@ def create_dynamic_reservation(ip) [] ) }.to raise_error(BD::NetworkInvalidGateway, - /must be a single IP/) + /not a valid IP format/) end it 'should make sure gateway is inside the subnet' do @@ -327,8 +327,8 @@ def create_dynamic_reservation(ip) end it 'should include the directors ip addresses in the reserved range' do - ip1 = NetAddr::CIDR.create('192.168.1.1') - ip2 = NetAddr::CIDR.create('192.168.1.2') + ip1 = NetAddr::IPv4.parse('192.168.1.1') + ip2 = NetAddr::IPv4.parse('192.168.1.2') allow(Bosh::Director::Config).to receive(:director_ips).and_return([ip1.to_s, ip2.to_s]) subnet = make_subnet( @@ -341,8 +341,8 @@ def create_dynamic_reservation(ip) [], ) - expect(subnet.restricted_ips).to include(ip1.to_i) - expect(subnet.restricted_ips).to include(ip2.to_i) + expect(subnet.restricted_ips).to include(ip1.addr) + expect(subnet.restricted_ips).to include(ip2.addr) end end @@ -413,26 +413,26 @@ def create_dynamic_reservation(ip) let(:reserved) { ['192.168.0.50-192.168.0.60'] } it 'returns false' do - expect(subnet.is_reservable?(NetAddr::CIDR.create('192.168.0.55'))).to be_falsey + expect(subnet.is_reservable?(NetAddr::IPv4.parse('192.168.0.55'))).to be_falsey end end context 'when subnet reserved does not include IP' do it 'returns true' do - expect(subnet.is_reservable?(NetAddr::CIDR.create('192.168.0.55'))).to be_truthy + expect(subnet.is_reservable?(NetAddr::IPv4.parse('192.168.0.55'))).to be_truthy end end end context 'when subnet range does not include IP' do it 'returns false' do - expect(subnet.is_reservable?(NetAddr::CIDR.create('192.168.10.55'))).to be_falsey + expect(subnet.is_reservable?(NetAddr::IPv4.parse('192.168.10.55'))).to be_falsey end end context 'when subnet range is not the same IP version' do it 'returns false' do - expect(subnet.is_reservable?(NetAddr::CIDR.create('f1ee:0000:0000:0000:0000:0000:0000:0001'))).to be_falsey + expect(subnet.is_reservable?(NetAddr::IPv6.parse('f1ee:0000:0000:0000:0000:0000:0000:0001'))).to be_falsey end end end diff --git a/src/bosh-director/spec/unit/deployment_plan/network_parser/name_servers_parser_spec.rb b/src/bosh-director/spec/unit/deployment_plan/network_parser/name_servers_parser_spec.rb index 544b02261bc..fd3a83df919 100644 --- a/src/bosh-director/spec/unit/deployment_plan/network_parser/name_servers_parser_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/network_parser/name_servers_parser_spec.rb @@ -16,7 +16,7 @@ module DeploymentPlan::NetworkParser it "should raise an error if a DNS server isn't specified with as an IP" do expect { name_servers_parser.parse('network', {'dns' => %w[1.2.3.4 foo.bar]}) - }.to raise_error(NetAddr::ValidationError, /foo.bar is invalid \(contains invalid characters\)./) + }.to raise_error(Bosh::Director::NetworkInvalidDns, /foo.bar contains invalid characters./) end context 'when power dns is not enabled' do diff --git a/src/bosh-director/spec/unit/deployment_plan/network_planner/planner_spec.rb b/src/bosh-director/spec/unit/deployment_plan/network_planner/planner_spec.rb index 3fa0b0f0d8c..7248e14f348 100644 --- a/src/bosh-director/spec/unit/deployment_plan/network_planner/planner_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/network_planner/planner_spec.rb @@ -17,7 +17,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14'] ), diff --git a/src/bosh-director/spec/unit/deployment_plan/network_planner/reservation_reconciler_spec.rb b/src/bosh-director/spec/unit/deployment_plan/network_planner/reservation_reconciler_spec.rb index 0feb3d7dfdd..da6a0bb5f32 100644 --- a/src/bosh-director/spec/unit/deployment_plan/network_planner/reservation_reconciler_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/network_planner/reservation_reconciler_spec.rb @@ -14,13 +14,13 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'my-network', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], ['192.168.1.10'] ), ManualNetworkSubnet.new( 'my-network', - NetAddr::CIDR.create('192.168.2.0/24'), + NetAddr::IPv4Net.parse('192.168.2.0/24'), nil, nil, nil, nil, ['zone_2'], [], ['192.168.2.10'] ), @@ -235,7 +235,7 @@ module Bosh::Director::DeploymentPlan expect(existing_plans[0].reservation.network.name).to eq('global-vip-network') expect(existing_plans[0].reservation.instance_model).to eq(instance_model) - expect(ip_to_netaddr(existing_plans[0].reservation.ip)).to eq('192.168.1.2') + expect(format_ip(existing_plans[0].reservation.ip)).to eq('192.168.1.2') end end @@ -255,7 +255,7 @@ module Bosh::Director::DeploymentPlan expect(existing_plans[0].reservation.network.name).to eq('my-network-2') expect(existing_plans[0].reservation.instance_model).to eq(instance_model) - expect(ip_to_netaddr(existing_plans[0].reservation.ip)).to eq('192.168.1.2') + expect(format_ip(existing_plans[0].reservation.ip)).to eq('192.168.1.2') end context 'and the new network does not match az' do @@ -280,7 +280,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'my-network', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, [], [], ['192.168.1.10'] ), @@ -311,7 +311,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'my-network', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, [], [], ['192.168.1.10'] ), @@ -417,11 +417,11 @@ module Bosh::Director::DeploymentPlan desired_plans = network_plans.reject(&:existing?).reject(&:obsolete?) expect(obsolete_plans.count).to eq(1) - expect(ip_to_netaddr(obsolete_plans.first.reservation.ip)).to eq('192.168.1.2') + expect(format_ip(obsolete_plans.first.reservation.ip)).to eq('192.168.1.2') expect(existing_plans.count).to eq(1) - expect(ip_to_netaddr(existing_plans.first.reservation.ip)).to eq('192.168.1.3') + expect(format_ip(existing_plans.first.reservation.ip)).to eq('192.168.1.3') expect(desired_plans.count).to eq(1) - expect(ip_to_netaddr(desired_plans.first.reservation.ip)).to eq('192.168.1.4') + expect(format_ip(desired_plans.first.reservation.ip)).to eq('192.168.1.4') end end end @@ -440,7 +440,7 @@ module Bosh::Director::DeploymentPlan desired_plans = network_plans.reject(&:existing?).reject(&:obsolete?) expect(obsolete_plans.count).to eq(1) - expect(ip_to_netaddr(obsolete_plans.first.reservation.ip)).to eq('192.168.1.2') + expect(format_ip(obsolete_plans.first.reservation.ip)).to eq('192.168.1.2') expect(existing_plans.count).to eq(0) expect(desired_plans.count).to eq(1) expect(desired_plans.first.reservation.type).to eq(:dynamic) @@ -468,7 +468,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'my-network', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, nil, [], ['192.168.1.10'] ), @@ -485,7 +485,7 @@ module Bosh::Director::DeploymentPlan expect(obsolete_plans.count).to eq(0) expect(existing_plans.count).to eq(1) - expect(ip_to_netaddr(existing_plans.first.reservation.ip)).to eq('192.168.1.2') + expect(format_ip(existing_plans.first.reservation.ip)).to eq('192.168.1.2') expect(desired_plans.count).to eq(0) end end diff --git a/src/bosh-director/spec/unit/deployment_plan/placement_planner/availability_zone_picker_spec.rb b/src/bosh-director/spec/unit/deployment_plan/placement_planner/availability_zone_picker_spec.rb index dc7dc7e184d..3f752a4e5fe 100644 --- a/src/bosh-director/spec/unit/deployment_plan/placement_planner/availability_zone_picker_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/placement_planner/availability_zone_picker_spec.rb @@ -27,7 +27,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14']) ] diff --git a/src/bosh-director/spec/unit/deployment_plan/placement_planner/networks_to_static_ips_spec.rb b/src/bosh-director/spec/unit/deployment_plan/placement_planner/networks_to_static_ips_spec.rb index 5b36a789ffd..572a2bebbc6 100644 --- a/src/bosh-director/spec/unit/deployment_plan/placement_planner/networks_to_static_ips_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/placement_planner/networks_to_static_ips_spec.rb @@ -102,7 +102,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, subnet_azs, [], ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14']) ] diff --git a/src/bosh-director/spec/unit/deployment_plan/placement_planner/plan_spec.rb b/src/bosh-director/spec/unit/deployment_plan/placement_planner/plan_spec.rb index 34bcf82c0b5..fd5e9c15044 100644 --- a/src/bosh-director/spec/unit/deployment_plan/placement_planner/plan_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/placement_planner/plan_spec.rb @@ -51,7 +51,7 @@ module Bosh::Director::DeploymentPlan [ ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('192.168.1.0/24'), + NetAddr::IPv4Net.parse('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], %w[ 192.168.1.10 @@ -63,7 +63,7 @@ module Bosh::Director::DeploymentPlan ), ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('10.10.1.0/24'), + NetAddr::IPv4Net.parse('10.10.1.0/24'), nil, nil, nil, nil, ['zone_2'], [], %w[ 10.10.1.10 @@ -75,7 +75,7 @@ module Bosh::Director::DeploymentPlan ), ManualNetworkSubnet.new( 'network_A', - NetAddr::CIDR.create('10.0.1.0/24'), + NetAddr::IPv4Net.parse('10.0.1.0/24'), nil, nil, nil, nil, ['zone_3'], [], %w[ 10.0.1.10 diff --git a/src/bosh-director/spec/unit/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb b/src/bosh-director/spec/unit/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb index 9daee76eb96..fc481c5d366 100644 --- a/src/bosh-director/spec/unit/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb @@ -59,7 +59,7 @@ module Bosh::Director::DeploymentPlan def make_subnet_spec(range, static_ips, zone_names) spec = { 'range' => range, - 'gateway' => NetAddr::CIDR.create(range)[1].ip, + 'gateway' => NetAddr::IPv4Net.parse(range).nth(1).to_s, 'dns' => ['8.8.8.8'], 'static' => static_ips, 'reserved' => [], @@ -952,7 +952,7 @@ def existing_instance_with_az_and_ips(az, ips, network_name = 'a') ips.each do |ip| instance.add_ip_address( Bosh::Director::Models::IpAddress.make( - address_str: NetAddr::CIDR.create(ip).to_i.to_s, + address_str: NetAddr::IPv4.parse(ip).addr.to_s, network_name: network_name, ), ) diff --git a/src/bosh-director/spec/unit/deployment_plan/stages/create_network_spec.rb b/src/bosh-director/spec/unit/deployment_plan/stages/create_network_spec.rb index f1589fbb6e4..5e65c388e49 100644 --- a/src/bosh-director/spec/unit/deployment_plan/stages/create_network_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/stages/create_network_spec.rb @@ -50,6 +50,13 @@ module DeploymentPlan::Stages 'cloud_properties' => { 't0_id' => '123456' }, 'dns' => ['8.8.8.8'], }, + { + 'name' => 'subnet-3', + 'range' => 'fdab:d85c:118d:8a46::/125', + 'gateway' => 'fdab:d85c:118d:8a46::1', + 'dns' => ['fdab:d85c:118d:8a46::1'], + 'cloud_properties' => { 't0_id' => '123456' }, + }, ], } end @@ -74,6 +81,11 @@ module DeploymentPlan::Stages ).and_return( ['67890', {}, { 'name': 'dummy2' }], ) + expect(cloud).to receive(:create_network).with( + hash_including('gateway' => 'fdab:d85c:118d:8a46::1'), + ).and_return( + ['24680', {}, { 'name': 'dummy3' }], + ) subject.perform end @@ -88,6 +100,11 @@ module DeploymentPlan::Stages ).and_return( ['67890', {}, { 'name': 'dummy2' }], ) + expect(cloud).to receive(:create_network).with( + hash_including('gateway' => 'fdab:d85c:118d:8a46::1'), + ).and_return( + ['24680', {}, { 'name': 'dummy3' }], + ) subject.perform nw = Bosh::Director::Models::Network.first(name: 'a') nw.orphaned = true @@ -108,6 +125,11 @@ module DeploymentPlan::Stages ).and_return( ['67890', {}, { 'name': 'dummy2' }], ) + expect(cloud).to receive(:create_network).once.with( + hash_including('gateway' => 'fdab:d85c:118d:8a46::1'), + ).and_return( + ['24680', {}, { 'name': 'dummy3' }], + ) 3.times do subject.perform end @@ -124,6 +146,11 @@ module DeploymentPlan::Stages ).and_return( ['67890', {}, { 'name': 'dummy2' }], ) + expect(cloud).to receive(:create_network).once.with( + hash_including('gateway' => 'fdab:d85c:118d:8a46::1'), + ).and_return( + ['24680', {}, { 'name': 'dummy3' }], + ) subject.perform diff --git a/src/bosh-director/spec/unit/deployment_plan/vip_network_spec.rb b/src/bosh-director/spec/unit/deployment_plan/vip_network_spec.rb index d6d393556aa..07cba19fe59 100644 --- a/src/bosh-director/spec/unit/deployment_plan/vip_network_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/vip_network_spec.rb @@ -103,7 +103,7 @@ it 'returns the availability zones associated with the given ip' do network = BD::DeploymentPlan::VipNetwork.parse(network_spec, azs, logger) - az = network.find_az_names_for_ip(NetAddr::CIDR.create('69.69.69.69.').to_i) + az = network.find_az_names_for_ip(NetAddr::IPv4.parse('69.69.69.69.').addr) expect(az).to include('z1') end end diff --git a/src/bosh-director/spec/unit/ip_util_spec.rb b/src/bosh-director/spec/unit/ip_util_spec.rb index e5c8e35940e..eb3f487a101 100644 --- a/src/bosh-director/spec/unit/ip_util_spec.rb +++ b/src/bosh-director/spec/unit/ip_util_spec.rb @@ -12,7 +12,7 @@ it 'should handle single ip' do counter = 0 @obj.each_ip('1.2.3.4') do |ip| - expect(ip).to eql(NetAddr::CIDR.create('1.2.3.4').to_i) + expect(ip).to eql(NetAddr::IPv4.parse('1.2.3.4').addr) counter += 1 end expect(counter).to eq(1) @@ -21,7 +21,7 @@ it 'should handle a range' do counter = 0 @obj.each_ip('1.0.0.0/24') do |ip| - expect(ip).to eql(NetAddr::CIDR.create('1.0.0.0').to_i + counter) + expect(ip).to eql(NetAddr::IPv4.parse('1.0.0.0').addr + counter) counter += 1 end expect(counter).to eq(256) @@ -30,20 +30,20 @@ it 'should handle a differently formatted range' do counter = 0 @obj.each_ip('1.0.0.0 - 1.0.1.0') do |ip| - expect(ip).to eql(NetAddr::CIDR.create('1.0.0.0').to_i + counter) + expect(ip).to eql(NetAddr::IPv4.parse('1.0.0.0').addr + counter) counter += 1 end expect(counter).to eq(257) end it 'should not accept invalid input' do - expect { @obj.each_ip('1.2.4') {} }.to raise_error(Bosh::Director::NetworkInvalidIpRangeFormat, /invalid \(IPv4 requires \(4\) octets\)/) + expect { @obj.each_ip('1.2.4') {} }.to raise_error(Bosh::Director::NetworkInvalidIpRangeFormat, /IPv4 requires \(4\) octets/) end it 'should ignore nil values' do counter = 0 @obj.each_ip(nil) do |ip| - expect(ip).to eql(NetAddr::CIDR.create('1.2.3.4').to_i) + expect(ip).to eql(NetAddr::IPv4.parse('1.2.3.4').addr) counter += 1 end expect(counter).to eq(0) @@ -59,7 +59,7 @@ it 'should raise NetAddr::ValidationError' do range = '192.168.1.1-192.168.1.1/25' expect { @obj.each_ip(range) {} }.to raise_error Bosh::Director::NetworkInvalidIpRangeFormat, - "Invalid IP range format: #{range}" + /Invalid IP range format: #{range}/ end end end diff --git a/src/bosh-director/spec/unit/jobs/vm_state_spec.rb b/src/bosh-director/spec/unit/jobs/vm_state_spec.rb index bdfa2f5314e..49368b9d422 100644 --- a/src/bosh-director/spec/unit/jobs/vm_state_spec.rb +++ b/src/bosh-director/spec/unit/jobs/vm_state_spec.rb @@ -55,7 +55,7 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) expect(agent).to receive(:get_state).with('full').and_return( @@ -83,13 +83,13 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('2.2.2.2').to_i.to_s, + address_str: NetAddr::IPv4.parse('2.2.2.2').addr.to_s, task_id: '12345', ) end @@ -126,13 +126,13 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('2.2.2.2').to_i.to_s, + address_str: NetAddr::IPv4.parse('2.2.2.2').addr.to_s, task_id: '12345', ) @@ -169,7 +169,7 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) stub_agent_get_state_to_return_state_with_vitals @@ -389,7 +389,7 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) instance.update(spec: { 'vm_type' => { 'name' => 'fake-vm-type', 'cloud_properties' => {} } }) @@ -470,13 +470,13 @@ def stub_agent_get_state_to_return_state_with_vitals Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s, task_id: '12345', ) Models::IpAddress.make( instance_id: instance.id, vm_id: inactive_vm.id, - address_str: NetAddr::CIDR.create('1.1.1.2').to_i.to_s, + address_str: NetAddr::IPv4.parse('1.1.1.2').addr.to_s, task_id: '12345', ) allow(AgentClient).to receive(:with_agent_id).with('other_agent_id', anything, timeout: 5).and_return(lazy_agent) diff --git a/src/bosh-director/spec/unit/metrics_collector_spec.rb b/src/bosh-director/spec/unit/metrics_collector_spec.rb index 7096033ee03..2e6aa5e0728 100644 --- a/src/bosh-director/spec/unit/metrics_collector_spec.rb +++ b/src/bosh-director/spec/unit/metrics_collector_spec.rb @@ -163,7 +163,7 @@ def tick Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('192.168.1.5').to_i.to_s, + address_str: NetAddr::IPv4.parse('192.168.1.5').addr.to_s, network_name: manual_network_spec['name'], static: false, ) @@ -180,7 +180,7 @@ def tick Models::IpAddress.make( instance_id: instance.id, vm_id: vm.id, - address_str: NetAddr::CIDR.create('192.168.1.4').to_i.to_s, + address_str: NetAddr::IPv4.parse('192.168.1.4').addr.to_s, network_name: manual_network_spec['name'], static: true, ) diff --git a/src/bosh-director/spec/unit/models/ip_address_spec.rb b/src/bosh-director/spec/unit/models/ip_address_spec.rb index 1324d32c7c3..b5a247a8ba0 100644 --- a/src/bosh-director/spec/unit/models/ip_address_spec.rb +++ b/src/bosh-director/spec/unit/models/ip_address_spec.rb @@ -7,7 +7,7 @@ module Bosh::Director::Models described_class.make( instance: instance, network_name: 'foonetwork', - address_str: NetAddr::CIDR.create('10.10.0.1').to_i.to_s, + address_str: NetAddr::IPv4.parse('10.10.0.1').addr.to_s, static: true, vm: vm ) @@ -45,7 +45,7 @@ module Bosh::Director::Models ip.address_str = "" expect { ip.save }.to raise_error /address_str presence/ - ip.address_str = NetAddr::CIDR.create('10.10.0.1').to_i.to_s + ip.address_str = NetAddr::IPv4.parse('10.10.0.1').addr.to_s expect { ip.save }.not_to raise_error end diff --git a/src/bosh-director/spec/unit/models/vm_spec.rb b/src/bosh-director/spec/unit/models/vm_spec.rb index 5966f5fed4b..7d438cebe04 100644 --- a/src/bosh-director/spec/unit/models/vm_spec.rb +++ b/src/bosh-director/spec/unit/models/vm_spec.rb @@ -40,8 +40,8 @@ module Bosh::Director::Models end describe '#ips' do - let!(:ip_address) { BD::Models::IpAddress.make(vm: vm, address_str: NetAddr::CIDR.create('1.1.1.1').to_i.to_s) } - let!(:ip_address2) { BD::Models::IpAddress.make(vm: vm, address_str: NetAddr::CIDR.create('1.1.1.2').to_i.to_s) } + let!(:ip_address) { BD::Models::IpAddress.make(vm: vm, address_str: NetAddr::IPv4.parse('1.1.1.1').addr.to_s) } + let!(:ip_address2) { BD::Models::IpAddress.make(vm: vm, address_str: NetAddr::IPv4.parse('1.1.1.2').addr.to_s) } before do vm.network_spec = { 'some' => { 'ip' => '1.1.1.3' } } diff --git a/src/bosh-director/spec/unit/orphaned_vm_deleter_spec.rb b/src/bosh-director/spec/unit/orphaned_vm_deleter_spec.rb index eb1e203f39f..281d23c88d3 100644 --- a/src/bosh-director/spec/unit/orphaned_vm_deleter_spec.rb +++ b/src/bosh-director/spec/unit/orphaned_vm_deleter_spec.rb @@ -20,7 +20,7 @@ module Director Bosh::Director::Models::IpAddress.create( orphaned_vm: orphaned_vm1, network_name: 'my-manual-network', - address_str: NetAddr::CIDR.create('127.0.0.2').to_i, + address_str: NetAddr::IPv4.parse('127.0.0.2').addr, task_id: 1, ) end @@ -38,7 +38,7 @@ module Director Bosh::Director::Models::IpAddress.create( orphaned_vm: orphaned_vm2, network_name: 'my-manual-network', - address_str: NetAddr::CIDR.create('127.0.0.1').to_i, + address_str: NetAddr::IPv4.parse('127.0.0.1').addr, task_id: 1, ) end @@ -72,8 +72,8 @@ module Director it 'releases the ip address used by the vm' do subject.delete_all - expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::CIDR.create('127.0.0.1').to_i.to_s)).to be_nil - expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::CIDR.create('127.0.0.2').to_i.to_s)).to be_nil + expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::IPv4.parse('127.0.0.1').addr.to_s)).to be_nil + expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::IPv4.parse('127.0.0.2').addr.to_s)).to be_nil end it 'records bosh event for vm deletion' do @@ -174,8 +174,8 @@ module Director it 'deletes the model from the database' do subject.delete_all expect(Models::OrphanedVm.all.find { |vm| vm.cid == 'cid1' }).to be_nil - expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::CIDR.create('127.0.0.1').to_i.to_s)).to be_nil - expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::CIDR.create('127.0.0.2').to_i.to_s)).to be_nil + expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::IPv4.parse('127.0.0.1').addr.to_s)).to be_nil + expect(Bosh::Director::Models::IpAddress.first(address_str: NetAddr::IPv4.parse('127.0.0.2').addr.to_s)).to be_nil end end diff --git a/src/spec/integration/global_networking/failing_deploy_spec.rb b/src/spec/integration/global_networking/failing_deploy_spec.rb index 4a6334fe884..84eb2531d98 100644 --- a/src/spec/integration/global_networking/failing_deploy_spec.rb +++ b/src/spec/integration/global_networking/failing_deploy_spec.rb @@ -3,7 +3,7 @@ def make_subnet_spec(range, static_ips, zone_names = nil) spec = { 'range' => range, - 'gateway' => NetAddr::CIDR.create(range)[1].ip, + 'gateway' => NetAddr::IPv4Net.parse(range).nth(1).to_s, 'dns' => ['8.8.8.8'], 'static' => static_ips, 'reserved' => [], diff --git a/src/spec/support/networking_manifest_helper.rb b/src/spec/support/networking_manifest_helper.rb index 16912683d22..bb7d6d5d847 100644 --- a/src/spec/support/networking_manifest_helper.rb +++ b/src/spec/support/networking_manifest_helper.rb @@ -22,20 +22,20 @@ def self.cloud_config(opts) def self.make_subnet(opts) range = opts.fetch(:range, '192.168.1.0/24') - ip_range = NetAddr::CIDR.create(range) + ip_range = NetAddr::IPv4Net.parse(range) ip_range_shift = opts.fetch(:shift_ip_range_by, 0) available_ips = opts.fetch(:available_ips) - raise "not enough IPs, don't be so greedy" if available_ips > ip_range.size + raise "not enough IPs, don't be so greedy" if available_ips > ip_range.len ip_to_reserve_from = ip_range.nth(available_ips+2+ip_range_shift) # first IP is gateway, range is inclusive, so +2 - reserved_ips = ["#{ip_to_reserve_from}-#{ip_range.last}"] + reserved_ips = ["#{ip_to_reserve_from}-#{ip_range.nth(ip_range.len-1)}"] if ip_range_shift > 0 reserved_ips << "#{ip_range.nth(2)}-#{ip_range.nth(ip_range_shift+1)}" end { 'range' => ip_range.to_s, - 'gateway' => ip_range.nth(1), + 'gateway' => ip_range.nth(1).to_s, 'dns' => [], 'static' => [], 'reserved' => reserved_ips, From 3414bce9832ae4433fdf36e3d4bf3ff952ff0445 Mon Sep 17 00:00:00 2001 From: D062794 Date: Tue, 5 Apr 2022 18:18:28 +0200 Subject: [PATCH 3/3] Workaround for IPv6Net /64 anomaly in netaddr gem --- .../lib/bosh/director/ip_util.rb | 8 +++-- .../spec/unit/cidr_range_combiner_spec.rb | 4 +-- .../manual_network_subnet_spec.rb | 29 ++++++++++++++----- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/bosh-director/lib/bosh/director/ip_util.rb b/src/bosh-director/lib/bosh/director/ip_util.rb index 5e1f4af5a61..429fdf6fa37 100644 --- a/src/bosh-director/lib/bosh/director/ip_util.rb +++ b/src/bosh-director/lib/bosh/director/ip_util.rb @@ -98,13 +98,17 @@ def to_s private def parse(cidr) - NetAddr::IPv4Net.parse(cidr) + cidr = NetAddr::IPv4Net.parse(cidr) rescue NetAddr::ValidationError => e_v4 begin - NetAddr::IPv6Net.parse(cidr) + cidr = NetAddr::IPv6Net.parse(cidr) + if cidr.netmask.prefix_len <= 64 + raise "Unsupported CIDR prefix length. Please choose a CIDR mask numerically larger than /64." + end rescue NetAddr::ValidationError => e_v6 raise NetAddr::ValidationError, "IP CIDR format #{ip} is neither a valid IPv4 nor IPv6 format: #{e_v4} / #{e_v6}" end + cidr end end diff --git a/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb b/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb index 824d863faa9..c75b4a871da 100644 --- a/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb +++ b/src/bosh-director/spec/unit/cidr_range_combiner_spec.rb @@ -92,7 +92,7 @@ module Bosh::Director [ NetAddr::IPv4Net.parse('192.168.0.8/30'), NetAddr::IPv6Net.parse('fd7a:eeed:e696:968f:0000:0000:0000:0005/128'), - NetAddr::IPv6Net.parse('fd7a:eeed:e696:968f:0000:0000:0000:0005/64'), + NetAddr::IPv6Net.parse('fd7a:eeed:e696:968f:0000:0000:0000:0005/96'), NetAddr::IPv4Net.parse('192.168.0.20/32'), ] end @@ -100,7 +100,7 @@ module Bosh::Director it 'combines the ranges' do expect(range_combiner.combine_ranges(cidr_ranges)).to eq( [['192.168.0.8', '192.168.0.11'], ['192.168.0.20', '192.168.0.20'], - ['fd7a:eeed:e696:968f:0000:0000:0000:0000', 'fd7a:eeed:e696:968f:ffff:ffff:ffff:ffff']], + ['fd7a:eeed:e696:968f::', 'fd7a:eeed:e696:968f::ffff:ffff']], ) end end diff --git a/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb b/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb index 14f151d153e..43b1e091f5b 100644 --- a/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb +++ b/src/bosh-director/spec/unit/deployment_plan/manual_network_subnet_spec.rb @@ -86,7 +86,7 @@ def create_dynamic_reservation(ip) it 'should create an IPv6 subnet spec' do subnet = make_subnet( { - 'range' => 'fdab:d85c:118d:8a46::/64', + 'range' => 'fdab:d85c:118d:8a46::/96', 'gateway' => 'fdab:d85c:118d:8a46::1', 'reserved' => [ 'fdab:d85c:118d:8a46::10-fdab:d85c:118d:8a46::ff', @@ -105,16 +105,29 @@ def create_dynamic_reservation(ip) [], ) - expect(subnet.range.ip).to eq('fdab:d85c:118d:8a46:0000:0000:0000:0000') - subnet.range.ip.size == 2**64 - expect(subnet.netmask).to eq('ffff:ffff:ffff:ffff:0000:0000:0000:0000') - expect(subnet.gateway).to eq('fdab:d85c:118d:8a46:0000:0000:0000:0001') + expect(subnet.range.network.to_s).to eq('fdab:d85c:118d:8a46::') + # subnet.range.ip.size == 2**64 # TODO NETADDR: bug? / what was tested here? + expect(subnet.netmask).to eq('ffff:ffff:ffff:ffff:ffff:ffff::') + expect(subnet.gateway.to_s).to eq('fdab:d85c:118d:8a46::1') # TODO NETADDR: not sure how it worked before (was previously an object as well) expect(subnet.dns).to eq([ - "2001:4860:4860:0000:0000:0000:0000:8888", - "2001:4860:4860:0000:0000:0000:0000:8844", + "2001:4860:4860::8888", + "2001:4860:4860::8844", ]) end + it 'should fail for <= /64 IPv6 CIDR prefixes' do + expect { + make_subnet( + { + 'range' => 'fdab:d85c:118d:8a46::/64', + 'gateway' => 'fdab:d85c:118d:8a46::1', + 'cloud_properties' => {'foo' => 'bar'}, + }, + [] + ) + }.to raise_error(/Unsupported CIDR prefix length/) + end + it 'should require a range' do expect { make_subnet( @@ -385,7 +398,7 @@ def create_dynamic_reservation(ip) it 'should return false when IPv4 and IPv6 ranges are compared' do other = make_subnet( { - 'range' => 'f1ee:0000:0000:0000:0000:0000:0000:0000/64', + 'range' => 'f1ee:0000:0000:0000:0000:0000:0000:0000/96', 'gateway' => 'f1ee:0000:0000:0000:0000:0000:0000:0001', 'cloud_properties' => { 'foo' => 'bar' }, },