Search code examples
c#linuxsystem-callsipv6multicast

What is the format of optval for MCAST_JOIN_SOURCE_GROUP setsockopt with IPv6 in Unix?


I'm trying to create a multicast testing tool for my local network. The only feature I don't yet have working is IPv6 SSM on Linux. I've used strace to try and debug and this is the error I'm receiving when I try to join the multicast group:

setsockopt(67, SOL_IPV6, MCAST_JOIN_SOURCE_GROUP, "\3\0\0\0\0\0\0\0\27\0\270\v\0\0\0\0\3778\0\0\0\0\0\0\0\0\0\0\207eC!"..., 264) = -1 EADDRNOTAVAIL (Cannot assign requested address)

Optval = 03000000000000001700B80B00000000FF38000000000000000000008765432100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001700000000000000FE8000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.

I am using interface 3, trying to connect to FF38::8765:4321 on port 3000 with FE80::1 as my source. This seems correct to me, any ideas what could be causing the error?

Edit: Here's my code to make the syscall:

[DllImport("libc", SetLastError = true)]
static extern int setsockopt(int sockfd, int level, int optname, void *optval, int size);
fixed(void* data = &joinData[0])
{
    setsockopt((int)socket.Handle, 41, 46, data, joinData.Length);
}

and here's my code to populate optval:

byte[] joinBytes;
GroupSourceReqModel v6ssmJoin = new GroupSourceReqModel();
v6ssmJoin.gr_interface = (uint)selections.LocalIp.ScopeId;
SocketAddressIpv6InputModel mGroupIn = new SocketAddressIpv6InputModel
{
    sin6_family = (ushort)selections.MulticastAddress.AddressFamily,
    sin6_port = (ushort)selections.Port,
    sin6_scope_id = 0
};
for (int i = 0; i < selections.MulticastAddress.GetAddressBytes().Length; i++)
{
    mGroupIn.sin6_addr[i] = selections.MulticastAddress.GetAddressBytes()[i];
}
SocketAddressStorageModel groupSock = new SocketAddressStorageModel();       
byte[] groupSockInData = ConvertToBytes(mGroupIn);
Console.WriteLine(Convert.ToHexString(groupSockInData));
byte* groupSockPointer = (byte*)&groupSock;
for (int i = 0; i < groupSockInData.Length; i++)
{
    groupSockPointer[i] = groupSockInData[i];         // Now populate the groupSock object with the byte data of mGroupIn
}
v6ssmJoin.gsr_group = groupSock;    

SocketAddressIpv6InputModel sGroupIn = new SocketAddressIpv6InputModel
{
    sin6_family = (ushort)selections.SourceAddress.AddressFamily,
    sin6_port = 0,
    sin6_scope_id = 0
};
for (int i = 0; i < selections.SourceAddress.GetAddressBytes().Length; i++)
{
    sGroupIn.sin6_addr[i] = selections.SourceAddress.GetAddressBytes()[i];
}
SocketAddressStorageModel sourceSock = new SocketAddressStorageModel
{
    ss_family = (short)selections.SourceAddress.AddressFamily
};

byte[] sourceSockInData = ConvertToBytes(sGroupIn);
byte* sourceSockPointer = (byte*)&sourceSock;
for (int i = 0; i < sourceSockInData.Length; i++)
{
    sourceSockPointer[i] = sourceSockInData[i];
}
v6ssmJoin.gsr_source = sourceSock;

joinBytes = ConvertToBytes(v6ssmJoin);
return joinBytes;

public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged
{
    byte* pointer = (byte*)&value;

    byte[] bytes = new byte[sizeof(T)];
    for (int i = 0; i < sizeof(T); i++)
    {
        bytes[i] = pointer[i];
    }

    return bytes;
}

If it's relevant, I'm running on kernel version 5.4.0-122-generic.


Solution

  • Address family for IPv6 in Linux is 10, as seen in the kernel source code here which differs from the Windows value that C# uses. In addition, the port value is stored in big endian.