diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7da2786 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Makefiles"] + path = Makefiles + url = https://gogs.dierkse.nl/JDierkse/Makefiles.git diff --git a/Libraries/Logging b/Libraries/Logging new file mode 120000 index 0000000..e18bda1 --- /dev/null +++ b/Libraries/Logging @@ -0,0 +1 @@ +../../Logging \ No newline at end of file diff --git a/Makefiles b/Makefiles deleted file mode 120000 index 2c30269..0000000 --- a/Makefiles +++ /dev/null @@ -1 +0,0 @@ -../../Makefiles \ No newline at end of file diff --git a/Makefiles b/Makefiles new file mode 160000 index 0000000..d82c8a2 --- /dev/null +++ b/Makefiles @@ -0,0 +1 @@ +Subproject commit d82c8a204714538d0e9414808388e8c79f20db26 diff --git a/Network/BroadcastClientImpl.cpp b/Network/BroadcastClientImpl.cpp new file mode 100644 index 0000000..020b30a --- /dev/null +++ b/Network/BroadcastClientImpl.cpp @@ -0,0 +1,12 @@ +#include "BroadcastClientImpl.h" + + +namespace Network { + +BroadcastClientImpl::BroadcastClientImpl(const std::string& listenAddress, int broadcastPort, std::function callback) : + UdpClientImpl(listenAddress, "255.255.255.255", broadcastPort, callback) +{ + Receive(); +} + +} // namespace Network diff --git a/Network/BroadcastClientImpl.h b/Network/BroadcastClientImpl.h new file mode 100644 index 0000000..2ca4da8 --- /dev/null +++ b/Network/BroadcastClientImpl.h @@ -0,0 +1,19 @@ +#ifndef NETWORK_BROADCASTCLIENTIMPL_H +#define NETWORK_BROADCASTCLIENTIMPL_H + +#include +#include +#include "UdpClientImpl.h" + + +namespace Network { + +class BroadcastClientImpl : public UdpClientImpl +{ +public: + BroadcastClientImpl(const std::string& listenAddress, int broadcastPort, std::function callback); +}; + +} // namespace Network + +#endif // NETWORK_BROADCASTCLIENTIMPL_H diff --git a/Network/BroadcastServerImpl.cpp b/Network/BroadcastServerImpl.cpp new file mode 100644 index 0000000..3983f97 --- /dev/null +++ b/Network/BroadcastServerImpl.cpp @@ -0,0 +1,11 @@ +#include "BroadcastServerImpl.h" + + +namespace Network { + +BroadcastServerImpl::BroadcastServerImpl(const std::string& listenAddress, int broadcastPort, std::function callback) : + UdpClientImpl(listenAddress, "255.255.255.255", broadcastPort, callback) +{ +} + +} // namespace Network diff --git a/Network/BroadcastServerImpl.h b/Network/BroadcastServerImpl.h new file mode 100644 index 0000000..2446ac4 --- /dev/null +++ b/Network/BroadcastServerImpl.h @@ -0,0 +1,19 @@ +#ifndef NETWORK_BROADCASTSERVER_IMPL_H +#define NETWORK_BROADCASTSERVER_IMPL_H + +#include +#include +#include "UdpClientImpl.h" + + +namespace Network { + +class BroadcastServerImpl : public UdpClientImpl +{ +public: + BroadcastServerImpl(const std::string& listenAddress, int broadcastPort, std::function callback); +}; + +} // namespace Network + +#endif // NETWORK_BROADCASTSERVERIMPL_H diff --git a/Network/IcmpClientImpl.cpp b/Network/IcmpClientImpl.cpp new file mode 100644 index 0000000..7ad4d50 --- /dev/null +++ b/Network/IcmpClientImpl.cpp @@ -0,0 +1,85 @@ +#include +#include "IcmpClientImpl.h" + + +namespace Network { + +IcmpClientImpl::IcmpClientImpl() : + m_socket(m_ioContext, asio::ip::icmp::v4()), + m_done(false) +{ + StartReceive(); + m_thread = std::thread([&] { m_ioContext.run(); }); + +#ifdef __linux__ + pthread_setname_np(m_thread.native_handle(), "IcmpClientImpl"); +#endif +} + +IcmpClientImpl::~IcmpClientImpl() +{ + m_ioContext.stop(); + m_thread.join(); +} + +bool IcmpClientImpl::Ping(const std::string& targetAddress) +{ + m_done = false; + + SendEchoRequest(asio::ip::icmp::endpoint(asio::ip::address::from_string(targetAddress), 0), std::string()); + + std::unique_lock lock(m_mutex); + if (m_condition.wait_for(lock, std::chrono::seconds(3), [&] { return m_done; })) + return true; + + return false; +} + +void IcmpClientImpl::SendEchoRequest(const asio::ip::icmp::endpoint& targetEndpoint, const std::string& data) +{ + Internal::IcmpHeader echoRequest; + echoRequest.type(Internal::IcmpHeader::echo_request); + echoRequest.code(0); + echoRequest.identifier(GetIdentifier()); + echoRequest.sequence_number(0); + Internal::ComputeChecksum(echoRequest, data.begin(), data.end()); + + asio::streambuf requestBuffer; + std::ostream os(&requestBuffer); + os << echoRequest << data; + + m_socket.send_to(requestBuffer.data(), targetEndpoint); +} + +void IcmpClientImpl::StartReceive() +{ + m_replyBuffer.consume(m_replyBuffer.size()); + m_socket.async_receive(m_replyBuffer.prepare(65536), std::bind(&IcmpClientImpl::HandleReceive, this, std::placeholders::_1, std::placeholders::_2)); +} + +void IcmpClientImpl::HandleReceive(const asio::error_code& error, std::size_t length) +{ + m_replyBuffer.commit(length); + + std::istream is(&m_replyBuffer); + Internal::Ipv4Header ipv4Header; + Internal::IcmpHeader icmpHeader; + is >> ipv4Header >> icmpHeader; + + if (is && icmpHeader.type() == Internal::IcmpHeader::echo_reply + && icmpHeader.identifier() == GetIdentifier() + && icmpHeader.sequence_number() == 0) + { + m_done = true; + m_condition.notify_all(); + } + + StartReceive(); +} + +unsigned short IcmpClientImpl::GetIdentifier() +{ + return static_cast(::getpid()); +} + +} // namespace Network diff --git a/Network/IcmpClientImpl.h b/Network/IcmpClientImpl.h new file mode 100644 index 0000000..688c165 --- /dev/null +++ b/Network/IcmpClientImpl.h @@ -0,0 +1,46 @@ +#ifndef NETWORK_ICMPCLIENTIMPL_H +#define NETWORK_ICMPCLIENTIMPL_H + +#include +#include +#include +#include +#include +#include +#include "asio.h" +#include "Ipv4Header.h" +#include "IcmpHeader.h" + + +namespace Network { + +class IcmpClientImpl +{ +public: + IcmpClientImpl(); + ~IcmpClientImpl(); + +public: + bool Ping(const std::string& targetAddress); + +private: + void SendEchoRequest(const asio::ip::icmp::endpoint& targetEndpoint, const std::string& data); + void StartReceive(); + void HandleReceive(const asio::error_code& error, std::size_t length); + unsigned short GetIdentifier(); + +private: + std::thread m_thread; + std::mutex m_mutex; + std::condition_variable m_condition; + + asio::io_context m_ioContext; + asio::ip::icmp::socket m_socket; + asio::streambuf m_replyBuffer; + + bool m_done; +}; + +} // namespace Network + +#endif // NETWORK_ICMPCLIENTIMPL_H diff --git a/Network/IcmpHeader.h b/Network/IcmpHeader.h new file mode 100644 index 0000000..cc07d4f --- /dev/null +++ b/Network/IcmpHeader.h @@ -0,0 +1,87 @@ +#ifndef ICMPHEADER_H +#define ICMPHEADER_H + +#include +#include "asio.h" + + +namespace Network { +namespace Internal { + +class IcmpHeader +{ +public: + enum + { + echo_reply = 0, + destination_unreachable = 3, + source_quench = 4, + redirect = 5, + echo_request = 8, + time_exceeded = 11, + parameter_problem = 12, + timestamp_request = 13, + timestamp_reply = 14, + info_request = 15, + info_reply = 16, + address_request = 17, + address_reply = 18 + }; + + IcmpHeader() { std::fill(rep_, rep_ + sizeof(rep_), 0); } + + unsigned char type() const { return rep_[0]; } + unsigned char code() const { return rep_[1]; } + unsigned short checksum() const { return decode(2, 3); } + unsigned short identifier() const { return decode(4, 5); } + unsigned short sequence_number() const { return decode(6, 7); } + + void type(unsigned char n) { rep_[0] = n; } + void code(unsigned char n) { rep_[1] = n; } + void checksum(unsigned short n) { encode(2, 3, n); } + void identifier(unsigned short n) { encode(4, 5, n); } + void sequence_number(unsigned short n) { encode(6, 7, n); } + + friend std::istream& operator>>(std::istream& is, IcmpHeader& header) + { return is.read(reinterpret_cast(header.rep_), 8); } + + friend std::ostream& operator<<(std::ostream& os, const IcmpHeader& header) + { return os.write(reinterpret_cast(header.rep_), 8); } + + private: + unsigned short decode(int a, int b) const + { return (rep_[a] << 8) + rep_[b]; } + + void encode(int a, int b, unsigned short n) + { + rep_[a] = static_cast(n >> 8); + rep_[b] = static_cast(n & 0xFF); + } + + unsigned char rep_[8]; +}; + +template +void ComputeChecksum(IcmpHeader& header, + Iterator body_begin, Iterator body_end) +{ + unsigned int sum = (header.type() << 8) + header.code() + + header.identifier() + header.sequence_number(); + + Iterator body_iter = body_begin; + while (body_iter != body_end) + { + sum += (static_cast(*body_iter++) << 8); + if (body_iter != body_end) + sum += static_cast(*body_iter++); + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + header.checksum(static_cast(~sum)); +} + +} // namespace Internal +} // namespace Network + +#endif // ICMPHEADER_H diff --git a/Network/Ipv4Header.h b/Network/Ipv4Header.h new file mode 100644 index 0000000..840e5a2 --- /dev/null +++ b/Network/Ipv4Header.h @@ -0,0 +1,63 @@ +#ifndef IPV4HEADER_H +#define IPV4HEADER_H + +#include +#include "asio.h" + + +namespace Network { +namespace Internal { + +class Ipv4Header +{ +public: + Ipv4Header() { std::fill(rep_, rep_ + sizeof(rep_), 0); } + + unsigned char version() const { return (rep_[0] >> 4) & 0xF; } + unsigned short header_length() const { return (rep_[0] & 0xF) * 4; } + unsigned char type_of_service() const { return rep_[1]; } + unsigned short total_length() const { return decode(2, 3); } + unsigned short identification() const { return decode(4, 5); } + bool dont_fragment() const { return (rep_[6] & 0x40) != 0; } + bool more_fragments() const { return (rep_[6] & 0x20) != 0; } + unsigned short fragment_offset() const { return decode(6, 7) & 0x1FFF; } + unsigned int time_to_live() const { return rep_[8]; } + unsigned char protocol() const { return rep_[9]; } + unsigned short header_checksum() const { return decode(10, 11); } + + asio::ip::address_v4 source_address() const + { + asio::ip::address_v4::bytes_type bytes = { { rep_[12], rep_[13], rep_[14], rep_[15] } }; + return asio::ip::address_v4(bytes); + } + + asio::ip::address_v4 destination_address() const + { + asio::ip::address_v4::bytes_type bytes = { { rep_[16], rep_[17], rep_[18], rep_[19] } }; + return asio::ip::address_v4(bytes); + } + + friend std::istream& operator>>(std::istream& is, Ipv4Header& header) + { + is.read(reinterpret_cast(header.rep_), 20); + if (header.version() != 4) + is.setstate(std::ios::failbit); + std::streamsize options_length = header.header_length() - 20; + if (options_length < 0 || options_length > 40) + is.setstate(std::ios::failbit); + else + is.read(reinterpret_cast(header.rep_) + 20, options_length); + return is; + } + +private: + unsigned short decode(int a, int b) const + { return (rep_[a] << 8) + rep_[b]; } + + unsigned char rep_[60]; +}; + +} // namespace Internal +} // namespace Network + +#endif // IPV4HEADER_H diff --git a/Network/MulticastClientImpl.cpp b/Network/MulticastClientImpl.cpp new file mode 100644 index 0000000..e1e932f --- /dev/null +++ b/Network/MulticastClientImpl.cpp @@ -0,0 +1,13 @@ +#include "MulticastClientImpl.h" + + +namespace Network { + +MulticastClientImpl::MulticastClientImpl(const std::string& listenAddress, const std::string& multicastAddress, int multicastPort, std::function callback) : + UdpClientImpl(listenAddress, multicastAddress, multicastPort, callback) +{ + EnableMulticast(); + Receive(); +} + +} // namespace Network diff --git a/Network/MulticastClientImpl.h b/Network/MulticastClientImpl.h new file mode 100644 index 0000000..6b8e23a --- /dev/null +++ b/Network/MulticastClientImpl.h @@ -0,0 +1,19 @@ +#ifndef NETWORK_MULTICASTCLIENTIMPL_H +#define NETWORK_MULTICASTCLIENTIMPL_H + +#include +#include +#include "UdpClientImpl.h" + + +namespace Network { + +class MulticastClientImpl : public UdpClientImpl +{ +public: + MulticastClientImpl(const std::string& listenAddress, const std::string& multicastAddress, int multicastPort, std::function callback); +}; + +} // namespace Network + +#endif // NETWORK_MULTICASTCLIENTIMPL_H diff --git a/Network/UdpClientImpl.cpp b/Network/UdpClientImpl.cpp new file mode 100644 index 0000000..e21cb9f --- /dev/null +++ b/Network/UdpClientImpl.cpp @@ -0,0 +1,171 @@ +#include "Logging.h" +#include "UdpClientImpl.h" + + +namespace Network { + +UdpClientImpl::UdpClientImpl(const std::string& listenAddress, const std::string& targetAddress, int targetPort, std::function callback) : + m_bufferLength(1024), + m_deadline(m_ioServiceAnswer), + m_listenAddress(asio::ip::address::from_string(listenAddress)), + m_targetAddress(asio::ip::address::from_string(targetAddress)), + m_targetPort(targetPort), + m_receiveSocket(m_ioService), + m_answerSocket(m_ioServiceAnswer), + m_targetEndpoint(m_targetAddress, m_targetPort), + m_callback(callback) +{ + m_thread = std::thread([&] { m_ioService.run(); }); + +#ifdef __linux__ + pthread_setname_np(m_thread.native_handle(), "UdpClientImpl"); +#endif + + m_socketReceiveData.reserve(m_bufferLength); + m_socketAnswerData.reserve(m_bufferLength); + + asio::ip::udp::endpoint listenEndpoint(m_targetAddress, m_targetPort); + + m_receiveSocket.open(listenEndpoint.protocol()); + m_receiveSocket.set_option(asio::ip::udp::socket::reuse_address(true)); + m_receiveSocket.bind(listenEndpoint); +} + +UdpClientImpl::~UdpClientImpl() +{ + m_ioService.stop(); + m_thread.join(); +} + +void UdpClientImpl::Receive() +{ + ReceiveSocket(); + + m_deadline.expires_at(asio::system_timer::time_point(std::chrono::system_clock::duration::max())); + CheckDeadline(); +} + +std::string UdpClientImpl::Send(const std::string& data, int sourcePort) +{ + asio::ip::udp::socket sendSocket(m_ioService); + sendSocket.open(asio::ip::udp::v4()); + + sendSocket.set_option(asio::ip::udp::socket::reuse_address(true)); + + if (sourcePort != 0) + { + asio::ip::udp::endpoint sendEndpoint(m_listenAddress, sourcePort); + sendSocket.bind(sendEndpoint); + } + + try + { + sendSocket.connect(m_targetEndpoint); + + asio::ip::udp::endpoint listenEndpoint(m_listenAddress, sendSocket.local_endpoint().port()); + m_answerSocket.open(listenEndpoint.protocol()); + m_answerSocket.set_option(asio::ip::udp::socket::reuse_address(true)); + m_answerSocket.bind(listenEndpoint); + + sendSocket.send(asio::buffer(data)); + + asio::error_code error = asio::error::would_block; + std::size_t length = 0; + + m_deadline.expires_from_now(std::chrono::seconds(1)); + m_answerSocket.async_receive(asio::buffer(m_socketAnswerData.data(), m_bufferLength), std::bind(&UdpClientImpl::HandleAnswer, this, std::placeholders::_1, std::placeholders::_2, &error, &length)); + + do m_ioServiceAnswer.run_one(); + while (error == asio::error::would_block); + + std::string answer; + if (error != asio::error::operation_aborted) + answer = std::string(m_socketAnswerData.begin(), m_socketAnswerData.begin() + length); + else + answer = "Error"; + + m_answerSocket.close(); + return answer; + } + catch (const asio::system_error& ex) + { + std::stringstream ss; + ss << "UdpClientImpl::Send() - Error: " << ex.what() << std::endl; + Logging::Log(Logging::Severity::Error, ss.str()); + return "Error"; + } +} + +void UdpClientImpl::Broadcast(const std::string& data, int sourcePort) +{ + asio::ip::udp::socket sendSocket(m_ioService); + sendSocket.open(asio::ip::udp::v4()); + + sendSocket.set_option(asio::ip::udp::socket::reuse_address(true)); + sendSocket.set_option(asio::socket_base::broadcast(true)); + + if (sourcePort != 0) + { + asio::ip::udp::endpoint sendEndpoint(m_listenAddress, sourcePort); + sendSocket.bind(sendEndpoint); + } + + asio::ip::udp::endpoint targetEndpoint(asio::ip::address_v4::broadcast(), m_targetPort); + + try + { + sendSocket.connect(targetEndpoint); + + sendSocket.send(asio::buffer(data)); + } + catch (const asio::system_error& ex) + { + std::stringstream ss; + ss << "UdpClientImpl::Broadcast() - Error: " << ex.what() << std::endl; + Logging::Log(Logging::Severity::Error, ss.str()); + } + sendSocket.close(); +} + +void UdpClientImpl::EnableMulticast() +{ + m_receiveSocket.set_option(asio::ip::multicast::join_group(m_targetAddress.to_v4(), m_listenAddress.to_v4())); +} + +void UdpClientImpl::CheckDeadline() +{ + if (m_deadline.expires_at() <= std::chrono::system_clock::now()) + { + m_answerSocket.cancel(); + m_deadline.expires_at(asio::system_timer::time_point(std::chrono::system_clock::duration::max())); + } + + m_deadline.async_wait(std::bind(&UdpClientImpl::CheckDeadline, this)); +} + +void UdpClientImpl::HandleAnswer(const asio::error_code& error, std::size_t length, asio::error_code* out_error, std::size_t* out_length) const +{ + *out_error = error; + *out_length = length; +} + +void UdpClientImpl::ReceiveSocket() +{ + m_receiveSocket.async_receive_from(asio::buffer(m_socketReceiveData.data(), m_bufferLength), m_sourceEndpoint, std::bind(&UdpClientImpl::HandleReceiveSocket, this, std::placeholders::_1, std::placeholders::_2)); +} + +void UdpClientImpl::HandleReceiveSocket(const asio::error_code& error, size_t bytes) +{ + if (error) + return; + + if (m_callback) + { + std::vector::iterator begin = m_socketReceiveData.begin(); + m_callback(std::string(begin, begin + bytes)); + } + + ReceiveSocket(); +} + +} // namespace Network diff --git a/Network/UdpClientImpl.h b/Network/UdpClientImpl.h new file mode 100644 index 0000000..07a22a2 --- /dev/null +++ b/Network/UdpClientImpl.h @@ -0,0 +1,56 @@ +#ifndef NETWORK_UDPCLIENTIMPL_H +#define NETWORK_UDPCLIENTIMPL_H + +#include +#include +#include +#include +#include "asio.h" + + +namespace Network { + +class UdpClientImpl +{ +public: + UdpClientImpl(const std::string& listenAddress, const std::string& targetAddress, int targetPort, std::function callback); + ~UdpClientImpl(); + +public: + void Receive(); + + std::string Send(const std::string& data, int sourcePort = 0); + void Broadcast(const std::string& data, int sourcePort = 0); + +protected: + void EnableMulticast(); + +private: + void CheckDeadline(); + void HandleAnswer(const asio::error_code& error, size_t bytes, asio::error_code* out_error, std::size_t* out_length) const; + void ReceiveSocket(); + void HandleReceiveSocket(const asio::error_code& error, size_t bytes); + +private: + std::thread m_thread; + std::string m_url; + int m_bufferLength; + asio::io_service m_ioService; + asio::io_service m_ioServiceAnswer; + asio::system_timer m_deadline; + asio::ip::address m_listenAddress; + asio::ip::address m_targetAddress; + int m_targetPort; + asio::ip::udp::socket m_receiveSocket; + asio::ip::udp::socket m_answerSocket; + asio::ip::udp::endpoint m_targetEndpoint; + asio::ip::udp::endpoint m_sourceEndpoint; + std::vector m_socketReceiveData; + std::vector m_socketAnswerData; + + std::function m_callback; +}; + +} // namespace Network + +#endif // NETWORK_UDPCLIENTIMPL_H