]> git.sesse.net Git - cubemap/blob - udpinput.cpp
Open up for C++11.
[cubemap] / udpinput.cpp
1 #include <assert.h>
2 #include <errno.h>
3 #include <poll.h>
4 #include <stddef.h>
5 #include <stdlib.h>
6 #include <sys/socket.h>
7 #include <time.h>
8 #include <unistd.h>
9 #include <math.h>
10 #include <string>
11
12 #include "acceptor.h"
13 #include "log.h"
14 #include "mutexlock.h"
15 #include "serverpool.h"
16 #include "state.pb.h"
17 #include "stream.h"
18 #include "udpinput.h"
19 #include "util.h"
20 #include "version.h"
21
22 using namespace std;
23
24 extern ServerPool *servers;
25
26 namespace {
27
28 // Similar to parse_hostport(), but only parses the IP address,
29 // and does not use mapped-v4 addresses, since multicast seems
30 // to not like that too much.
31 bool parse_ip_address(const string &ip, sockaddr_storage *addr)
32 {
33         memset(addr, 0, sizeof(*addr));
34
35         assert(!ip.empty());
36         if (ip[0] == '[') {
37                 sockaddr_in6 *addr6 = (sockaddr_in6 *)addr;
38                 addr6->sin6_family = AF_INET6;
39                 if (ip[ip.size() - 1] != ']') {
40                         log(ERROR, "address '%s' is malformed; must be either [ipv6addr] or ipv4addr",
41                                 ip.c_str());
42                         return false;
43                 }
44                 string raw_ip(ip.begin() + 1, ip.end() - 1);
45                 if (inet_pton(AF_INET6, raw_ip.c_str(), &addr6->sin6_addr) != 1) {
46                         log(ERROR, "'%s' is not a valid IPv6 address", raw_ip.c_str());
47                         return false;
48                 }
49         } else {
50                 sockaddr_in *addr4 = (sockaddr_in *)addr;
51                 addr4->sin_family = AF_INET;
52                 if (inet_pton(AF_INET, ip.c_str(), &addr4->sin_addr) != 1) {
53                         log(ERROR, "'%s' is not a valid IPv4 address");
54                         return false;
55                 }
56         }
57
58         return true;
59 }
60
61 bool maybe_join_multicast_group(int sock, const string &group, const string &source)
62 {
63         if (group.empty()) {
64                 // Not multicast.
65                 return true;
66         }
67
68         // Join the given multicast group (ASM or SSM).
69         // TODO: Also support sources apart from multicast groups,
70         // e.g. udp://[::1]:1234 for only receiving from localhost.
71         if (!source.empty()) {
72                 // Single-Source Multicast (SSM).
73                 group_source_req gsr;
74                 memset(&gsr, 0, sizeof(gsr));
75                 if (!parse_ip_address(group, &gsr.gsr_group)) {
76                         return false;
77                 }
78                 if (!parse_ip_address(source, &gsr.gsr_source)) {
79                         return false;
80                 }
81                 int level = (gsr.gsr_group.ss_family == AF_INET) ? SOL_IP : SOL_IPV6;
82                 if (setsockopt(sock, level, MCAST_JOIN_SOURCE_GROUP, &gsr, sizeof(gsr)) == -1) {
83                         log_perror("setsockopt(MCAST_JOIN_SOURCE_GROUP)");
84                         return false;
85                 }
86         } else {
87                 // Any-Source Multicast (ASM).
88                 group_req gr;
89                 memset(&gr, 0, sizeof(gr));
90                 if (!parse_ip_address(group, &gr.gr_group)) {
91                         return false;
92                 }
93                 int level = (gr.gr_group.ss_family == AF_INET) ? SOL_IP : SOL_IPV6;
94                 if (setsockopt(sock, level, MCAST_JOIN_GROUP, &gr, sizeof(gr)) == -1) {
95                         log_perror("setsockopt(MCAST_JOIN_GROUP)");
96                         return false;
97                 }
98         }
99
100         return true;
101 }
102
103 }  // namespace
104
105 UDPInput::UDPInput(const string &url)
106         : url(url),
107           sock(-1)
108 {
109         // Should be verified by the caller.
110         string protocol;
111         bool ok = parse_url(url, &protocol, &user, &host, &port, &path);
112         assert(ok);
113
114         construct_header();
115
116         pthread_mutex_init(&stats_mutex, NULL);
117         stats.url = url;
118         stats.bytes_received = 0;
119         stats.data_bytes_received = 0;
120         stats.metadata_bytes_received = 0;
121         stats.connect_time = time(NULL);
122         stats.latency_sec = HUGE_VAL;
123 }
124
125 UDPInput::UDPInput(const InputProto &serialized)
126         : url(serialized.url()),
127           sock(serialized.sock())
128 {
129         // Should be verified by the caller.
130         string protocol;
131         bool ok = parse_url(url, &protocol, &user, &host, &port, &path);
132         assert(ok);
133
134         construct_header();
135
136         pthread_mutex_init(&stats_mutex, NULL);
137         stats.url = url;
138         stats.bytes_received = serialized.bytes_received();
139         stats.data_bytes_received = serialized.data_bytes_received();
140         if (serialized.has_connect_time()) {
141                 stats.connect_time = serialized.connect_time();
142         } else {
143                 stats.connect_time = time(NULL);
144         }
145 }
146
147 InputProto UDPInput::serialize() const
148 {
149         InputProto serialized;
150         serialized.set_url(url);
151         serialized.set_sock(sock);
152         serialized.set_bytes_received(stats.bytes_received);
153         serialized.set_data_bytes_received(stats.data_bytes_received);
154         serialized.set_connect_time(stats.connect_time);
155         serialized.set_is_metacube_encoded(false);
156         return serialized;
157 }
158
159 void UDPInput::close_socket()
160 {
161         safe_close(sock);
162         sock = -1;
163 }
164         
165 void UDPInput::construct_header()
166 {
167         http_header =
168                 "HTTP/1.0 200 OK\r\n"
169                 "Content-type: application/octet-stream\r\n"
170                 "Cache-control: no-cache\r\n"
171                 "Server: " SERVER_IDENTIFICATION "\r\n"
172                 "Connection: close\r\n";
173 }
174         
175 void UDPInput::add_destination(int stream_index)
176 {
177         stream_indices.push_back(stream_index);
178         servers->set_header(stream_index, http_header, "");
179 }
180
181 void UDPInput::do_work()
182 {
183         while (!should_stop()) {
184                 if (sock == -1) {
185                         int port_num = atoi(port.c_str());
186                         sockaddr_in6 addr = create_any_address(port_num);
187                         sock = create_server_socket(addr, UDP_SOCKET);
188                         if (sock == -1) {
189                                 log(WARNING, "[%s] UDP socket creation failed. Waiting 0.2 seconds and trying again...",
190                                              url.c_str());
191                                 usleep(200000);
192                                 continue;
193                         }
194
195                         // The syntax udp://source@group (abusing the username field
196                         // to store the sender in SSM) seems to be a VLC invention.
197                         // We mimic it.
198                         if (!maybe_join_multicast_group(sock, host, user)) {
199                                 log(WARNING, "[%s] Multicast join failed. Waiting 0.2 seconds and trying again...",
200                                              url.c_str());
201                                 safe_close(sock);
202                                 sock = -1;
203                                 usleep(200000);
204                                 continue;
205                         }
206                 }
207
208                 // Wait for a packet, or a wakeup.
209                 bool activity = wait_for_activity(sock, POLLIN, NULL);
210                 if (!activity) {
211                         // Most likely, should_stop was set.
212                         continue;
213                 }
214
215                 int ret;
216                 do {
217                         ret = recv(sock, packet_buf, sizeof(packet_buf), 0);
218                 } while (ret == -1 && errno == EINTR);
219
220                 if (ret <= 0) {
221                         log_perror("recv");
222                         close_socket();
223                         continue;
224                 }
225
226                 {
227                         MutexLock lock(&stats_mutex);
228                         stats.bytes_received += ret;
229                         stats.data_bytes_received += ret;
230                 }
231                 
232                 for (size_t i = 0; i < stream_indices.size(); ++i) {
233                         servers->add_data(stream_indices[i], packet_buf, ret, /*metacube_flags=*/0);
234                 }
235         }
236 }
237
238 InputStats UDPInput::get_stats() const
239 {
240         MutexLock lock(&stats_mutex);
241         return stats;
242 }