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