1 //
2 // Copyright 2011 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 // Copyright 2019 Ettus Research, A National Instruments Brand
5 //
6 // SPDX-License-Identifier: GPL-3.0-or-later
7 //
8
9 #pragma once
10
11 #include <uhd/config.hpp>
12 #include <uhd/exception.hpp>
13 #include <uhd/rfnoc/constants.hpp>
14 #include <uhd/types/device_addr.hpp>
15 #include <uhd/utils/log.hpp>
16 #include <uhdlib/transport/links.hpp>
17 #include <uhdlib/utils/narrow.hpp>
18 #include <boost/asio.hpp>
19 #include <boost/format.hpp>
20 #include <thread>
21
22 namespace uhd { namespace transport {
23
24 // Jumbo frames can be up to 9600 bytes;
25 constexpr size_t MAX_ETHERNET_MTU = 9600;
26
27 constexpr size_t UDP_DEFAULT_NUM_FRAMES = 1;
28
29 // Based on common 1500 byte MTU for 1GbE
30 constexpr size_t UDP_DEFAULT_FRAME_SIZE = 1472;
31
32 // 20ms of data for 1GbE link (in bytes)
33 constexpr size_t UDP_DEFAULT_BUFF_SIZE = 2500000;
34
35
36 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
37 // MacOS limits socket buffer size to 1 Mib
38 static const size_t MAX_BUFF_SIZE_ETH_MACOS = 0x100000; // 1Mib
39 #endif
40
41 typedef std::shared_ptr<boost::asio::ip::udp::socket> socket_sptr;
42
43 /*!
44 * Wait for the socket to become ready for a receive operation.
45 * \param sock_fd the open socket file descriptor
46 * \param timeout_ms the timeout duration in milliseconds
47 * \return true when the socket is ready for receive
48 */
wait_for_recv_ready(int sock_fd,int32_t timeout_ms)49 UHD_INLINE bool wait_for_recv_ready(int sock_fd, int32_t timeout_ms)
50 {
51 #ifdef UHD_PLATFORM_WIN32 // select is more portable than poll unfortunately
52 // setup timeval for timeout
53 timeval tv;
54 // If the tv_usec > 1 second on some platforms, select will
55 // error EINVAL: An invalid timeout interval was specified.
56 tv.tv_sec = int(timeout_ms / 1000);
57 tv.tv_usec = int(timeout_ms * 1000) % 1000000;
58
59 // setup rset for timeout
60 fd_set rset;
61 FD_ZERO(&rset);
62 FD_SET(sock_fd, &rset);
63
64 // http://www.gnu.org/s/hello/manual/libc/Interrupted-Primitives.html
65 // This macro is provided with gcc to properly deal with EINTR.
66 // If not provided, define an empty macro, assume that is OK
67 # ifndef TEMP_FAILURE_RETRY
68 # define TEMP_FAILURE_RETRY(x) (x)
69 # endif
70
71 // call select with timeout on receive socket
72 return TEMP_FAILURE_RETRY(::select(sock_fd + 1, &rset, NULL, NULL, &tv)) > 0;
73 #else
74 pollfd pfd_read;
75 pfd_read.fd = sock_fd;
76 pfd_read.events = POLLIN;
77
78 // call poll with timeout on receive socket
79 return ::poll(&pfd_read, 1, (int)timeout_ms) > 0;
80 #endif
81 }
82
open_udp_socket(const std::string & addr,const std::string & port,boost::asio::io_service & io_service)83 UHD_INLINE socket_sptr open_udp_socket(
84 const std::string& addr, const std::string& port, boost::asio::io_service& io_service)
85 {
86 using udp = boost::asio::ip::udp;
87
88 // resolve the address
89 udp::resolver resolver(io_service);
90 udp::resolver::query query(udp::v4(), addr, port);
91 udp::endpoint receiver_endpoint = *resolver.resolve(query);
92
93 // create, open, and connect the socket
94 socket_sptr socket = socket_sptr(new udp::socket(io_service));
95 socket->open(udp::v4());
96 socket->connect(receiver_endpoint);
97
98 return socket;
99 }
100
recv_udp_packet(int sock_fd,void * mem,size_t frame_size,int32_t timeout_ms)101 UHD_INLINE size_t recv_udp_packet(
102 int sock_fd, void* mem, size_t frame_size, int32_t timeout_ms)
103 {
104 ssize_t len;
105
106 #ifdef MSG_DONTWAIT // try a non-blocking recv() if supported
107 len = ::recv(sock_fd, (char*)mem, frame_size, MSG_DONTWAIT);
108 if (len > 0) {
109 return len;
110 }
111 #endif
112
113 if (wait_for_recv_ready(sock_fd, timeout_ms)) {
114 len = uhd::narrow_cast<ssize_t>(::recv(sock_fd, (char*)mem, frame_size, 0));
115 if (len == 0) {
116 throw uhd::io_error("socket closed");
117 }
118 if (len < 0) {
119 throw uhd::io_error(
120 str(boost::format("recv error on socket: %s") % strerror(errno)));
121 }
122 return len;
123 }
124
125 return 0; // timeout
126 }
127
send_udp_packet(int sock_fd,void * mem,size_t len)128 UHD_INLINE void send_udp_packet(int sock_fd, void* mem, size_t len)
129 {
130 // Retry logic because send may fail with ENOBUFS.
131 // This is known to occur at least on some OSX systems.
132 // But it should be safe to always check for the error.
133 while (true) {
134 const ssize_t ret =
135 uhd::narrow_cast<ssize_t>(::send(sock_fd, (const char*)mem, len, 0));
136 if (ret == ssize_t(len))
137 break;
138 if (ret == -1 and errno == ENOBUFS) {
139 std::this_thread::sleep_for(std::chrono::microseconds(1));
140 continue; // try to send again
141 }
142 if (ret == -1) {
143 throw uhd::io_error(
144 str(boost::format("send error on socket: %s") % strerror(errno)));
145 }
146 UHD_ASSERT_THROW(ret == ssize_t(len));
147 }
148 }
149
150 template <typename Opt>
get_udp_socket_buffer_size(socket_sptr socket)151 size_t get_udp_socket_buffer_size(socket_sptr socket)
152 {
153 Opt option;
154 socket->get_option(option);
155 return option.value();
156 }
157
158 template <typename Opt>
resize_udp_socket_buffer(socket_sptr socket,size_t num_bytes)159 size_t resize_udp_socket_buffer(socket_sptr socket, size_t num_bytes)
160 {
161 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
162 // limit buffer resize on macos or it will error
163 num_bytes = std::min(num_bytes, MAX_BUFF_SIZE_ETH_MACOS);
164 #endif
165 Opt option(num_bytes);
166 socket->set_option(option);
167 return get_udp_socket_buffer_size<Opt>(socket);
168 }
169
resize_udp_socket_buffer_with_warning(std::function<size_t (size_t)> resize_fn,const size_t target_size,const std::string & name)170 UHD_INLINE size_t resize_udp_socket_buffer_with_warning(
171 std::function<size_t(size_t)> resize_fn,
172 const size_t target_size,
173 const std::string& name)
174 {
175 size_t actual_size = 0;
176 std::string help_message;
177 #if defined(UHD_PLATFORM_LINUX)
178 help_message = str(boost::format("Please run: sudo sysctl -w net.core.%smem_max=%d")
179 % ((name == "recv") ? "r" : "w") % target_size);
180 #endif /*defined(UHD_PLATFORM_LINUX)*/
181
182 // resize the buffer if size was provided
183 if (target_size > 0) {
184 actual_size = resize_fn(target_size);
185
186 UHD_LOGGER_TRACE("UDP")
187 << boost::format("Target/actual %s sock buff size: %d/%d bytes") % name
188 % target_size % actual_size;
189 if (actual_size < target_size)
190 UHD_LOGGER_WARNING("UDP")
191 << boost::format(
192 "The %s buffer could not be resized sufficiently.\n"
193 "Target sock buff size: %d bytes.\n"
194 "Actual sock buff size: %d bytes.\n"
195 "See the transport application notes on buffer resizing.\n%s")
196 % name % target_size % actual_size % help_message;
197 }
198
199 return actual_size;
200 }
201
202 /*!
203 * Determines a set of values to use for a UDP CHDR link based on defaults and
204 * any overrides that the user may have provided. In cases where both device
205 * and stream arguments can be used to override a value, note that the stream
206 * argument will always take precedence.
207 *
208 * \param link_type the link type (CTRL, RX, TX) to calculate parameters for
209 * \param send_mtu the MTU of link for Tx cases
210 * \param recv_mtu the MTU of link for Rx cases
211 * \param default_link_params default values to use for the link parameters
212 * \param device_args device-level argument dictionary for overrides
213 * \param link_args argument dictionary with stream-level overrides (come from
214 * stream params)
215 * \return Parameters to apply
216 */
calculate_udp_link_params(const uhd::transport::link_type_t link_type,const size_t send_mtu,const size_t recv_mtu,const link_params_t & default_link_params,const uhd::device_addr_t & device_args,const uhd::device_addr_t & link_args)217 inline link_params_t calculate_udp_link_params(
218 const uhd::transport::link_type_t link_type,
219 const size_t send_mtu,
220 const size_t recv_mtu,
221 const link_params_t& default_link_params,
222 const uhd::device_addr_t& device_args,
223 const uhd::device_addr_t& link_args)
224 {
225 // Apply any device-level overrides to the default values first.
226 // If the MTU is overridden, it will be capped to the value provided by
227 // the caller.
228 const size_t constrained_send_mtu =
229 std::min(send_mtu, device_args.cast<size_t>("mtu", send_mtu));
230 const size_t constrained_recv_mtu =
231 std::min(recv_mtu, device_args.cast<size_t>("mtu", recv_mtu));
232
233 link_params_t link_params;
234 link_params.num_send_frames =
235 device_args.cast<size_t>("num_send_frames", default_link_params.num_send_frames);
236 link_params.num_recv_frames =
237 device_args.cast<size_t>("num_recv_frames", default_link_params.num_recv_frames);
238 link_params.send_frame_size =
239 device_args.cast<size_t>("send_frame_size", default_link_params.send_frame_size);
240 link_params.recv_frame_size =
241 device_args.cast<size_t>("recv_frame_size", default_link_params.recv_frame_size);
242 link_params.send_buff_size =
243 device_args.cast<size_t>("send_buff_size", default_link_params.send_buff_size);
244 link_params.recv_buff_size =
245 device_args.cast<size_t>("recv_buff_size", default_link_params.recv_buff_size);
246
247 // Now apply stream-level overrides based on the link type.
248 if (link_type == link_type_t::CTRL) {
249 // Control links typically do not allow the number of frames to be
250 // configured.
251 link_params.num_recv_frames =
252 uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE;
253 } else if (link_type == link_type_t::TX_DATA) {
254 // Note that the send frame size will be capped to the Tx MTU.
255 link_params.send_frame_size = link_args.cast<size_t>("send_frame_size",
256 std::min(link_params.send_frame_size, constrained_send_mtu));
257 link_params.num_send_frames =
258 link_args.cast<size_t>("num_send_frames", link_params.num_send_frames);
259 link_params.send_buff_size =
260 link_args.cast<size_t>("send_buff_size", link_params.send_buff_size);
261 } else if (link_type == link_type_t::RX_DATA) {
262 // Note that the receive frame size will be capped to the Rx MTU.
263 link_params.recv_frame_size = link_args.cast<size_t>("recv_frame_size",
264 std::min(link_params.recv_frame_size, constrained_recv_mtu));
265 link_params.num_recv_frames =
266 link_args.cast<size_t>("num_recv_frames", link_params.num_recv_frames);
267 link_params.recv_buff_size =
268 link_args.cast<size_t>("recv_buff_size", link_params.recv_buff_size);
269 }
270
271 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
272 // limit buffer size on OSX to avoid the warning issued by
273 // resize_buff_helper
274 if (link_params.recv_buff_size > MAX_BUFF_SIZE_ETH_MACOS) {
275 link_params.recv_buff_size = MAX_BUFF_SIZE_ETH_MACOS;
276 }
277 if (link_params.send_buff_size > MAX_BUFF_SIZE_ETH_MACOS) {
278 link_params.send_buff_size = MAX_BUFF_SIZE_ETH_MACOS;
279 }
280 #endif
281
282 return link_params;
283 }
284
285
286 }} // namespace uhd::transport
287