Skip to content

Commit 648f7f1

Browse files
committed
fix ip4 bitwise AND and ipv6 from_str parsing, add raw socket support, non-blocking connect with timeout, split tcp open() by role, update state_e enum names, improve examples and docs
1 parent d22070a commit 648f7f1

12 files changed

Lines changed: 629 additions & 251 deletions

CMakeLists.txt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ option(IP_SOCKETS_CPP_LITE_BUILD_EXAMPLES "Build with examples" OFF)
1515
# for initialise variables: CMAKE_INSTALL_INCLUDEDIR, CMAKE_INSTALL_LIBDIR, CMAKE_INSTALL_BINDIR
1616
include(GNUInstallDirs)
1717

18-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
19-
20-
if(WIN32)
21-
foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
22-
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
23-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/bin)
24-
endforeach()
18+
# when this project is the root - set output to own bin/, otherwise inherit from parent
19+
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
20+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
21+
if(WIN32)
22+
foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
23+
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
24+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_SOURCE_DIR}/bin)
25+
endforeach()
26+
endif()
2527
endif()
2628

2729
# =============================================================================
@@ -46,6 +48,12 @@ add_library (${PROJECT_NAME} INTERFACE)
4648
target_sources (${PROJECT_NAME} INTERFACE ${IP_SOCKETS_CPP_LITE_HEADERS})
4749
target_include_directories(${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE: ${CMAKE_CURRENT_SOURCE_DIR}/include>
4850
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> )
51+
52+
# link pthreads on Linux (sockets use threads for examples and may be used in threaded apps)
53+
if(NOT WIN32)
54+
find_package(Threads)
55+
target_link_libraries(${PROJECT_NAME} INTERFACE Threads::Threads)
56+
endif()
4957
# it was need to show header files of INTERFACE target in IDEs like Visual Studio 2015 with CMake < 3.0
5058
#add_custom_target (${PROJECT_NAME}-ide SOURCES ${IP_SOCKETS_CPP_LITE_HEADERS})
5159

examples/CMakeLists.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11

2-
if(NOT WIN32)
3-
find_package(Threads)
4-
target_link_libraries(${PROJECT_NAME} INTERFACE ${CMAKE_THREAD_LIBS_INIT})
5-
endif()
6-
72
function(add_example NAME_EXAMPLE REQUIRED_TARGETS)
83
string(CONCAT SUBPROJECT_NAME "ipsockets_" ${NAME_EXAMPLE})
94
message(STATUS " Adding ${PROJECT_NAME}::${NAME_EXAMPLE} example..")
105

116
add_executable (${SUBPROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/${NAME_EXAMPLE}.cpp)
127
target_link_libraries (${SUBPROJECT_NAME} ${REQUIRED_TARGETS})
13-
set_target_properties (${SUBPROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/examples)
8+
set_target_properties (${SUBPROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
149
endfunction()
1510

1611
add_example(ip_address ip-sockets-cpp-lite)
1712
add_example(ip_prefix ip-sockets-cpp-lite)
1813
add_example(udp_socket ip-sockets-cpp-lite)
1914
add_example(tcp_socket ip-sockets-cpp-lite)
2015
add_example(resolve_host ip-sockets-cpp-lite)
16+
add_example(raw_socket ip-sockets-cpp-lite)
2117
add_example(http_server ip-sockets-cpp-lite)
2218
add_example(tcp_stream ip-sockets-cpp-lite)

examples/http_server.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ struct mini_http_server_t {
8686
address_t client_addr;
8787

8888
// Main accept loop
89-
while (!must_die && server_socket.state == state_e::state_opened) {
89+
while (!must_die && server_socket.state == state_e::opened) {
9090

9191
tcp_client_t client = server_socket.accept(client_addr);
92-
if (client.state != state_e::state_opened) continue;
92+
if (client.state != state_e::opened) continue;
9393

9494
// Handle client in separate thread
9595
tasks.push_back(std::async(std::launch::async,&mini_http_server_t::handle_client,this,std::move(client),client_addr));

examples/ip_address.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ int main () {
3737

3838
std::cout << "\n--- Construction (braces) ---\n";
3939

40-
ip4_t ipv4_00;
4140
ip4_t ipv4_01 {};
4241
ip4_t ipv4_02 { std::array<uint8_t, 4> ({ 0xc0, 0xa8, 0x01, 0x02 }) };
4342
ip4_t ipv4_03 { 0xc0a80102 };
@@ -71,12 +70,12 @@ int main () {
7170
std::cout << "\n--- Construction (assignment) ---\n";
7271

7372
ip4_t ipv4_11 = ip4_t { "127.1" }; (void)ipv4_11;
74-
ip4_t ipv4_12 = std::array<uint8_t, 4> ({ 0xc0, 0xa8, 0x01, 0x02 }); (void)ipv4_12;
73+
ip4_t ipv4_12 = std::array<uint8_t, 4> ({ 0xc0, 0xa8, 0x01, 0x02 }); (void)ipv4_12;
7574
ip4_t ipv4_13 = 0xc0a80102; (void)ipv4_13;
76-
ip4_t ipv4_14 = { 0xc0, 0xa8, 0x01, 0x02 }; (void)ipv4_14;
75+
ip4_t ipv4_14 = { 0xc0, 0xa8, 0x01, 0x02 }; (void)ipv4_14;
7776
ip4_t ipv4_15 = "0xc0a80102"; (void)ipv4_15;
7877
ip4_t ipv4_16 = "3232235778"; (void)ipv4_16;
79-
ip4_t ipv4_17 = "0xc0.0xa8.0x01.0x02"; (void)ipv4_17;
78+
ip4_t ipv4_17 = "0xc0.0xa8.0x01.0x02"; (void)ipv4_17;
8079
ip4_t ipv4_18 = "192.168.1.2"; (void)ipv4_18;
8180
ip4_t ipv4_19 = "192.168.2"; (void)ipv4_19;
8281
ip4_t ipv4_1a = "127.1"; (void)ipv4_1a;
@@ -121,13 +120,13 @@ int main () {
121120
bool val_bt = ipv4_30;
122121
bool val_bf = ip4_t("0.0.0.0");
123122

124-
CHECK (val_u32 == 0xc0a80102, "convert to uint32");
125-
CHECK (val_str == "192.168.1.2", "convert to string");
126-
CHECK (val_ipt == ipv4_30, "convert to ip_t<v4>");
127-
CHECK (val_arr[0] == 0xc0, "convert to array [0]");
128-
CHECK (val_arr[3] == 0x02, "convert to array [3]");
129-
CHECK (val_bt == true, "bool(192.168.1.2) = true");
130-
CHECK (val_bf == false, "bool(0.0.0.0) = false");
123+
CHECK (val_u32 == 0xc0a80102, "convert to uint32");
124+
CHECK (val_str == "192.168.1.2", "convert to string");
125+
CHECK (val_ipt == ipv4_30, "convert to ip_t<v4>");
126+
CHECK (val_arr[0] == 0xc0, "convert to array [0]");
127+
CHECK (val_arr[3] == 0x02, "convert to array [3]");
128+
CHECK (val_bt == true, "bool(192.168.1.2) = true");
129+
CHECK (val_bf == false, "bool(0.0.0.0) = false");
131130

132131
// --- Bitwise operations ---
133132

examples/raw_socket.cpp

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
2+
// ip-sockets-cpp-lite - RAW socket example (send only, requires administrator/root privileges)
3+
//
4+
// This example demonstrates how to send a hand-crafted UDP packet using a RAW socket.
5+
// The sender builds an IP header + UDP header + payload manually and sends it via sendto().
6+
// The receiver is a normal UDP server socket that receives the packet as regular UDP.
7+
//
8+
// Usage: run as administrator (Windows) or root (Linux).
9+
//
10+
// [raw client] --( IP + UDP + payload )--> [regular udp server]
11+
//
12+
13+
#include "udp_socket.h"
14+
15+
#include <thread>
16+
#include <chrono>
17+
#include <cstdio>
18+
#include <cstring>
19+
20+
using namespace ipsockets;
21+
22+
static const addr4_t server_addr = "127.0.0.1:9000";
23+
static const addr4_t sender_addr = "127.0.0.1:8000"; // spoofed source address (can be anything for raw)
24+
25+
// ============================================================
26+
// IP + UDP header structures (packed, network byte order)
27+
// ============================================================
28+
29+
#pragma pack(push, 1)
30+
struct ip_header_t {
31+
uint8_t ver_ihl; // version (4) + IHL (5) = 0x45
32+
uint8_t tos; // type of service
33+
uint16_t total_len; // total length (IP header + UDP header + payload)
34+
uint16_t id; // identification
35+
uint16_t flags_frag; // flags + fragment offset
36+
uint8_t ttl; // time to live
37+
uint8_t protocol; // protocol (17 = UDP)
38+
uint16_t checksum; // header checksum (kernel fills this when IP_HDRINCL is set)
39+
ip4_t src_ip; // source IP address
40+
ip4_t dst_ip; // destination IP address
41+
};
42+
43+
struct udp_header_t {
44+
uint16_t src_port; // source port
45+
uint16_t dst_port; // destination port
46+
uint16_t length; // UDP header + payload length
47+
uint16_t checksum; // UDP checksum (0 = disabled)
48+
};
49+
#pragma pack(pop)
50+
51+
// ============================================================
52+
// build_raw_packet - constructs IP + UDP + payload in buffer
53+
// ============================================================
54+
55+
int build_raw_packet (char* buf, int buf_size, const addr4_t& src, const addr4_t& dst, const char* payload, int payload_len) {
56+
57+
int ip_hdr_len = sizeof (ip_header_t);
58+
int udp_hdr_len = sizeof (udp_header_t);
59+
int total_len = ip_hdr_len + udp_hdr_len + payload_len;
60+
61+
if (total_len > buf_size)
62+
return -1;
63+
64+
memset (buf, 0, total_len);
65+
66+
// fill IP header
67+
ip_header_t* ip = (ip_header_t*)buf;
68+
ip->ver_ihl = 0x45; // IPv4, IHL = 5 (20 bytes, no options)
69+
ip->tos = 0;
70+
ip->total_len = orders::htonT<uint16_t> ((uint16_t)total_len);
71+
ip->id = orders::htonT<uint16_t> (0x1234);
72+
ip->ttl = 64;
73+
ip->protocol = IPPROTO_UDP;
74+
ip->checksum = 0; // kernel calculates this when IP_HDRINCL is set
75+
ip->src_ip = src.ip;
76+
ip->dst_ip = dst.ip;
77+
78+
// fill UDP header
79+
udp_header_t* udp = (udp_header_t*)(buf + ip_hdr_len);
80+
udp->src_port = orders::htonT (src.port);
81+
udp->dst_port = orders::htonT (dst.port);
82+
udp->length = orders::htonT<uint16_t> ((uint16_t)(udp_hdr_len + payload_len));
83+
udp->checksum = 0; // 0 = checksum disabled (valid for UDP)
84+
85+
// copy payload
86+
memcpy (buf + ip_hdr_len + udp_hdr_len, payload, payload_len);
87+
88+
return total_len;
89+
}
90+
91+
// ============================================================
92+
// receiver - regular UDP server (receives normal UDP payload)
93+
// ============================================================
94+
95+
bool shutdown_receiver = false;
96+
97+
void receiver_func () {
98+
99+
udp_socket_t<v4, socket_type_e::server> sock (log_e::debug);
100+
if (sock.open (server_addr) != no_error) {
101+
printf ("receiver: failed to open server socket\n");
102+
return;
103+
}
104+
105+
char buf[1500];
106+
addr4_t from;
107+
while (!shutdown_receiver) {
108+
int res = sock.recvfrom (buf, sizeof (buf), from);
109+
if (res > 0) {
110+
buf[res] = '\0';
111+
printf ("receiver: got %d bytes from %s: \"%s\"\n", res, from.to_str().c_str(), buf);
112+
}
113+
else if (res == error_timeout) {
114+
// normal timeout, keep waiting
115+
}
116+
else
117+
printf ("receiver: error %d\n", res);
118+
}
119+
printf ("receiver: shutdown\n");
120+
}
121+
122+
// ============================================================
123+
// sender - RAW socket client (sends hand-crafted IP+UDP packet)
124+
// ============================================================
125+
126+
void sender_func () {
127+
udp_socket_t<v4, socket_type_e::client> sock (log_e::debug, udp_type_e::raw);
128+
if (sock.open (server_addr) != no_error) {
129+
printf ("sender: failed to open raw socket (need admin/root privileges)\n");
130+
return;
131+
}
132+
133+
std::this_thread::sleep_for (std::chrono::milliseconds (500)); // wait for receiver to start
134+
135+
const char* messages[] = { "Hello from RAW socket!", "Second RAW packet", "Goodbye!" };
136+
137+
for (int i = 0; i < 3; i++) {
138+
char packet[1500];
139+
int payload_len = (int)strlen (messages[i]) + 1; // include null terminator
140+
int packet_len = build_raw_packet (packet, sizeof (packet), sender_addr, server_addr, messages[i], payload_len);
141+
addr4_t dst = server_addr;
142+
143+
if (packet_len > 0) {
144+
int res = sock.sendto (packet, packet_len, dst);
145+
if (res > 0) printf ("sender: sent %d bytes (payload: \"%s\")\n", res, messages[i]);
146+
else printf ("sender: sendto failed with error %d\n", res);
147+
}
148+
149+
std::this_thread::sleep_for (std::chrono::seconds (1));
150+
}
151+
152+
printf ("sender: done\n");
153+
}
154+
155+
// ============================================================
156+
// main
157+
// ============================================================
158+
159+
int main () {
160+
161+
printf ("=== RAW socket example ===\n");
162+
printf ("NOTE: requires administrator (Windows) or root (Linux) privileges.\n\n");
163+
164+
std::thread receiver (receiver_func);
165+
std::thread sender (sender_func);
166+
167+
sender.join ();
168+
169+
// give receiver time to process last packet
170+
std::this_thread::sleep_for (std::chrono::seconds (1));
171+
shutdown_receiver = true;
172+
receiver.join ();
173+
174+
printf ("\ndemo app shutdown\n");
175+
176+
return 0;
177+
}

examples/tcp_socket.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ using namespace ipsockets;
1010

1111
#if true
1212
// server and client work on IPv4 mode
13-
static const ip_type_e cfg_ip_type = v4;
14-
static const addr4_t cfg_server = "127.0.0.1:2000";
15-
static const addr4_t cfg_client = "127.0.0.1:2000";
13+
static const ip_type_e ip_type = v4;
14+
static const addr4_t ip_server = "127.0.0.1:2000";
15+
static const addr4_t ip_client = "127.0.0.1:2000";
1616
#else
1717
// server and client work on IPv6 mode
18-
static const ip_type_e cfg_ip_type = v6;
19-
static const addr6_t cfg_server = "[::1]:2000";
20-
static const addr6_t cfg_client = "[::1]:2000";
18+
static const ip_type_e ip_type = v6;
19+
static const addr6_t ip_server = "[::1]:2000";
20+
static const addr6_t ip_client = "[::1]:2000";
2121
#endif
2222

23-
using tcp_server_t = tcp_socket_t<cfg_ip_type, socket_type_e::server>;
24-
using tcp_client_t = tcp_socket_t<cfg_ip_type, socket_type_e::client>;
23+
using tcp_server_t = tcp_socket_t<ip_type, socket_type_e::server>;
24+
using tcp_client_t = tcp_socket_t<ip_type, socket_type_e::client>;
2525

2626
// Handles a single client connection.
2727
// All accepted connections will be automatically closed when will be closed main server connection
@@ -55,14 +55,16 @@ void accepted_client_func (tcp_client_t accepted_client) {
5555
bool shutdown_server = false;
5656
tcp_server_t server_sock (log_e::debug);
5757
void server_func () {
58-
if (server_sock.open (cfg_server) == ipsockets::no_error) {
59-
addr_t<cfg_ip_type> accepted_client_addr;
58+
uint32_t timeout_ms = 1000; // recv/send timeout in ms (SO_RCVTIMEO), also affects accept() timeout on Linux
59+
int max_incoming_queue = 1000; // maximum number of pending connections in the listen queue
60+
if (server_sock.open (ip_server, timeout_ms, max_incoming_queue) == ipsockets::no_error) {
61+
addr_t<ip_type> accepted_client_addr;
6062

6163
while (shutdown_server == false) {
6264

6365
// accept() waiting new connections until server socket will be closed
6466
tcp_client_t accepted_client = server_sock.accept (accepted_client_addr);
65-
if (accepted_client.state == state_e::state_opened) {
67+
if (accepted_client.state == state_e::opened) {
6668
// fire-and-forget thread for this connection
6769
printf ("server: accept new connection\n");
6870
std::thread accepted_thread = std::thread (accepted_client_func, std::move (accepted_client));
@@ -79,8 +81,13 @@ void server_func () {
7981

8082
// Simple client sending periodic requests.
8183
void client_func () {
84+
// small delay to let server start listening
85+
std::this_thread::sleep_for (std::chrono::milliseconds (200));
86+
8287
tcp_client_t sock (log_e::debug);
83-
if (sock.open (cfg_client) == ipsockets::no_error) {
88+
uint32_t timeout_ms = 1000; // recv/send timeout in ms (SO_RCVTIMEO)
89+
uint32_t connect_timeout_ms = 5000; // connect timeout in ms (max wait for TCP handshake to complete)
90+
if (sock.open (ip_client, timeout_ms, connect_timeout_ms) == ipsockets::no_error) {
8491

8592
for (size_t i = 0; i < 5; i++) {
8693
std::this_thread::sleep_for (std::chrono::seconds (4));

0 commit comments

Comments
 (0)