]> git.sesse.net Git - cubemap/blob - udpinput.cpp
93eaaa76801c0dbab7383e2e7b8902b5fd408ea0
[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, nullptr);
117         stats.url = url;
118         stats.connect_time = time(nullptr);
119 }
120
121 UDPInput::UDPInput(const InputProto &serialized)
122         : url(serialized.url()),
123           sock(serialized.sock())
124 {
125         // Should be verified by the caller.
126         string protocol;
127         bool ok = parse_url(url, &protocol, &user, &host, &port, &path);
128         assert(ok);
129
130         construct_header();
131
132         pthread_mutex_init(&stats_mutex, nullptr);
133         stats.url = url;
134         stats.bytes_received = serialized.bytes_received();
135         stats.data_bytes_received = serialized.data_bytes_received();
136         if (serialized.has_connect_time()) {
137                 stats.connect_time = serialized.connect_time();
138         } else {
139                 stats.connect_time = time(nullptr);
140         }
141 }
142
143 InputProto UDPInput::serialize() const
144 {
145         InputProto serialized;
146         serialized.set_url(url);
147         serialized.set_sock(sock);
148         serialized.set_bytes_received(stats.bytes_received);
149         serialized.set_data_bytes_received(stats.data_bytes_received);
150         serialized.set_connect_time(stats.connect_time);
151         serialized.set_is_metacube_encoded(false);
152         return serialized;
153 }
154
155 void UDPInput::close_socket()
156 {
157         safe_close(sock);
158         sock = -1;
159 }
160         
161 void UDPInput::construct_header()
162 {
163         http_header =
164                 "HTTP/1.0 200 OK\r\n"
165                 "Content-type: application/octet-stream\r\n"
166                 "Cache-control: no-cache\r\n"
167                 "Server: " SERVER_IDENTIFICATION "\r\n"
168                 "Connection: close\r\n";
169 }
170         
171 void UDPInput::add_destination(int stream_index)
172 {
173         stream_indices.push_back(stream_index);
174         servers->set_header(stream_index, http_header, "");
175 }
176
177 void UDPInput::do_work()
178 {
179         while (!should_stop()) {
180                 if (sock == -1) {
181                         int port_num = atoi(port.c_str());
182                         sockaddr_in6 addr = create_any_address(port_num);
183                         sock = create_server_socket(addr, UDP_SOCKET);
184                         if (sock == -1) {
185                                 log(WARNING, "[%s] UDP socket creation failed. Waiting 0.2 seconds and trying again...",
186                                              url.c_str());
187                                 usleep(200000);
188                                 continue;
189                         }
190
191                         // The syntax udp://source@group (abusing the username field
192                         // to store the sender in SSM) seems to be a VLC invention.
193                         // We mimic it.
194                         if (!maybe_join_multicast_group(sock, host, user)) {
195                                 log(WARNING, "[%s] Multicast join failed. Waiting 0.2 seconds and trying again...",
196                                              url.c_str());
197                                 safe_close(sock);
198                                 sock = -1;
199                                 usleep(200000);
200                                 continue;
201                         }
202                 }
203
204                 // Wait for a packet, or a wakeup.
205                 bool activity = wait_for_activity(sock, POLLIN, nullptr);
206                 if (!activity) {
207                         // Most likely, should_stop was set.
208                         continue;
209                 }
210
211                 int ret;
212                 do {
213                         ret = recv(sock, packet_buf, sizeof(packet_buf), 0);
214                 } while (ret == -1 && errno == EINTR);
215
216                 if (ret <= 0) {
217                         log_perror("recv");
218                         close_socket();
219                         continue;
220                 }
221
222                 {
223                         MutexLock lock(&stats_mutex);
224                         stats.bytes_received += ret;
225                         stats.data_bytes_received += ret;
226                 }
227         
228                 for (size_t stream_index : stream_indices) {    
229                         servers->add_data(stream_index, packet_buf, ret, /*metacube_flags=*/0);
230                 }
231         }
232 }
233
234 InputStats UDPInput::get_stats() const
235 {
236         MutexLock lock(&stats_mutex);
237         return stats;
238 }