forked from operasfantom/os-net-multiplexing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.cpp
188 lines (160 loc) Β· 5.51 KB
/
client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <time.h>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <system_error>
#include "client.h"
#include "protocol.h"
NTPClient::NTPClient(const std::string &hostname, uint16_t port)
{
struct addrinfo hints = {};
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // Stream socket
hints.ai_flags = 0;
hints.ai_protocol = IPPROTO_TCP;
struct addrinfo* server_addr; // NTP server address
int s = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &server_addr);
if (s != 0) {
throw std::runtime_error(std::string("getaddrinfo failed: ") + gai_strerror(s));
}
m_socketfd = open_socket(server_addr);
m_mult.add_polled(STDIN_FILENO, Multiplexer::POLLIN);
}
NTPClient::~NTPClient()
{
if (close(m_socketfd) == -1) {
std::cerr << "Failed to close the socket: " << strerror(errno) << std::endl;
}
}
void NTPClient::run() const
{
print_help();
bool disconnect_requested = false;
for (;;) {
auto ready = m_mult.get_ready();
for (auto event : ready) {
if (event.fd == m_socketfd) {
m_mult.delete_polled(m_socketfd);
if (event.type == Multiplexer::POLLIN) {
time_t time = receive_response();
if (time) {
std::cout << "Time: " << ctime(&time);
}
} else if (event.type == Multiplexer::POLLOUT) {
send_request(disconnect_requested);
if (!disconnect_requested) {
m_mult.add_polled(m_socketfd, Multiplexer::POLLIN);
} else {
return;
}
}
} else if (event.fd == STDIN_FILENO) {
std::string cmd;
std::getline(std::cin, cmd);
if (cmd == "ask") {
m_mult.add_polled(m_socketfd, Multiplexer::POLLOUT);
} else if (cmd == "quit") {
m_mult.add_polled(m_socketfd, Multiplexer::POLLOUT);
disconnect_requested = true;
} else {
print_help(true);
}
}
}
}
}
void NTPClient::print_help(bool error)
{
(error ? std::cerr : std::cout) << "Available commands: ask, quit" << std::endl;
}
int NTPClient::open_socket(addrinfo *addr) const
{
// getaddrinfo() returns a list of address structures
// Trying each until we successfully connect
int sockfd = -1;
struct addrinfo* rp;
for (rp = addr; rp != nullptr; rp = rp->ai_next) {
sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sockfd != -1) {
if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
break; // Success
}
close(sockfd);
sockfd = -1;
}
}
freeaddrinfo(addr);
// All addresses failed
if (rp == nullptr) {
std::error_code ec(errno, std::system_category());
throw std::system_error(ec, "Failed to open and connect a socket");
}
return sockfd;
}
void NTPClient::send_request(bool empty) const
{
ntp_packet packet = {};
packet.li_vn_mode = 0b11'011'011; // li = 3 (unknown), vn = 3 (version), mode = 3 (client)
// Send a request
if (write(m_socketfd, &packet, empty ? 0 : sizeof(ntp_packet)) == -1) {
close(m_socketfd);
std::error_code ec(errno, std::system_category());
throw std::system_error(ec, "Write failed");
}
}
time_t NTPClient::receive_response() const
{
ntp_packet packet = {};
ssize_t ntransferred = read(m_socketfd, &packet, sizeof(ntp_packet));
if (ntransferred == -1) {
close(m_socketfd);
std::error_code ec(errno, std::system_category());
throw std::system_error(ec, "Read failed");
} else if (ntransferred != sizeof (packet)) {
std::cerr << "Partial data read" << std::endl;
return 0;
}
// Using time-stamp seconds: time when the packet left the NTP server,
// in seconds passed since 1900
// ntohl() converts the bit/byte order from the network's to host's "endianness"
packet.txTm_s = ntohl(packet.txTm_s); // seconds
packet.txTm_f = ntohl(packet.txTm_f); // fractions of a second
// Subtract 70 years from the NTP epoch time to get UNIX epoch
// (seconds passed since 1970)
return static_cast<time_t>(packet.txTm_s - NTP_TIMESTAMP_DELTA);
}
int main(int argc, char* argv[])
{
std::string server_hostname = NTP_DEFAULT_SERVER;
uint16_t server_port = NTP_DEFAULT_PORT;
if (argc > 3) {
std::cerr << "Usage: ntp-client [<server hostname or address> [<server port>]]" << std::endl;
return EXIT_FAILURE;
}
if (argc >= 2) {
server_hostname = argv[1];
}
if (argc == 3) {
if (!(std::istringstream(argv[2]) >> server_port)) {
std::cerr << "Invalid port number provided" << std::endl;
return EXIT_FAILURE;
}
}
try {
NTPClient client(server_hostname, server_port);
client.run();
} catch (std::runtime_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}