Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 26 additions & 15 deletions src/socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ void CSocket::Init ( const quint16 iNewPortNumber, const quint16 iNewQosNumber,
}
#endif

#if !defined( Q_OS_DARWIN ) && !defined( Q_OS_WIN )
#if !defined( Q_OS_BSD4 ) && !defined( Q_OS_WIN )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a guidance note on where these are defined and how - specifically for Jamulus - they should be used. (Not part of this PR, obviously.)

// set the QoS for IPv4 as well, as this is a dual-protocol socket
if ( setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, (const char*) &tos, sizeof ( tos ) ) == -1 )
{
Expand Down Expand Up @@ -272,44 +272,55 @@ CSocket::~CSocket()
#endif
}

#if defined( Q_OS_DARWIN )
// sendto_ipv4_with_tos - helper function for macOS to set TOS when sending IPv4 over IPv6 socket
#if defined( Q_OS_BSD4 )
Comment thread
ann0see marked this conversation as resolved.
// sendto_ipv4_with_tos - helper function for macOS and FreeBSD to set TOS when sending IPv4 over IPv6 socket
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 )
{
// For a description of 'struct cmsghdr' and the 'CMSG_xxx' macros, see 'man 3 cmsg' on a Linux machine.
// The macOS man pages are less descriptive, but the API is the same, being based on the BSD socket interface.

# if defined( Q_OS_FREEBSD )
using tos_cmsg_type = unsigned char;
# else
using tos_cmsg_type = int;
# endif

// The cmsg buffer is only set up once (tos doesn't change) so can be static
static union
{
unsigned char cbuf[CMSG_SPACE ( sizeof ( int ) )];
struct cmsghdr h;
unsigned char cbuf[CMSG_SPACE ( sizeof ( tos_cmsg_type ) )];
struct cmsghdr align;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Why) is the naming change needed? here probably stands for header?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cbuf for "unsigned character buffer" is okay (better with a "u"?), as it implies "here's some unstructured data".

h, yes, a bit brief and align is... odd - it's "a cmsghdr struct aligned to that unsigned character buffer". msghdr maybe?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably no reason. Name was chosen by ChatGPT, but h is good enough really.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And lots and lots of networking code everywhere uses this approach. (OK, it's very old networking code...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> back to h

} u;
static socklen_t clen = 0;
static size_t clen = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is more generic. Is this guaranteed to be sound?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChatGPT recommended it for maximum portability, but I didn't test both with and without.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not use size_t if something specific exists and it compiles.


if ( clen == 0 )
{
// set up the cmsg buffer
memset ( u.cbuf, 0, sizeof ( u.cbuf ) );
memset ( &u, 0, sizeof ( u ) );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&u is just to make it more concise?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to make it clear it's the address of both the cbuf and header.


struct cmsghdr* cmsg = reinterpret_cast<struct cmsghdr*> ( u.cbuf );
Copy link
Copy Markdown
Collaborator

@pljones pljones Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is C pointer magic -- but isn't the union overlaying struct cmsghdr align and unsigned char cbuf[..] at the same address, so this could just be

struct cmsghdr* cmsg = &u.align;

or am I misunderstanding it completely?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's an excellent point. The magic was provided by ChatGPT when I asked it why the original function worked on macOS but not FreeBSD. I didn't study it in detail at the time, but I think you're right, and prefer your suggestion.

I'm happy to change it and test, but no time today.

cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_TOS;
cmsg->cmsg_len = CMSG_LEN ( sizeof ( tos_cmsg_type ) );

u.h.cmsg_level = IPPROTO_IP;
u.h.cmsg_type = IP_TOS;
u.h.cmsg_len = CMSG_LEN ( sizeof ( int ) );
memcpy ( CMSG_DATA ( &u.h ), &tos, sizeof ( int ) );
clen = (socklen_t) u.h.cmsg_len;
tos_cmsg_type v = static_cast<tos_cmsg_type> ( tos & 0xFF );
memcpy ( CMSG_DATA ( cmsg ), &v, sizeof ( v ) );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v doesn't say much. As far as I understand this, you extract a bit from tos? It should be made more clear what that is.

Copy link
Copy Markdown
Collaborator

@pljones pljones Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's throw-away -- it needs to be an instance variable (so v for "I need an instance variable of no other use"), though, otherwise you can't take the address of it to pass to memcpy.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll revisit tomorrow, tidy up and test again.

clen = CMSG_SPACE ( sizeof ( tos_cmsg_type ) );
}

struct iovec iov;
memset ( &iov, 0, sizeof ( iov ) );
iov.iov_base = const_cast<void*> ( buf );
iov.iov_len = len;

struct msghdr msg;
memset ( &msg, 0, sizeof ( msg ) );

msg.msg_name = const_cast<sockaddr*> ( dest );
msg.msg_namelen = destlen;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (void*) u.cbuf;
msg.msg_control = u.cbuf;
msg.msg_controllen = clen;

return sendmsg ( fd, &msg, flags );
Expand Down Expand Up @@ -355,8 +366,8 @@ void CSocket::SendPacket ( const CVector<uint8_t>& vecbySendBuf, const CHostAddr
addr[2] = htonl ( 0xFFFF );
addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() );

#if defined( Q_OS_DARWIN )
// In macOS we need to set TOS explicitly when sending IPv4 over IPv6 socket
#if defined( Q_OS_BSD4 )
// In macOS and FreeBSD we need to set TOS explicitly when sending IPv4 over IPv6 socket
status = sendto_ipv4_with_tos ( UdpSocket,
(const char*) &( (CVector<uint8_t>) vecbySendBuf )[0],
iVecSizeOut,
Expand Down
Loading