From da893aa63dcaf556ccbe5449713c43d462dcb71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kesson?= Date: Tue, 20 Jun 2017 14:33:02 +0200 Subject: [PATCH 1/5] net: Extracted Port_util from TCP to net namespace --- api/net/port_util.hpp | 139 ++++++++++++++++++++++++++++++++++++++++++ api/net/tcp/tcp.hpp | 85 +------------------------- src/net/tcp/tcp.cpp | 25 -------- 3 files changed, 140 insertions(+), 109 deletions(-) create mode 100644 api/net/port_util.hpp diff --git a/api/net/port_util.hpp b/api/net/port_util.hpp new file mode 100644 index 0000000000..e76a5bae31 --- /dev/null +++ b/api/net/port_util.hpp @@ -0,0 +1,139 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2017 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#ifndef NET_PORT_UTIL_HPP +#define NET_PORT_UTIL_HPP + +#include "inet_common.hpp" +#include + +namespace net { + +struct Port_error : public std::runtime_error { + using base = std::runtime_error; + using base::base; +}; + +/** + * @brief Class for port utility. + */ +class Port_util { +public: + /** + * @brief Construct a port util with a new generated ephemeral port + * and a empty port list. + */ + Port_util() + : ports(), + ephemeral_(net::new_ephemeral_port()), + eph_count(0) + { + ports.set(port_ranges::DYNAMIC_END); + } + + /** + * @brief Gets the next ephemeral port. + * increment_ephemeral may throw + * + * @return The next ephemeral port. + */ + uint16_t get_next_ephemeral() + { + increment_ephemeral(); + return ephemeral_; + } + + /** + * @brief Bind a port, making it reserved. + * + * @param[in] port The port + */ + void bind(const uint16_t port) noexcept + { + Expects(port < port_ranges::DYNAMIC_END); + ports.set(port); + + if(port_ranges::is_dynamic(port)) ++eph_count; + } + + /** + * @brief Unbind a port, making it available. + * + * @param[in] port The port + */ + void unbind(const uint16_t port) noexcept + { + Expects(port < port_ranges::DYNAMIC_END); + ports.reset(port); + + if(port_ranges::is_dynamic(port)) --eph_count; + } + + /** + * @brief Determines if the port is bound. + * + * @param[in] port The port + * + * @return True if bound, False otherwise. + */ + bool is_bound(const uint16_t port) const noexcept + { + Expects(port < port_ranges::DYNAMIC_END); + return ports[port]; + } + + /** + * @brief Determines if it has any free ephemeral ports. + * + * @return True if has free ephemeral, False otherwise. + */ + bool has_free_ephemeral() const noexcept + { return eph_count < (port_ranges::DYNAMIC_END - port_ranges::DYNAMIC_START); } + +private: + std::bitset<65536> ports; + uint16_t ephemeral_; + uint16_t eph_count; + + /** + * @brief Increment the ephemeral port by one. + * Throws if there are no more free ephemeral ports available. + */ + void increment_ephemeral() + { + if(UNLIKELY( not has_free_ephemeral() )) + throw Port_error{"All ephemeral ports are taken"}; + + ephemeral_++; + + // wrap around to dynamic start if end + if(UNLIKELY(ephemeral_ == port_ranges::DYNAMIC_END)) + ephemeral_ = port_ranges::DYNAMIC_START; + + // TODO: Avoid wrap around, increment ephemeral to next free port. + // while(is_bound(ephemeral_)) ++ephemeral_; // worst case is like 16k iterations :D + // need a solution that checks each word of the subset (the dynamic range) + // FIXME: this may happen... + if(UNLIKELY( is_bound(ephemeral_) )) + throw Port_error{"Generated ephemeral port is already bound. Please fix me!"}; + } +}; // < class Port_util + +} // < namespace net + +#endif diff --git a/api/net/tcp/tcp.hpp b/api/net/tcp/tcp.hpp index 33e9b7cc52..716f587497 100644 --- a/api/net/tcp/tcp.hpp +++ b/api/net/tcp/tcp.hpp @@ -29,7 +29,7 @@ #include // writeq #include #include -#include +#include namespace net { @@ -54,89 +54,6 @@ namespace net { private: using Listeners = std::map>; using Connections = std::map; - - /** - * @brief Class for port utility. - */ - class Port_util { - public: - /** - * @brief Construct a port util with a new generated ephemeral port - * and a empty port list. - */ - Port_util(); - - /** - * @brief Gets the next ephemeral port. - * increment_ephemeral may throw - * - * @return The next ephemeral port. - */ - uint16_t get_next_ephemeral() - { - increment_ephemeral(); - return ephemeral_; - } - - /** - * @brief Bind a port, making it reserved. - * - * @param[in] port The port - */ - void bind(const uint16_t port) noexcept - { - Expects(port < port_ranges::DYNAMIC_END); - ports.set(port); - - if(port_ranges::is_dynamic(port)) ++eph_count; - } - - /** - * @brief Unbind a port, making it available. - * - * @param[in] port The port - */ - void unbind(const uint16_t port) noexcept - { - Expects(port < port_ranges::DYNAMIC_END); - ports.reset(port); - - if(port_ranges::is_dynamic(port)) --eph_count; - } - - /** - * @brief Determines if the port is bound. - * - * @param[in] port The port - * - * @return True if bound, False otherwise. - */ - bool is_bound(const uint16_t port) const noexcept - { - Expects(port < port_ranges::DYNAMIC_END); - return ports[port]; - } - - /** - * @brief Determines if it has any free ephemeral ports. - * - * @return True if has free ephemeral, False otherwise. - */ - bool has_free_ephemeral() const noexcept - { return eph_count < (port_ranges::DYNAMIC_END - port_ranges::DYNAMIC_START); } - - private: - std::bitset<65536> ports; - uint16_t ephemeral_; - uint16_t eph_count; - - /** - * @brief Increment the ephemeral port by one. - * Throws if there are no more free ephemeral ports available. - */ - void increment_ephemeral(); - - }; // < class Port_util using Port_lists = std::map; public: diff --git a/src/net/tcp/tcp.cpp b/src/net/tcp/tcp.cpp index 8cf2071b8c..d398d9c21c 100644 --- a/src/net/tcp/tcp.cpp +++ b/src/net/tcp/tcp.cpp @@ -79,31 +79,6 @@ void TCP::smp_process_writeq(size_t packets) SMP::signal(this->cpu_id); } -TCP::Port_util::Port_util() - : ports{}, - ephemeral_(new_ephemeral_port()), - eph_count(0) -{ - ports.set(port_ranges::DYNAMIC_END); -} - -void TCP::Port_util::increment_ephemeral() -{ - if(UNLIKELY(! has_free_ephemeral() )) - throw TCP_error{"All ephemeral ports are taken"}; - - ephemeral_++; - - if(UNLIKELY(ephemeral_ == port_ranges::DYNAMIC_END)) - ephemeral_ = port_ranges::DYNAMIC_START; - - // TODO: Avoid wrap around, increment ephemeral to next free port. - // while(is_bound(ephemeral_)) ++ephemeral_; // worst case is like 16k iterations :D - // need a solution that checks each word of the subset (the dynamic range) - // FIXME: this may happen... - Ensures(is_bound(ephemeral_) == false && "Hoped I wouldn't see the day..."); -} - /* Note: There is different approaches to how to handle listeners & connections. Need to discuss and decide for the best one. From 6d5055df76f7577d6327c7aab5b6b7bb4fa328ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kesson?= Date: Tue, 20 Jun 2017 16:10:11 +0200 Subject: [PATCH 2/5] test: Use built-in hton to avoid macro conflict on mac OS --- test/net/unit/dhcp_message_test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/net/unit/dhcp_message_test.cpp b/test/net/unit/dhcp_message_test.cpp index 68fd78f4d7..75896b5128 100644 --- a/test/net/unit/dhcp_message_test.cpp +++ b/test/net/unit/dhcp_message_test.cpp @@ -32,6 +32,7 @@ struct test_opt // Creates a DHCP DISCOVERY message on the buffer net::dhcp::Message* create_discovery_msg(uint8_t* buffer) { + using namespace net; using namespace net::dhcp; auto* msg = reinterpret_cast(buffer); @@ -39,9 +40,9 @@ net::dhcp::Message* create_discovery_msg(uint8_t* buffer) msg->htype = static_cast(htype::ETHER); msg->hlen = ETH_ALEN; msg->hops = 0; - msg->xid = net::htonl(322420); + msg->xid = htonl(322420); msg->secs = 0; - msg->flags = net::htons(static_cast(flag::BOOTP_BROADCAST)); + msg->flags = htons(static_cast(flag::BOOTP_BROADCAST)); msg->ciaddr = 0; msg->yiaddr = 0; msg->siaddr = 0; From dc393793024bb944bf07bef35e33e614d0f261d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kesson?= Date: Tue, 20 Jun 2017 16:35:58 +0200 Subject: [PATCH 3/5] test: Added unit test for Port_util --- api/net/port_util.hpp | 3 +- test/CMakeLists.txt | 1 + test/net/unit/port_util_test.cpp | 65 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 test/net/unit/port_util_test.cpp diff --git a/api/net/port_util.hpp b/api/net/port_util.hpp index e76a5bae31..f1e9038154 100644 --- a/api/net/port_util.hpp +++ b/api/net/port_util.hpp @@ -129,8 +129,7 @@ class Port_util { // while(is_bound(ephemeral_)) ++ephemeral_; // worst case is like 16k iterations :D // need a solution that checks each word of the subset (the dynamic range) // FIXME: this may happen... - if(UNLIKELY( is_bound(ephemeral_) )) - throw Port_error{"Generated ephemeral port is already bound. Please fix me!"}; + Expects(not is_bound(ephemeral_) && "Generated ephemeral port is already bound. Please fix me!"); } }; // < class Port_util diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 648511b0b0..518aaa0d7c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -85,6 +85,7 @@ set(TEST_SOURCES ${TEST}/net/unit/ip4_addr.cpp ${TEST}/net/unit/ip4.cpp ${TEST}/net/unit/packets.cpp + ${TEST}/net/unit/port_util_test.cpp ${TEST}/net/unit/socket.cpp ${TEST}/net/unit/tcp_packet_test.cpp ${TEST}/net/unit/tcp_write_queue.cpp diff --git a/test/net/unit/port_util_test.cpp b/test/net/unit/port_util_test.cpp new file mode 100644 index 0000000000..3617ef5a22 --- /dev/null +++ b/test/net/unit/port_util_test.cpp @@ -0,0 +1,65 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2017 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +CASE("Binding and unbinding") +{ + net::Port_util util; + + const uint16_t port = 322; + + EXPECT(util.is_bound(port) == false); + + util.bind(port); + + EXPECT(util.is_bound(port) == true); + + util.unbind(port); + + EXPECT(util.is_bound(port) == false); +} + +CASE("Generating ephemeral port throws when all are bound") +{ + net::Port_util util; + + // Bind all + for(auto i = 0; i < (net::port_ranges::DYNAMIC_END - net::port_ranges::DYNAMIC_START); ++i) + util.bind(util.get_next_ephemeral()); + + EXPECT_THROWS_AS(util.get_next_ephemeral(), net::Port_error); +} + +CASE("[.expectedfailure] Generating ephemeral handles wrap around") +{ + net::Port_util util; + + auto port = util.get_next_ephemeral(); + + // bind first one + util.bind(port); + + // wrap around + for(auto i = 0; i < (net::port_ranges::DYNAMIC_END - net::port_ranges::DYNAMIC_START - 1); ++i) + util.get_next_ephemeral(); + + auto port2 = util.get_next_ephemeral(); + + EXPECT(port != port2); +} From 9bfbb43404b1f9767448a7637fb43235225c3c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kesson?= Date: Wed, 21 Jun 2017 11:49:06 +0200 Subject: [PATCH 4/5] util: Added a fixed version of MemBitmap which owns storage --- api/util/fixed_bitmap.hpp | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 api/util/fixed_bitmap.hpp diff --git a/api/util/fixed_bitmap.hpp b/api/util/fixed_bitmap.hpp new file mode 100644 index 0000000000..4e7cc7b199 --- /dev/null +++ b/api/util/fixed_bitmap.hpp @@ -0,0 +1,50 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2017 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#ifndef UTIL_FIXED_BITMAP_HPP +#define UTIL_FIXED_BITMAP_HPP + +#include "membitmap.hpp" +#include + +/** + * @brief A membitmap with a fixed amount of bits and storage. + * + * @tparam N Number of bits. Needs to be divisable by sizeof(Storage) + */ +template +class Fixed_bitmap : public MemBitmap { +public: + using Storage = MemBitmap::word; + static_assert(N >= sizeof(Storage), "Number of bits need to be atleast sizeof(Storage)"); + static_assert(N % sizeof(Storage) == 0, "Number of bits need to be divisable by sizeof(Storage)"); + +public: + Fixed_bitmap() : + MemBitmap{}, + storage{} + { + set_location(storage.data(), N / sizeof(Storage)); + } + +private: + std::array storage; + +}; // < class Fixed_bitmap + +#endif From f38d7f8c16d1352ad7d54dada3732b6bbdb4104e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kesson?= Date: Wed, 21 Jun 2017 11:53:49 +0200 Subject: [PATCH 5/5] util: Replaced bitset with Fixed_bitmap, inversed bits, allow 65535 as eph port, fixed wrap around issue --- api/net/inet_common.hpp | 2 +- api/net/port_util.hpp | 42 +++++++++++++++++++------------- test/net/unit/port_util_test.cpp | 6 ++++- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/api/net/inet_common.hpp b/api/net/inet_common.hpp index 8eb63fac66..a30d4550dc 100644 --- a/api/net/inet_common.hpp +++ b/api/net/inet_common.hpp @@ -161,7 +161,7 @@ namespace net { static constexpr uint16_t USER_START {1024}; static constexpr uint16_t USER_END {49151}; static constexpr uint16_t DYNAMIC_START {49152}; - static constexpr uint16_t DYNAMIC_END {65535}; // 65535 should never be assigned + static constexpr uint16_t DYNAMIC_END {65535}; static constexpr bool is_dynamic(const uint16_t port) noexcept { return port > USER_END; } diff --git a/api/net/port_util.hpp b/api/net/port_util.hpp index f1e9038154..f683df8715 100644 --- a/api/net/port_util.hpp +++ b/api/net/port_util.hpp @@ -20,7 +20,7 @@ #define NET_PORT_UTIL_HPP #include "inet_common.hpp" -#include +#include namespace net { @@ -30,7 +30,9 @@ struct Port_error : public std::runtime_error { }; /** - * @brief Class for port utility. + * @brief Class for handling a full range of network ports. + * Generates ephemeral ports and track what ports are bound or not. + * 1 means free, 0 means bound (occupied) */ class Port_util { public: @@ -40,10 +42,15 @@ class Port_util { */ Port_util() : ports(), + eph_view{ // set the ephemeral view to be between 49152-65535 + ports.data() + port_ranges::DYNAMIC_START, + (port_ranges::DYNAMIC_END - port_ranges::DYNAMIC_START + 1) / sizeof(MemBitmap::word) + }, ephemeral_(net::new_ephemeral_port()), eph_count(0) { - ports.set(port_ranges::DYNAMIC_END); + // all ports are free + ports.set_all(); } /** @@ -65,8 +72,7 @@ class Port_util { */ void bind(const uint16_t port) noexcept { - Expects(port < port_ranges::DYNAMIC_END); - ports.set(port); + ports.reset(port); if(port_ranges::is_dynamic(port)) ++eph_count; } @@ -78,8 +84,7 @@ class Port_util { */ void unbind(const uint16_t port) noexcept { - Expects(port < port_ranges::DYNAMIC_END); - ports.reset(port); + ports.set(port); if(port_ranges::is_dynamic(port)) --eph_count; } @@ -93,8 +98,7 @@ class Port_util { */ bool is_bound(const uint16_t port) const noexcept { - Expects(port < port_ranges::DYNAMIC_END); - return ports[port]; + return !ports[port]; } /** @@ -106,9 +110,10 @@ class Port_util { { return eph_count < (port_ranges::DYNAMIC_END - port_ranges::DYNAMIC_START); } private: - std::bitset<65536> ports; - uint16_t ephemeral_; - uint16_t eph_count; + Fixed_bitmap<65536> ports; + MemBitmap eph_view; + uint16_t ephemeral_; + uint16_t eph_count; /** * @brief Increment the ephemeral port by one. @@ -122,13 +127,16 @@ class Port_util { ephemeral_++; // wrap around to dynamic start if end - if(UNLIKELY(ephemeral_ == port_ranges::DYNAMIC_END)) + if(UNLIKELY(ephemeral_ == 0)) ephemeral_ = port_ranges::DYNAMIC_START; - // TODO: Avoid wrap around, increment ephemeral to next free port. - // while(is_bound(ephemeral_)) ++ephemeral_; // worst case is like 16k iterations :D - // need a solution that checks each word of the subset (the dynamic range) - // FIXME: this may happen... + if(UNLIKELY( is_bound(ephemeral_) )) + { + auto i = eph_view.first_set(); + Ensures(i != -1 && "Did not found a free ephemeral even tho has_free_ephemeral() == true..."); + ephemeral_ = port_ranges::DYNAMIC_START + i; + } + Expects(not is_bound(ephemeral_) && "Generated ephemeral port is already bound. Please fix me!"); } }; // < class Port_util diff --git a/test/net/unit/port_util_test.cpp b/test/net/unit/port_util_test.cpp index 3617ef5a22..263f94580b 100644 --- a/test/net/unit/port_util_test.cpp +++ b/test/net/unit/port_util_test.cpp @@ -39,14 +39,18 @@ CASE("Generating ephemeral port throws when all are bound") { net::Port_util util; + EXPECT(util.has_free_ephemeral() == true); + // Bind all for(auto i = 0; i < (net::port_ranges::DYNAMIC_END - net::port_ranges::DYNAMIC_START); ++i) util.bind(util.get_next_ephemeral()); + EXPECT(util.has_free_ephemeral() == false); + EXPECT_THROWS_AS(util.get_next_ephemeral(), net::Port_error); } -CASE("[.expectedfailure] Generating ephemeral handles wrap around") +CASE("Generating ephemeral handles wrap around") { net::Port_util util;