Shaka Packager SDK
udp_file.cc
1 // Copyright 2014 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/file/udp_file.h"
8 
9 #if defined(OS_WIN)
10 
11 #include <windows.h>
12 #include <ws2tcpip.h>
13 #define close closesocket
14 #define EINTR_CODE WSAEINTR
15 
16 #else
17 
18 #include <arpa/inet.h>
19 #include <errno.h>
20 #include <strings.h>
21 #include <sys/socket.h>
22 #include <sys/time.h>
23 #include <unistd.h>
24 #define INVALID_SOCKET -1
25 #define EINTR_CODE EINTR
26 
27 // IP_MULTICAST_ALL has been supported since kernel version 2.6.31 but we may be
28 // building on a machine that is older than that.
29 #ifndef IP_MULTICAST_ALL
30 #define IP_MULTICAST_ALL 49
31 #endif
32 
33 #endif // defined(OS_WIN)
34 
35 #include <limits>
36 
37 #include "packager/base/logging.h"
38 #include "packager/file/udp_options.h"
39 
40 namespace shaka {
41 
42 namespace {
43 
44 bool IsIpv4MulticastAddress(const struct in_addr& addr) {
45  return (ntohl(addr.s_addr) & 0xf0000000) == 0xe0000000;
46 }
47 
48 int GetSocketErrorCode() {
49 #if defined(OS_WIN)
50  return WSAGetLastError();
51 #else
52  return errno;
53 #endif
54 }
55 
56 } // anonymous namespace
57 
58 UdpFile::UdpFile(const char* file_name)
59  : File(file_name), socket_(INVALID_SOCKET) {}
60 
61 UdpFile::~UdpFile() {}
62 
64  if (socket_ != INVALID_SOCKET) {
65  close(socket_);
66  socket_ = INVALID_SOCKET;
67  }
68  delete this;
69 #if defined(OS_WIN)
70  if (wsa_started_)
71  WSACleanup();
72 #endif
73  return true;
74 }
75 
76 int64_t UdpFile::Read(void* buffer, uint64_t length) {
77  DCHECK(buffer);
78  DCHECK_GE(length, 65535u)
79  << "Buffer may be too small to read entire datagram.";
80 
81  if (socket_ == INVALID_SOCKET)
82  return -1;
83 
84  int64_t result;
85  do {
86  result =
87  recvfrom(socket_, reinterpret_cast<char*>(buffer), length, 0, NULL, 0);
88  } while (result == -1 && GetSocketErrorCode() == EINTR_CODE);
89 
90  return result;
91 }
92 
93 int64_t UdpFile::Write(const void* buffer, uint64_t length) {
94  NOTIMPLEMENTED();
95  return -1;
96 }
97 
98 int64_t UdpFile::Size() {
99  if (socket_ == INVALID_SOCKET)
100  return -1;
101 
102  return std::numeric_limits<int64_t>::max();
103 }
104 
106  NOTIMPLEMENTED();
107  return false;
108 }
109 
110 bool UdpFile::Seek(uint64_t position) {
111  NOTIMPLEMENTED();
112  return false;
113 }
114 
115 bool UdpFile::Tell(uint64_t* position) {
116  NOTIMPLEMENTED();
117  return false;
118 }
119 
120 class ScopedSocket {
121  public:
122  explicit ScopedSocket(SOCKET sock_fd) : sock_fd_(sock_fd) {}
123 
124  ~ScopedSocket() {
125  if (sock_fd_ != INVALID_SOCKET)
126  close(sock_fd_);
127  }
128 
129  SOCKET get() { return sock_fd_; }
130 
131  SOCKET release() {
132  SOCKET socket = sock_fd_;
133  sock_fd_ = INVALID_SOCKET;
134  return socket;
135  }
136 
137  private:
138  SOCKET sock_fd_;
139 
140  DISALLOW_COPY_AND_ASSIGN(ScopedSocket);
141 };
142 
144 #if defined(OS_WIN)
145  WSADATA wsa_data;
146  int wsa_error = WSAStartup(MAKEWORD(2, 2), &wsa_data);
147  if (wsa_error != 0) {
148  LOG(ERROR) << "Winsock start up failed with error " << wsa_error;
149  return false;
150  }
151  wsa_started_ = true;
152 #endif // defined(OS_WIN)
153 
154  DCHECK_EQ(INVALID_SOCKET, socket_);
155 
156  std::unique_ptr<UdpOptions> options =
158  if (!options)
159  return false;
160 
161  ScopedSocket new_socket(socket(AF_INET, SOCK_DGRAM, 0));
162  if (new_socket.get() == INVALID_SOCKET) {
163  LOG(ERROR) << "Could not allocate socket, error = " << GetSocketErrorCode();
164  return false;
165  }
166 
167  struct in_addr local_in_addr = {0};
168  if (inet_pton(AF_INET, options->address().c_str(), &local_in_addr) != 1) {
169  LOG(ERROR) << "Malformed IPv4 address " << options->address();
170  return false;
171  }
172 
173  struct sockaddr_in local_sock_addr = {0};
174  // TODO(kqyang): Support IPv6.
175  local_sock_addr.sin_family = AF_INET;
176  local_sock_addr.sin_port = htons(options->port());
177  const bool is_multicast = IsIpv4MulticastAddress(local_in_addr);
178  if (is_multicast) {
179  local_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
180  } else {
181  local_sock_addr.sin_addr = local_in_addr;
182  }
183 
184  if (options->reuse()) {
185  const int optval = 1;
186  if (setsockopt(new_socket.get(), SOL_SOCKET, SO_REUSEADDR,
187  reinterpret_cast<const char*>(&optval),
188  sizeof(optval)) < 0) {
189  LOG(ERROR) << "Could not apply the SO_REUSEADDR property to the UDP "
190  "socket, error = "
191  << GetSocketErrorCode();
192  return false;
193  }
194  }
195 
196  if (bind(new_socket.get(),
197  reinterpret_cast<struct sockaddr*>(&local_sock_addr),
198  sizeof(local_sock_addr)) < 0) {
199  LOG(ERROR) << "Could not bind UDP socket, error = " << GetSocketErrorCode();
200  return false;
201  }
202 
203  if (is_multicast) {
204  if (options->is_source_specific_multicast()) {
205  struct ip_mreq_source source_multicast_group;
206 
207  source_multicast_group.imr_multiaddr = local_in_addr;
208  if (inet_pton(AF_INET,
209  options->interface_address().c_str(),
210  &source_multicast_group.imr_interface) != 1) {
211  LOG(ERROR) << "Malformed IPv4 interface address "
212  << options->interface_address();
213  return false;
214  }
215  if (inet_pton(AF_INET,
216  options->source_address().c_str(),
217  &source_multicast_group.imr_sourceaddr) != 1) {
218  LOG(ERROR) << "Malformed IPv4 source specific multicast address "
219  << options->source_address();
220  return false;
221  }
222 
223  if (setsockopt(new_socket.get(),
224  IPPROTO_IP,
225  IP_ADD_SOURCE_MEMBERSHIP,
226  reinterpret_cast<const char*>(&source_multicast_group),
227  sizeof(source_multicast_group)) < 0) {
228  LOG(ERROR) << "Failed to join multicast group, error = "
229  << GetSocketErrorCode();
230  return false;
231  }
232  } else {
233  // this is a v2 join without a specific source.
234  struct ip_mreq multicast_group;
235 
236  multicast_group.imr_multiaddr = local_in_addr;
237 
238  if (inet_pton(AF_INET, options->interface_address().c_str(),
239  &multicast_group.imr_interface) != 1) {
240  LOG(ERROR) << "Malformed IPv4 interface address "
241  << options->interface_address();
242  return false;
243  }
244 
245  if (setsockopt(new_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
246  reinterpret_cast<const char*>(&multicast_group),
247  sizeof(multicast_group)) < 0) {
248  LOG(ERROR) << "Failed to join multicast group, error = "
249  << GetSocketErrorCode();
250  return false;
251  }
252  }
253 
254 #if defined(__linux__)
255  // Disable IP_MULTICAST_ALL to avoid interference caused when two sockets
256  // are bound to the same port but joined to different multicast groups.
257  const int optval_zero = 0;
258  if (setsockopt(new_socket.get(), IPPROTO_IP, IP_MULTICAST_ALL,
259  reinterpret_cast<const char*>(&optval_zero),
260  sizeof(optval_zero)) < 0 &&
261  GetSocketErrorCode() != ENOPROTOOPT) {
262  LOG(ERROR) << "Failed to disable IP_MULTICAST_ALL option, error = "
263  << GetSocketErrorCode();
264  return false;
265  }
266 #endif // #if defined(__linux__)
267  }
268 
269  // Set timeout if needed.
270  if (options->timeout_us() != 0) {
271  struct timeval tv;
272  tv.tv_sec = options->timeout_us() / 1000000;
273  tv.tv_usec = options->timeout_us() % 1000000;
274  if (setsockopt(new_socket.get(), SOL_SOCKET, SO_RCVTIMEO,
275  reinterpret_cast<const char*>(&tv), sizeof(tv)) < 0) {
276  LOG(ERROR) << "Failed to set socket timeout, error = "
277  << GetSocketErrorCode();
278  return false;
279  }
280  }
281 
282  if (options->buffer_size() > 0) {
283  const int receive_buffer_size = options->buffer_size();
284  if (setsockopt(new_socket.get(), SOL_SOCKET, SO_RCVBUF,
285  reinterpret_cast<const char*>(&receive_buffer_size),
286  sizeof(receive_buffer_size)) < 0) {
287  LOG(ERROR) << "Failed to set the maximum receive buffer size, error = "
288  << GetSocketErrorCode();
289  return false;
290  }
291  }
292 
293  socket_ = new_socket.release();
294  return true;
295 }
296 
297 } // namespace shaka
shaka::UdpFile::Flush
bool Flush() override
Definition: udp_file.cc:105
shaka::UdpFile::UdpFile
UdpFile(const char *address_and_port)
Definition: udp_file.cc:58
shaka::File::file_name
const std::string & file_name() const
Definition: file.h:96
shaka
All the methods that are virtual are virtual for mocking.
Definition: gflags_hex_bytes.cc:11
shaka::UdpFile::Open
bool Open() override
Internal open. Should not be used directly.
Definition: udp_file.cc:143
shaka::UdpFile::Close
bool Close() override
Definition: udp_file.cc:63
shaka::UdpFile::Seek
bool Seek(uint64_t position) override
Definition: udp_file.cc:110
shaka::UdpFile::Size
int64_t Size() override
Definition: udp_file.cc:98
shaka::File
Define an abstract file interface.
Definition: file.h:28
shaka::UdpFile::Tell
bool Tell(uint64_t *position) override
Definition: udp_file.cc:115
shaka::UdpFile::Read
int64_t Read(void *buffer, uint64_t length) override
Definition: udp_file.cc:76
shaka::UdpOptions::ParseFromString
static std::unique_ptr< UdpOptions > ParseFromString(base::StringPiece udp_url)
Definition: udp_options.cc:75
shaka::UdpFile::Write
int64_t Write(const void *buffer, uint64_t length) override
Definition: udp_file.cc:93