Setzen der QoS-per-packet-Werte bei IPv4 und IPv6
Mögliche Werte des Qos-Values bei IPv4 (IP_TOS) und IPv6 (IPV6_TCLASS:
Setzen des dscp-Values als dauerhafter Wert eines sockets:
void set_dscp(int fd, unsigned dscp, sa_family_t af)
{
assert(fd >= 0 && "ioops::set_dscp called with invalid fd");
int rc(0);
switch (af) {
case AF_INET6:
rc = mysetsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof dscp);
break;
case AF_INET:
rc = mysetsockopt(fd, IPPROTO_IP, IP_TOS, &dscp, sizeof dscp);
break;
default:
assert(!"set_dscp unknown address family");
break;
}
(void)rc; // to avoid compiler warning if assert is not compiled
assert(rc == 0 && "setting of dscp value failed");
}
Die Bestimmung der sa_family_t ist in den meisten Fällen eine Abfrage der 'sa_family' der
gegebenen Adresse. Allerdings kann in einer IPv6-Adresse eine IPv4-Adresse gemapped sein -
dies muss ermittelt werden. Hat eine IPv6-Adresse in den ersten 80 Bits '0', dann 16-mal '1',
handlet es ich um eine gemappte IPv4-Adresse:
sa_family_t detect_sa_family(const sockaddr& sa) {
sa_family_t of(sa.sa_family);
assert((of == AF_INET || of == AF_INET6)
&& "ioops::detect_sa_family called for invalid adress family");
sa_family_t af(of);
if (of == AF_INET6) {
struct sockaddr_in6* s6 = (struct sockaddr_in6*) sa.sa_data;
struct in6_addr* in6a = &s6->sin6_addr;
const uint32_t* data = (const uint32_t*) in6a->s6_addr;
if ((data[0] == 0) && (data[1] == 0) && ((data[2] & htonl(0xffff0000)) == 0xffff))
af = AF_INET;
else
af = AF_INET6;
}
return af;
}
Möchte man 'Pro-Paket-Qos' einsetzen, hilft das dauerhafte Setzen der setsockopt nicht.
Hier muss man mit sog. 'ancillary'-Data arbeiten; pro sendmsg wird im verwendeten 'msghdr'
der Qos-Value als ancillary-data in einer cmsghdr-Struktur hinzugefügt:
(Die angeommene Klasse hat dabei den verwendeten-Socketdescriptor 'fd_' als Member)
int
Class::sendmsg(struct msghdr* mh, const sockaddr& to, unsigned dscp, int flags)
{
char buf[CMSG_SPACE(sizeof(unsigned))];
mh->msg_control = (caddr_t) buf;
mh->msg_controllen = CMSG_LEN(sizeof(unsigned));
sa_family_t af(detect_sa_family(to));
switch (af) {
case AF_INET6:
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_TCLASS;
break;
case AF_INET:
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_TOS;
break;
default:
assert(!"fill dscp cmsg called for invalid address family");
break;
}
cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned));
*(unsigned*)(CMSG_DATA(cmsg)) = dscp;
return ::sendmsg(fd_, mh, flags);
}
Möchte man auf Empfänger-Seite ermitteln, ob der Sender ein Qos-Setting als 'ancillary'-data
mitgegeben hat, kann er dies per setsockopt bei seinem kernel beauftragen.
void order_cmsg(int fd, const sockaddr& a)
{
sa_family_t af(detect_sa_family(a));
int set = 1;
switch (af) {
case AF_INET6:
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &set, sizeof(set)) < 0) {
printf("IPV6_RECVTCLASS setting failed\n");
}
break;
case AF_INET:
if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &set, sizeof(set)) < 0) {
printf("IP_RECVTOS setting failed\n");
}
break;
default:
break;
}
}
Hat man nun per 'recvmsg' ein Paket empfangen (und zuvor die ancillary-QoS-Settings beauftragt),
hängen an dem beim Empfang gefüllten 'msghdr' auch auch noch 'cmsghdr', die durchlaufen und gestest
werden können. Die folgende Funktion macht dies und liefert - sofern gesetzt - den QoS-Value des
Senders:
unsigned retrieve_dscp_value(struct msghdr& m)
{
unsigned rc = 0;
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
while (cmsg != NULL) {
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS) {
rc = *(xtd::punned_cast(CMSG_DATA(cmsg)));
break;
}
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS) {
rc = *(xtd::punned_cast(CMSG_DATA(cmsg)));
break;
}
cmsg = CMSG_NXTHDR(&m, cmsg);
}
return rc;
}
|