xbox-kernel/private/ntos/xnet/tcp/mcast.c
2020-09-30 17:17:25 +02:00

393 lines
8.3 KiB
C

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
mcast.c
Abstract:
Implement multicast support on UDP and RAW sockets
Revision History:
07/07/2000 davidx
Created it.
--*/
#include "precomp.h"
//
// Maximum of multicast groups that can be joined from a single socket
//
UINT cfgMaxSocketMcastGroups = 16;
VOID
PcbCleanupMcastData(
PCB* pcb
)
/*++
Routine Description:
Cleanup multicast group membership data associated with the PCB
Arguments:
pcb - Points to the protocol control block
Return Value:
NONE
--*/
{
PcbMcastData* pcbmcast;
PcbMcastGroup* mcastgrp;
UINT count;
RUNS_AT_DISPATCH_LEVEL
if ((pcbmcast = pcb->mcastData) == NULL) return;
pcb->mcastData = NULL;
// Drop all multicast groups that this socket belongs to
mcastgrp = pcbmcast->mcastGroups;
count = pcbmcast->groupCount;
while (count--) {
IfChangeMcastGroup(mcastgrp->ifp, mcastgrp->mcastaddr, FALSE);
mcastgrp++;
}
SysFree(pcbmcast);
}
INLINE PcbMcastData*
PcbGetMcastData(
PCB* pcb
)
/*++
Routine Description:
Allocate per-socket multicast group membership information
Arguments:
pcb - Points to the protocol control block
Return Value:
Points to the per-socket multicast information
NULL if out of memory
--*/
{
if (pcb->mcastData == NULL) {
UINT size = offsetof(PcbMcastData, mcastGroups) +
sizeof(PcbMcastGroup) * cfgMaxSocketMcastGroups;
pcb->mcastData = (PcbMcastData*) SysAlloc0(size, PTAG_MCAST);
}
return pcb->mcastData;
}
NTSTATUS
PcbSetMcastIf(
PCB* pcb,
IPADDR ifaddr
)
/*++
Routine Description:
Specifies the default interface for sending out multicast datagrams
Arguments:
pcb - Points to the protocol control block
ifaddr - Specifies the address of the interface designated
for sending out multicast datagrams by default;
use 0 to unset any existing multicast interface
Return Value:
Status code
--*/
{
NTSTATUS status;
KIRQL irql;
IfInfo* ifp;
PcbMcastData* pcbmcast;
irql = RaiseToDpc();
if ((pcbmcast = PcbGetMcastData(pcb)) != NULL) {
if (ifaddr == 0) {
RTE* rte = IpFindRTE(CLASSD_NETID, NULL);
ifp = rte ? rte->ifp : NULL;
} else {
ifp = IfFindInterface(ifaddr);
}
if (ifp && IfMcastEnabled(ifp)) {
CACHE_IFP_REFERENCE(pcbmcast->mcastIfp, ifp);
pcbmcast->mcastIfAddr = ifaddr;
status = NETERR_OK;
} else {
status = NETERR(WSAEADDRNOTAVAIL);
}
} else {
status = NETERR_MEMORY;
}
LowerFromDpc(irql);
return status;
}
IfInfo*
PcbGetMcastIf(
PCB* pcb
)
/*++
Routine Description:
Return the default multicast interface for a socket
Arguments:
pcb - Points to the protocol control block
Return Value:
Status code
--*/
{
NTSTATUS status;
if (!pcb->mcastData || !pcb->mcastData->mcastIfp) {
ASSERT(!pcb->mcastData || pcb->mcastData->mcastIfAddr == 0);
status = PcbSetMcastIf(pcb, 0);
if (!NT_SUCCESS(status))
return NULL;
}
return pcb->mcastData->mcastIfp;
}
NTSTATUS
PcbChangeMcastGroup(
PCB* pcb,
IPADDR mcastaddr,
IPADDR ifaddr,
BOOL add
)
/*++
Routine Description:
Join or leave a multicast group
Arguments:
pcb - Points to the protocol control block
mcastaddr - Specifies the multicast group address
ifaddr - Specifies the interface address (0 means any)
add - Whether to join or leave the group
Return Value:
Status code
--*/
{
NTSTATUS status;
PcbMcastData* pcbmcast;
KIRQL irql = RaiseToDpc();
pcbmcast = PcbGetMcastData(pcb);
if (pcbmcast) {
PcbMcastGroup* mcastgrp;
UINT index;
BOOL existing;
// Check to see if the socket is already joined into
// the specified group/interface combination.
mcastgrp = pcbmcast->mcastGroups;
for (index=0; index < pcbmcast->groupCount; index++, mcastgrp++) {
if (mcastgrp->mcastaddr == mcastaddr &&
mcastgrp->ifaddr == ifaddr)
break;
}
existing = (index < pcbmcast->groupCount);
if (add) {
// Joining a group
if (existing) {
// The socket already belong to the specified group
status = NETERR(WSAEADDRINUSE);
} else if (pcbmcast->groupCount == cfgMaxSocketMcastGroups) {
// Socket belongs to too many groups
status = NETERR(WSAEADDRNOTAVAIL);
} else {
IfInfo* ifp;
ifp = ifaddr ? IfFindInterface(ifaddr) : PcbGetMcastIf(pcb);
if (ifp) {
status = IfChangeMcastGroup(ifp, mcastaddr, TRUE);
if (NT_SUCCESS(status)) {
mcastgrp->mcastaddr = mcastaddr;
mcastgrp->ifaddr = ifaddr;
CACHE_IFP_REFERENCE(mcastgrp->ifp, ifp);
pcbmcast->groupCount++;
}
} else {
status = NETERR_UNREACHABLE;
}
}
} else {
// Leaving a group
if (existing) {
status = IfChangeMcastGroup(mcastgrp->ifp, mcastgrp->mcastaddr, FALSE);
pcbmcast->groupCount--;
while (index++ < pcbmcast->groupCount) {
*mcastgrp = *(mcastgrp+1);
mcastgrp++;
}
} else {
status = NETERR_PARAM;
}
}
} else {
status = NETERR_MEMORY;
}
LowerFromDpc(irql);
return status;
}
BOOL
PcbCheckMcastGroup(
PCB* pcb,
IPADDR mcastaddr
)
/*++
Routine Description:
Check if the socket is joined into the specified multicast group
Arguments:
pcb - Points to the protocol control block
mcastaddr - Specifies the multicast group address
Return Value:
TRUE if the socket belongs to the specified multicast group
FALSE otherwise
Note:
We're not checking the receiving interface here.
In theory, we could redundantly deliver a multicast
datagram received from one interface to a socket that
belongs to the same multicast group on another interface.
But such practice is discouraged. Besides, we'll only have
one active interface other than the loopback interface.
--*/
{
PcbMcastData* pcbmcast = pcb->mcastData;
PcbMcastGroup* mcastgrp;
UINT count;
if (!pcbmcast) return FALSE;
mcastgrp = pcbmcast->mcastGroups;
count = pcbmcast->groupCount;
while (count--) {
if (mcastgrp->mcastaddr == mcastaddr) return TRUE;
mcastgrp++;
}
return FALSE;
}
NTSTATUS
PcbSendMcastDgram(
PCB* pcb,
Packet* pkt,
IPADDR dstaddr
)
/*++
Routine Description:
Send out a multicast datagram
Arguments:
pcb - Points to the protocol control block
pkt - Points to the packet to be sent
dstaddr - Specifies the destination multicast address
Return Value:
Status code
--*/
{
IfInfo* ifp;
RUNS_AT_DISPATCH_LEVEL
// Send a copy of the multicast datagram to ourselves if needed
if (!pcb->noMcastLoopback) {
IpQueueLoopbackPacket(pkt, TRUE);
}
// Choose the outgoing interface if we have done so already
if ((pcb->mcastData == NULL || (ifp = pcb->mcastData->mcastIfp) == NULL) &&
(ifp = PcbGetMcastIf(pcb)) == NULL) {
XnetCompletePacket(pkt, NETERR_UNREACHABLE);
return NETERR_UNREACHABLE;
}
// Check to see if the datagram size is too large
if (pkt->datalen > ifp->mtu) {
XnetCompletePacket(pkt, NETERR_MSGSIZE);
return NETERR_MSGSIZE;
}
// Queue up the packet for transmission
pkt->nexthop = dstaddr;
IfEnqueuePacket(ifp, pkt);
ifp->StartOutput(ifp);
return NETERR_OK;
}