Skip to content

Commit 8d4c155

Browse files
authored
Merge pull request #3622 from rdica/IPv6-QoS-fix
IPv6 QoS fix for Linux and Mac
2 parents 043dafe + f0ca488 commit 8d4c155

2 files changed

Lines changed: 86 additions & 6 deletions

File tree

src/main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,12 @@ int main ( int argc, char** argv )
222222
// Quality of Service --------------------------------------------------
223223
if ( GetNumericArgument ( argc, argv, i, "-Q", "--qos", 0, 255, rDbleArgument ) )
224224
{
225+
#if defined( Q_OS_WIN )
226+
qWarning() << "QoS is currently not available under Windows - ignoring";
227+
#else
225228
iQosNumber = static_cast<quint16> ( rDbleArgument );
226229
qInfo() << qUtf8Printable ( QString ( "- selected QoS value: %1" ).arg ( iQosNumber ) );
230+
#endif
227231
CommandLineOptions << "--qos";
228232
continue;
229233
}

src/socket.cpp

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,28 @@ void CSocket::Init ( const quint16 iNewPortNumber, const quint16 iNewQosNumber,
114114

115115
// The IPV6_V6ONLY socket option must be false in order for the socket to listen on both protocols.
116116
// On Linux it's false by default on most (all?) distros, but on Windows it is true by default
117-
const uint8_t no = 0;
118-
setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*) &no, sizeof ( no ) );
117+
const int no = 0;
118+
if ( setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*) &no, sizeof ( no ) ) == -1 )
119+
{
120+
throw CGenErr ( "request to support IPv4 over IPv6 failed", "Network Error" );
121+
}
119122

120123
// set the QoS
121-
const char tos = (char) iQosNumber; // Quality of Service
122-
setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof ( tos ) );
124+
const int tos = (int) iQosNumber; // Quality of Service
125+
#if !defined( Q_OS_WIN )
126+
if ( setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*) &tos, sizeof ( tos ) ) == -1 )
127+
{
128+
throw CGenErr ( "request to set ToS for IPv6 failed", "Network Error" );
129+
}
130+
#endif
131+
132+
#if !defined( Q_OS_DARWIN ) && !defined( Q_OS_WIN )
133+
// set the QoS for IPv4 as well, as this is a dual-protocol socket
134+
if ( setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, (const char*) &tos, sizeof ( tos ) ) == -1 )
135+
{
136+
throw CGenErr ( "request to set ToS for IPv4 over IPv6 failed", "Network Error" );
137+
}
138+
#endif
123139

124140
UdpSocketAddr.sa6.sin6_family = AF_INET6;
125141
UdpSocketAddr.sa6.sin6_addr = in6addr_any;
@@ -146,9 +162,14 @@ void CSocket::Init ( const quint16 iNewPortNumber, const quint16 iNewQosNumber,
146162
throw CGenErr ( "IPv4 requested but not available on this system.", "Network Error" );
147163
}
148164

165+
#if !defined( Q_OS_WIN )
149166
// set the QoS
150-
const char tos = (char) iQosNumber; // Quality of Service
151-
setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) );
167+
const int tos = (int) iQosNumber; // Quality of Service
168+
if ( setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, (const char*) &tos, sizeof ( tos ) ) == -1 )
169+
{
170+
throw CGenErr ( "request to set ToS for IPv4 failed", "Network Error" );
171+
}
172+
#endif
152173

153174
// preinitialize socket in address (only the port number is missing)
154175
UdpSocketAddr.sa4.sin_family = AF_INET;
@@ -251,6 +272,50 @@ CSocket::~CSocket()
251272
#endif
252273
}
253274

275+
#if defined( Q_OS_DARWIN )
276+
// sendto_ipv4_with_tos - helper function for macOS to set TOS when sending IPv4 over IPv6 socket
277+
static ssize_t sendto_ipv4_with_tos ( int fd, const void* buf, size_t len, int flags, const struct sockaddr* dest, socklen_t destlen, int tos )
278+
{
279+
// For a description of 'struct cmsghdr' and the 'CMSG_xxx' macros, see 'man 3 cmsg' on a Linux machine.
280+
// The macOS man pages are less descriptive, but the API is the same, being based on the BSD socket interface.
281+
282+
// The cmsg buffer is only set up once (tos doesn't change) so can be static
283+
static union
284+
{
285+
unsigned char cbuf[CMSG_SPACE ( sizeof ( int ) )];
286+
struct cmsghdr h;
287+
} u;
288+
static socklen_t clen = 0;
289+
290+
if ( clen == 0 )
291+
{
292+
// set up the cmsg buffer
293+
memset ( u.cbuf, 0, sizeof ( u.cbuf ) );
294+
295+
u.h.cmsg_level = IPPROTO_IP;
296+
u.h.cmsg_type = IP_TOS;
297+
u.h.cmsg_len = CMSG_LEN ( sizeof ( int ) );
298+
memcpy ( CMSG_DATA ( &u.h ), &tos, sizeof ( int ) );
299+
clen = (socklen_t) u.h.cmsg_len;
300+
}
301+
302+
struct iovec iov;
303+
iov.iov_base = const_cast<void*> ( buf );
304+
iov.iov_len = len;
305+
306+
struct msghdr msg;
307+
308+
msg.msg_name = const_cast<sockaddr*> ( dest );
309+
msg.msg_namelen = destlen;
310+
msg.msg_iov = &iov;
311+
msg.msg_iovlen = 1;
312+
msg.msg_control = (void*) u.cbuf;
313+
msg.msg_controllen = clen;
314+
315+
return sendmsg ( fd, &msg, flags );
316+
}
317+
#endif
318+
254319
void CSocket::SendPacket ( const CVector<uint8_t>& vecbySendBuf, const CHostAddress& HostAddr )
255320
{
256321
int status = 0;
@@ -290,12 +355,23 @@ void CSocket::SendPacket ( const CVector<uint8_t>& vecbySendBuf, const CHostAddr
290355
addr[2] = htonl ( 0xFFFF );
291356
addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() );
292357

358+
#if defined( Q_OS_DARWIN )
359+
// In macOS we need to set TOS explicitly when sending IPv4 over IPv6 socket
360+
status = sendto_ipv4_with_tos ( UdpSocket,
361+
(const char*) &( (CVector<uint8_t>) vecbySendBuf )[0],
362+
iVecSizeOut,
363+
0,
364+
&UdpSocketAddr.sa,
365+
sizeof ( UdpSocketAddr.sa6 ),
366+
(int) iQosNumber );
367+
#else
293368
status = sendto ( UdpSocket,
294369
(const char*) &( (CVector<uint8_t>) vecbySendBuf )[0],
295370
iVecSizeOut,
296371
0,
297372
&UdpSocketAddr.sa,
298373
sizeof ( UdpSocketAddr.sa6 ) );
374+
#endif
299375
}
300376
else
301377
{

0 commit comments

Comments
 (0)