]> git.sesse.net Git - cubemap/blob - udpinput.cpp
Stop leaking the /dev/null fd after spawning a subprocess.
[cubemap] / udpinput.cpp
1 #include <assert.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <poll.h>
5 #include <stddef.h>
6 #include <stdlib.h>
7 #include <sys/socket.h>
8 #include <time.h>
9 #include <unistd.h>
10 #include <math.h>
11 #include <string>
12
13 #include "acceptor.h"
14 #include "log.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         stats.url = url;
117         stats.connect_time = time(nullptr);
118 }
119
120 UDPInput::UDPInput(const InputProto &serialized)
121         : url(serialized.url()),
122           sock(serialized.sock())
123 {
124         // Set back the close-on-exec flag for the socket.
125         // (This can't leak into a child, since we haven't been started yet.)
126         fcntl(sock, F_SETFD, O_CLOEXEC);
127
128         // Should be verified by the caller.
129         string protocol;
130         bool ok = parse_url(url, &protocol, &user, &host, &port, &path);
131         assert(ok);
132
133         construct_header();
134
135         stats.url = url;
136         stats.bytes_received = serialized.bytes_received();
137         stats.data_bytes_received = serialized.data_bytes_received();
138         if (serialized.has_connect_time()) {
139                 stats.connect_time = serialized.connect_time();
140         } else {
141                 stats.connect_time = time(nullptr);
142         }
143 }
144
145 InputProto UDPInput::serialize() const
146 {
147         // Unset the close-on-exec flag for the socket.
148         // (This can't leak into a child, since there's only one thread left.)
149         fcntl(sock, F_SETFD, 0);
150
151         InputProto serialized;
152         serialized.set_url(url);
153         serialized.set_sock(sock);
154         serialized.set_bytes_received(stats.bytes_received);
155         serialized.set_data_bytes_received(stats.data_bytes_received);
156         serialized.set_connect_time(stats.connect_time);
157         serialized.set_is_metacube_encoded(false);
158         return serialized;
159 }
160
161 void UDPInput::close_socket()
162 {
163         safe_close(sock);
164         sock = -1;
165 }
166         
167 void UDPInput::construct_header()
168 {
169         http_header =
170                 "HTTP/1.0 200 OK\r\n"
171                 "Content-type: application/octet-stream\r\n"
172                 "Cache-control: no-cache\r\n"
173                 "Server: " SERVER_IDENTIFICATION "\r\n"
174                 "Connection: close\r\n";
175 }
176         
177 void UDPInput::add_destination(int stream_index)
178 {
179         stream_indices.push_back(stream_index);
180         servers->set_header(stream_index, http_header, "");
181 }
182
183 void UDPInput::do_work()
184 {
185         while (!should_stop()) {
186                 if (sock == -1) {
187                         int port_num = atoi(port.c_str());
188                         sockaddr_in6 addr = create_any_address(port_num);
189                         sock = create_server_socket(addr, UDP_SOCKET);
190                         if (sock == -1) {
191                                 log(WARNING, "[%s] UDP socket creation failed. Waiting 0.2 seconds and trying again...",
192                                              url.c_str());
193                                 usleep(200000);
194                                 continue;
195                         }
196
197                         // The syntax udp://source@group (abusing the username field
198                         // to store the sender in SSM) seems to be a VLC invention.
199                         // We mimic it.
200                         if (!maybe_join_multicast_group(sock, host, user)) {
201                                 log(WARNING, "[%s] Multicast join failed. Waiting 0.2 seconds and trying again...",
202                                              url.c_str());
203                                 safe_close(sock);
204                                 sock = -1;
205                                 usleep(200000);
206                                 continue;
207                         }
208                 }
209
210                 // Wait for a packet, or a wakeup.
211                 bool activity = wait_for_activity(sock, POLLIN, nullptr);
212                 if (!activity) {
213                         // Most likely, should_stop was set.
214                         continue;
215                 }
216
217                 int ret;
218                 do {
219                         ret = recv(sock, packet_buf, sizeof(packet_buf), 0);
220                 } while (ret == -1 && errno == EINTR);
221
222                 if (ret < 0) {  // Note that zero-byte packets are legal.
223                         log_perror("recv");
224                         close_socket();
225                         continue;
226                 }
227
228                 {
229                         lock_guard<mutex> lock(stats_mutex);
230                         stats.bytes_received += ret;
231                         stats.data_bytes_received += ret;
232                 }
233         
234                 for (size_t stream_index : stream_indices) {    
235                         servers->add_data(stream_index, packet_buf, ret, /*metacube_flags=*/0, /*pts=*/RationalPTS());
236                 }
237         }
238 }
239
240 InputStats UDPInput::get_stats() const
241 {
242         lock_guard<mutex> lock(stats_mutex);
243         return stats;
244 }