Обновление контрольной суммы UDP в фрагментированных пакетах

Я создаю сетевое устройство. Мне нужно поддерживать фрагментацию пакетов NAT и IP. Когда я меняю исходный или целевой адрес UDP-пакета, мне приходится исправлять контрольную сумму UDP (а также контрольную сумму IP, но это тривиально). Когда пакет фрагментирован, мне пришлось бы собрать все фрагменты для пересчета контрольной суммы. Я знаю старый адрес и новый адрес. Я хотел бы:

  1. Отменить отрицание контрольной суммы
  2. Вычесть старый адрес
  3. Добавить новый адрес
  4. Повторно уменьшить сумму и отрицать

Этот процесс не всегда работает. Есть ли способ обновить контрольную сумму, а не пересчитывать ее с нуля?

Я пробовал:

long CalcCheckSumAdd(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum += *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum += *pbHeader;

    return lSum;

}

long CalcCheckSumSubract(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum -= *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum -= *pbHeader;

    return lSum;

}

unsigned short CalcCheckSumFinish(long lSum){

    while (lSum >> 16){

        lSum = (lSum & 0xFFFF) + (lSum >> 16);

    }

    return (unsigned short)(~lSum);

}

long CalcCheckSumUnfinish(unsigned short usSum){

    // Can't totally undo lossy finish logic

    return ~usSum;

}

unsigned short CalcCheckSumUpdateAddress(unsigned short usOldSum, unsigned long ulOldAddress, unsigned long ulNewAddress){

    long lSumFixed = CalcCheckSumUnfinish(usOldSum);

    lSumFixed = CalcCheckSumSubract((unsigned char*)&ulOldAddress,sizeof(ulOldAddress),lSumFixed);

    lSumFixed = CalcCheckSumAdd((unsigned char*)&ulNewAddress,sizeof(ulNewAddress),lSumFixed);

    return CalcCheckSumFinish(lSumFixed);

}

Спасибо!

EDIT: ниже добавлен код модульного теста

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

long CalcCheckSumAdd(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum += *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum += *pbHeader;

    return lSum;

}

unsigned short CalcCheckSumFinish(long lSum){

    while (lSum >> 16){

        lSum = (lSum & 0xFFFF) + (lSum >> 16);

    }

    return (unsigned short)(~lSum);

}

void Randomize(unsigned char *pucPacket, unsigned long ulSize){

    for (unsigned long ulByte = 0; ulByte < ulSize; ulByte++){

        pucPacket[ulByte] = (unsigned char)(255 * rand() / RAND_MAX);

    }

}

unsigned short Calc(unsigned char *pucPacket, unsigned long ulSize){

    long lSum = CalcCheckSumAdd(pucPacket,ulSize,0);

    return CalcCheckSumFinish(lSum);

}

unsigned short Fix(unsigned short usOrig, unsigned int uiOld, unsigned int uiNew){

    // TODO: Replace this with something that makes main never fail
    usOrig -= uiOld & 0xffff;
    usOrig -= uiOld >> 16 & 0xffff;
    usOrig += uiNew & 0xffff;
    usOrig += uiNew >>16 & 0xffff;

    return usOrig;

}

void Break(unsigned char *pucPacket, unsigned int *puiOld, unsigned int *puiNew){

    unsigned int *puiChange = (unsigned int*)pucPacket;

    *puiOld = *puiChange;

    Randomize((unsigned char*)puiNew,sizeof(unsigned int));

    *puiChange = *puiNew;

}

void PrintBuffer(const char *szName, unsigned char *pucBuff, unsigned int uiSize){

    printf("%s: ",szName);

    for (unsigned int uiByte = 0; uiByte < uiSize; uiByte++){

        printf("%02X",(unsigned int)pucBuff[uiByte]);

    }

    printf("\n");

}

void PrintTestCase(unsigned char *pucOrig, unsigned char *pucChanged, unsigned int uiSize, unsigned short usOrig, unsigned short usChanged, unsigned short usFixed){

    PrintBuffer("Original Buffer",pucOrig,uiSize);
    PrintBuffer("Changed Buffer ",pucChanged,uiSize);

    printf("Orig    checksum: %04X\n",(unsigned int)usOrig);
    printf("Changed checksum: %04X\n",(unsigned int)usChanged);
    printf("Fixed   checksum: %04X\n",(unsigned int)usFixed);

}

int main(){

    srand((unsigned int)time(nullptr));

    unsigned char pucDataOrig[100];
    unsigned char pucDataChanged[100];

    bool bTestFailed = false;

    while (!bTestFailed){

        Randomize(pucDataOrig,sizeof(pucDataOrig));

        memcpy(pucDataChanged,pucDataOrig,sizeof(pucDataOrig));

        unsigned short usOrig = Calc(pucDataOrig,sizeof(pucDataOrig));

        unsigned int uiOld = 0,
                     uiNew = 0;

        Break(pucDataChanged,&uiOld,&uiNew);

        unsigned short usFixed = Fix(usOrig,uiOld,uiNew);

        unsigned short usChanged = Calc(pucDataChanged,sizeof(pucDataChanged));

        if (usChanged == usFixed){

            printf(".");

        }else{

            printf("\nTest case failed\n");
            PrintTestCase(pucDataOrig,pucDataChanged,sizeof(pucDataOrig),usOrig,usChanged,usFixed);

            bTestFailed = true;

        }

    }

    return 0;

}

person Daniel Knueven    schedule 25.04.2017    source источник
comment
Фрагментация и повторная сборка должны происходить на уровне 3 и быть прозрачными для уровня 4. Я действительно не вижу необходимости пересчитывать контрольную сумму UDP из-за фрагментации. Фрагментация должна происходить после NAT, когда пакет выходит из маршрутизатора, на выходном интерфейсе, а контрольная сумма уровня 4 уже должна быть пересчитана и обновлена.   -  person Ron Maupin    schedule 25.04.2017
comment
@RonMaupin Мое устройство получает фрагментированный пакет UDP. Затем он должен выполнить NAT. Вы не можете изменить исходный или целевой адрес пакета IP/UDP без пересчета/обновления контрольной суммы UDP, поскольку контрольная сумма UDP включает sudo-заголовок, который включает IP-адреса источника и получателя. Маршрутизаторы должны делать то же самое.   -  person Daniel Knueven    schedule 25.04.2017
comment
Маршрутизаторы не собирают фрагментированные пакеты, это работа хоста-получателя. Текущая тенденция сегодня заключается в том, что маршрутизаторы и брандмауэры настроены так, чтобы даже не принимать фрагменты пакетов (кроме первого фрагмента).   -  person Ron Maupin    schedule 25.04.2017
comment
Это из RFC 791: Базовый интернет-сервис ориентирован на дейтаграммы и обеспечивает фрагментацию дейтаграмм на шлюзах с повторной сборкой в ​​целевом модуле интернет-протокола на целевом хосте.   -  person Ron Maupin    schedule 25.04.2017
comment
@RonMaupin Я не пытаюсь собрать пакет. Я пытаюсь изменить исходный адрес пакета. Поскольку пакет представляет собой IP/UDP, мне нужно пересчитать контрольную сумму UDP. Мне нужны все фрагменты, чтобы пересчитать контрольную сумму UDP, я хотел бы избежать необходимости собирать фрагменты и просто немного посчитать, чтобы обновить контрольную сумму, а не пересчитывать ее. Большинство маршрутизаторов отправляют сообщение ICMP обратно на хост для фрагментации на основе TCP. Для UDP маршрутизаторам не нужно много делать для поддержки фрагментации (если только они не используют NAT). Cisco 2911, который у меня есть, поддерживает фрагментацию IP/UDP.   -  person Daniel Knueven    schedule 25.04.2017


Ответы (2)


Вы правы, приведенное выше решение работает только в некоторых случаях, но у меня есть новая реализация, которая работает для всех типов пакетов (фрагментированных или нет, UDP, TCP, IP). Вот реализация:

/* incremental checksum update */
static inline void
cksum_update(uint16_t *csum, uint32_t from, uint32_t to)
{
    uint32_t sum, csum_c, from_c, res, res2, ret, ret2;

    csum_c = ~((uint32_t)*csum);
    from_c = ~from;
    res = csum_c + from_c;
    ret = res + (res < from_c);

   res2 = ret + to;
   ret2 = res2 + (res2 < to);

   sum = ret2;
   sum = (sum & 0xffff) + (sum >> 16);
   sum = (sum & 0xffff) + (sum >> 16);
   *csum = (uint16_t)~sum;

}

Теперь вы можете использовать эту функцию при переводе адреса пакета и перед отправкой:

/* Update L4 checksums on all packet a part from [2nd, n] fragment */
switch (IS_FRAG(ipv4_hdr) ? 0 : ipv4_hdr->next_proto_id) {
case IPPROTO_TCP:
{
    struct tcp_hdr *tcp_hdr = tcp_header(pkt);

    /* Compute TCP checksum using incremental update */
    cksum_update(&tcp_hdr->cksum, old_ip_addr, *address);
    break;
}
case IPPROTO_UDPLITE:
case IPPROTO_UDP:
{
    struct udp_hdr *udp_hdr = udp_header(pkt);

    /* Compute UDP checksum using incremental update */
    cksum_update(&udp_hdr->dgram_cksum, old_ip_addr, *address);
    break;
}
default:
    break;
}
person Kaminek    schedule 15.09.2018

Вы должны вычесть старый IP-адрес и добавить новый в контрольную сумму udp, вот псевдокод:

udp_hdr->dgram_cksum -= old_ipv4_addr & 0xffff;
udp_hdr->dgram_cksum -= old_ipv4_addr >> 16 & 0xffff;
udp_hdr->dgram_cksum += new_ipv4_addr & 0xffff;
udp_hdr->dgram_cksum += new_ipv4_addr >>16 & 0xffff;

Это должно обрабатывать контрольную сумму UDP для фрагментов IP.

person Kaminek    schedule 16.07.2018
comment
Спасибо за попытку, но ваше решение не работает. Проблема в том, что не учитывается, как уменьшается контрольная сумма после сложения всех слов. Я добавил код модульного теста, если вы хотите проверить какие-либо другие идеи. Спасибо! - person Daniel Knueven; 19.07.2018
comment
В зависимости от размера/содержимого пакета этот подход может работать. Это просто не всегда работает. - person Daniel Knueven; 15.08.2018