The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* $Id: Fast.xs 26 2012-11-10 16:34:28Z gomor $ */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

//#define DEBUG  1

//#define TCP_LEN       24
//#define TCP_OPT_LEN    4
#define TCP_LEN       40
#define TCP_OPT_LEN   20
#define TCP_PHDR4_LEN 12
#define TCP_PHDR6_LEN 36

#define MAXERRBUF 1024

char nwf_errbuf[MAXERRBUF];
int  nwf_isrand = 0;

struct tcphdr {
   u_int16_t th_sport;
   u_int16_t th_dport;
   u_int32_t th_seq;
   u_int32_t th_ack;
   u_int8_t  th_x2:4, th_off:4;
   u_int8_t  th_flags;
   u_int16_t th_win;
   u_int16_t th_sum;
   u_int16_t th_urp;
};

struct ptcphdr4 {
   in_addr_t ip_src;
   in_addr_t ip_dst;
   u_int16_t ip_p;
   u_int16_t tcp_len;
   struct tcphdr tcp_hdr;
   u_int8_t tcp_opt[TCP_OPT_LEN];
};

struct ptcphdr6 {
   struct in6_addr ip_src;
   struct in6_addr ip_dst;
   u_int16_t ip_p;
   u_int16_t tcp_len;
   struct tcphdr tcp_hdr;
   u_int8_t tcp_opt[TCP_OPT_LEN];
};

u_int16_t
_nwf_csum(u_int16_t *buf, int nwords)
{
   u_int32_t sum;

   for (sum = 0; nwords > 0; nwords--) {
      sum += *buf++;
   }
   sum = (sum >> 16) + (sum & 0xffff);
   sum += (sum >> 16);

   return ~sum;
}

int
_nwf_socket(int v6, struct addrinfo *asrc)
{
   int r;
   int fd;

   fd = socket(v6 ? AF_INET6 : AF_INET, SOCK_RAW, IPPROTO_TCP);
   if (fd < 0) {
      memset(nwf_errbuf, 0, MAXERRBUF);
      snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_socket: %s", strerror(errno));
      return(0);
   }

   if (asrc != NULL) {
      r = bind(fd, (const struct sockaddr *)asrc->ai_addr, asrc->ai_addrlen);
      if (r < 0) {
         memset(nwf_errbuf, 0, MAXERRBUF);
         snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_socket: bind: %s", 
            strerror(errno));
         return(0);
      }
   }

   return(fd);
}

int
_nwf_inet_addr(const char *ip)
{
   in_addr_t a;

   a = inet_addr(ip);
   if (a == INADDR_NONE) {
      memset(nwf_errbuf, 0, MAXERRBUF);
      snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_inet_addr: %s for [%s]",
               strerror(errno), ip);
      return(0);
   }

   return(a);
}

int
_nwf_sendto(int sockfd, const void *buf, size_t len, int flags,
            const struct sockaddr *dest_addr, socklen_t addrlen,
            char *ip_dst)
{
   int r;

   r = sendto(sockfd, buf, len, flags, dest_addr, addrlen);
   if (r < 0) {
      memset(nwf_errbuf, 0, MAXERRBUF);
      snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_sendto: %s [to %s]",
               strerror(errno), ip_dst);
      return(0);
   }

   return(1);
}

void *
_nwf_malloc(size_t size)
{
   void *ptr;

   ptr = malloc(size);
   if (ptr == NULL) {
      memset(nwf_errbuf, 0, MAXERRBUF);
      snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_malloc: %s", strerror(errno));
      return(NULL);
   }

   return(ptr);
}

int
_nwf_getaddrinfo(const char *node, const char *service,
                 const struct addrinfo *hints, struct addrinfo **res)
{
   int r;

   r = getaddrinfo(node, service, hints, res);
   if (r < 0) {
      memset(nwf_errbuf, 0, MAXERRBUF);
      snprintf(nwf_errbuf, MAXERRBUF - 1, "_nwf_getaddrinfo: %s [%s]",
               gai_strerror(r), node);
      return(0);
   }

   return(1);
}

int
l4_send_tcp_syn_multi(char *ip_src, char **ip_dst, int ndst, int *ports,
                      int nports, int pps, int n, int v6)
{
   int r;
   int fd;
   int i;
   int j;
   int k;
   int nwords;
   struct addrinfo hints;
   struct addrinfo *asrc;
   struct addrinfo *adst;
   struct sockaddr_in  *ptr4;
   struct sockaddr_in6 *ptr6;
   u_int8_t datagram[TCP_LEN];
   u_int8_t *pdatagram;
#ifdef DEBUG
   time_t begin;
   time_t now;
#endif
   int count;
   int scount;
   int npackets = 0; // Total number of packets
   int runtime  = 0; // Estimated running time
   int every    = 0; // Sleep every number of packets
   int us       = 0; // Sleep time in us (minimum 10ms, per classic OS)
   struct ptcphdr4 *ptcph4;
   struct ptcphdr6 *ptcph6;
   struct tcphdr   *tcph;

   if (! v6) {
      pdatagram = (u_int8_t *)_nwf_malloc(TCP_LEN + TCP_PHDR4_LEN);
      if (pdatagram == NULL)
         return(0);
      ptcph4    = (struct ptcphdr4 *)pdatagram;
      tcph      = (struct tcphdr *) (pdatagram + TCP_PHDR4_LEN);
   }
   else {
      pdatagram = (u_int8_t *)_nwf_malloc(TCP_LEN + TCP_PHDR6_LEN);
      if (pdatagram == NULL)
         return(0);
      ptcph6    = (struct ptcphdr6 *)pdatagram;
      tcph      = (struct tcphdr *) (pdatagram + TCP_PHDR6_LEN);
   }

   if (nwf_isrand == 0) {
      srand(time(NULL));
      nwf_isrand++;
   }

   memset(&hints, 0, sizeof(hints));
   if (! v6) {
      hints.ai_family = AF_INET;
   }
   else {
      hints.ai_family = AF_INET6;
   }
   hints.ai_flags    = AI_NUMERICHOST;
   hints.ai_socktype = SOCK_RAW;
   hints.ai_protocol = IPPROTO_RAW;

   asrc = (struct addrinfo *)_nwf_malloc(sizeof(struct addrinfo));
   if (asrc == NULL) {
      free(pdatagram);
      return(0);
   }
   r = _nwf_getaddrinfo(ip_src, NULL, &hints, &asrc);
   if (r == 0) {
      freeaddrinfo(asrc);
      free(pdatagram);
      return(0);
   }

   memset(datagram, 0, TCP_LEN);
   if (! v6) {
      memset(pdatagram, 0, TCP_PHDR4_LEN + TCP_LEN);
   }
   else {
      memset(pdatagram, 0, TCP_PHDR6_LEN + TCP_LEN);
   }

   if (! v6) {
      ptr4 = (struct sockaddr_in *)asrc->ai_addr;
      memcpy(&(ptcph4->ip_src), &(ptr4->sin_addr), 4);
      ptcph4->ip_p             = ntohs(6);
      ptcph4->tcp_len          = ntohs(TCP_LEN);
      ptcph4->tcp_hdr.th_ack   = 0;
      ptcph4->tcp_hdr.th_x2    = 0;
      ptcph4->tcp_hdr.th_off   = TCP_LEN >> 2;
      ptcph4->tcp_hdr.th_flags = 0x02;
      ptcph4->tcp_hdr.th_win   = htons(5840);
      ptcph4->tcp_hdr.th_sum   = 0;
      ptcph4->tcp_hdr.th_urp   = 0;
      // MSS 1460
      //memcpy(ptcph4->tcp_opt, "\x02\x04\x05\xb4", TCP_OPT_LEN);
      memcpy(ptcph4->tcp_opt, "\x02\x04\x05\xb4\x08\x0a\x44\x45\x41\x44\x00\x00\x00\x00\x03\x03\x01\x04\x02\x00", TCP_OPT_LEN);
   }
   else {
      ptr6 = (struct sockaddr_in6 *)asrc->ai_addr;
      memcpy(&(ptcph6->ip_src), &(ptr6->sin6_addr), 16);
      ptcph6->ip_p             = ntohs(6);
      ptcph6->tcp_len          = ntohs(TCP_LEN);
      ptcph6->tcp_hdr.th_ack   = 0;
      ptcph6->tcp_hdr.th_x2    = 0;
      ptcph6->tcp_hdr.th_off   = TCP_LEN >> 2;
      ptcph6->tcp_hdr.th_flags = 0x02;
      ptcph6->tcp_hdr.th_win   = htons(5840);
      ptcph6->tcp_hdr.th_sum   = 0;
      ptcph6->tcp_hdr.th_urp   = 0;
      // MSS 1460
      //memcpy(ptcph6->tcp_opt, "\x02\x04\x05\xb4", TCP_OPT_LEN);
      memcpy(ptcph6->tcp_opt, "\x02\x04\x05\xb4\x08\x0a\x44\x45\x41\x44\x00\x00\x00\x00\x03\x03\x01\x04\x02\x00", TCP_OPT_LEN);
   }

   fd = _nwf_socket(v6, asrc);
   if (fd == 0) {
      freeaddrinfo(asrc);
      free(pdatagram);
      return(0);
   }

#ifdef DEBUG
   begin    = time(NULL);
#endif
   count    = 0;
   scount   = 0;
   npackets = nports * ndst * n;
   runtime  = npackets / pps;
   us       = 10000; // Minimum delay 10ms, per classic OS restiction
   every    = ((float)pps / (float)us) * 100.00;
#ifdef DEBUG
   fprintf(stderr,
      "Using usleep of %d every %d packets during a runtime of %d seconds\n",
           us, every, runtime);
#endif
   for (i=0; i<nports; i++) {
      if (! v6) {
         ptcph4->tcp_hdr.th_sport = htons(random());
         ptcph4->tcp_hdr.th_dport = htons(ports[i]);
         ptcph4->tcp_hdr.th_seq   = random();
      }
      else {
         ptcph6->tcp_hdr.th_sport = htons(random());
         ptcph6->tcp_hdr.th_dport = htons(ports[i]);
         ptcph6->tcp_hdr.th_seq   = random();
      }

      for (j=0; j<ndst; j++) {
         //printf("Target [%s]:%d [ipv6:%d]\n", ip_dst[j], ports[i], v6);

         adst = (struct addrinfo *)_nwf_malloc(sizeof(struct addrinfo));
         if (adst == NULL) {
            freeaddrinfo(asrc);
            free(pdatagram);
            return(0);
         }
         r = _nwf_getaddrinfo(ip_dst[j], NULL, &hints, &adst);
         if (r == 0) {
            freeaddrinfo(asrc);
            freeaddrinfo(adst);
            free(pdatagram);
            return(0);
         }

         if (! v6) {
            ptr4 = (struct sockaddr_in *)adst->ai_addr;
            memcpy(&(ptcph4->ip_dst), &(ptr4->sin_addr), sizeof(in_addr_t));
            // Compute checksums
            nwords                 = (TCP_LEN + TCP_PHDR4_LEN) * 8 / 16;
            ptcph4->tcp_hdr.th_sum = _nwf_csum((u_int16_t *)ptcph4, nwords);
         }
         else {
            ptr6 = (struct sockaddr_in6 *)adst->ai_addr;
            memcpy(&(ptcph6->ip_dst), &(ptr6->sin6_addr), 16);
            // Compute checksums
            nwords                 = (TCP_LEN + TCP_PHDR6_LEN) * 8 / 16;
            ptcph6->tcp_hdr.th_sum = _nwf_csum((u_int16_t *)ptcph6, nwords);
         }

         memcpy(datagram, tcph, TCP_LEN);

         for (k=0; k<n; k++) {
            r = _nwf_sendto(fd, (u_int8_t *)datagram, TCP_LEN, 0,
                            adst->ai_addr, adst->ai_addrlen,
                            ip_dst[j]);
            if (r == 0) {
#ifdef DEBUG
               fprintf(stderr, "WARNING: %s\n", nwf_errbuf);
#endif
               freeaddrinfo(adst);
               continue;
            }
            count++;
            scount++;

            // Sleep every X packet
            if (scount > every) {
               usleep(us);
               scount = 0;
            }

#ifdef DEBUG
            // Print stats and reset count
            now = time(NULL);
            if (now - begin >= 1) {
               fprintf(stderr, "Sent %d pps (i/o %d pps), time to sleep %d us (total: %d)\n",
                       count, pps, us, npackets);
               begin = time(NULL);
               count = 0;
            }
#endif
         }

         // Reset checksum for next round
         if (! v6) {
            ptcph4->tcp_hdr.th_sum = 0;
         }
         else {
            ptcph6->tcp_hdr.th_sum = 0;
         }
         freeaddrinfo(adst);
      }
   }
   freeaddrinfo(asrc);
   free(pdatagram);

   close(fd);

   return(1);
}

char *
nwf_geterror(void)
{
   return((char *)nwf_errbuf);
}

MODULE = Net::Write::Fast  PACKAGE = Net::Write::Fast
PROTOTYPES: DISABLE

int
l4_send_tcp_syn_multi(src, dst, ports, pps, n, v6)
      char *src
      SV   *dst
      SV   *ports
      int   pps
      int   n
      int   v6
   PREINIT:
      if (!SvROK(ports) || SvTYPE((SV *)SvRV(ports)) != SVt_PVAV) {
         croak("Argument ports shall be an ARRAYREF");
      }
      if (!SvROK(dst) || SvTYPE((SV *)SvRV(dst)) != SVt_PVAV) {
         croak("Argument dst shall be an ARRAYREF");
      }
   INIT:
      int i;
      AV *p = (AV *)SvRV(ports);
      AV *d = (AV *)SvRV(dst);
      int plen = av_len(p) + 1;
      int dlen = av_len(d) + 1;
      int *cports;
      char *targets[dlen];
      Newx(cports, plen, int);
   CODE:
      for (i=0; i<plen; i++) {
         SV **e = av_fetch(p, i, 0);
         cports[i] = SvIV(*e);
      }
      for (i=0; i<dlen; i++) {
         STRLEN l;
         targets[i] = SvPV(*av_fetch(d, i, 0), l);
      }
      RETVAL = l4_send_tcp_syn_multi(src, targets, dlen, cports, plen, pps, n,
                                     v6);
   OUTPUT:
      RETVAL

char *
nwf_geterror()