Subversion Repositories HomeAutomation

Rev

Rev 213 | Blame | Compare with Previous | Last modification | View Log | SVN | RSS feed

  1. /*********************************************
  2.  * vim:sw=8:ts=8:si:et
  3.  * To use the above modeline in vim you must have "set modeline" in your .vimrc
  4.  *
  5.  * Author: Guido Socher
  6.  * Copyright: GPL V2
  7.  * See http://www.gnu.org/licenses/gpl.html
  8.  *
  9.  * IP, Arp, UDP and TCP functions.
  10.  *
  11.  * The TCP implementation uses some size optimisations which are valid
  12.  * only if all data can be sent in one single packet. This is however
  13.  * not a big limitation for a microcontroller as you will anyhow use
  14.  * small web-pages. The TCP stack is therefore a SDP-TCP stack (single data packet TCP).
  15.  *
  16.  * Chip type           : ATMEGA88 with ENC28J60
  17.  *********************************************/
  18. #include <avr/io.h>
  19. #include <avr/pgmspace.h>
  20. #include "avr_compat.h"
  21. #include "net.h"
  22. #include "enc28j60.h"
  23. #include "ip_arp_udp_tcp.h"
  24.  
  25. static uint8_t wwwport=80;
  26. static uint8_t macaddr[6];
  27. static uint8_t ipaddr[4];
  28.  
  29. #ifdef useTCP
  30. static int16_t info_hdr_len=0;
  31. static int16_t info_data_len=0;
  32. static uint8_t seqnum=0xa; // my initial tcp sequence number
  33. #endif //useTCP
  34.  
  35. // The Ip checksum is calculated over the ip header only starting
  36. // with the header length field and a total length of 20 bytes
  37. // unitl ip.dst
  38. // You must set the IP checksum field to zero before you start
  39. // the calculation.
  40. // len for ip is 20.
  41. //
  42. // For UDP/TCP we do not make up the required pseudo header. Instead we
  43. // use the ip.src and ip.dst fields of the real packet:
  44. // The udp checksum calculation starts with the ip.src field
  45. // Ip.src=4bytes,Ip.dst=4 bytes,Udp header=8bytes + data length=16+len
  46. // In other words the len here is 8 + length over which you actually
  47. // want to calculate the checksum.
  48. // You must set the checksum field to zero before you start
  49. // the calculation.
  50. // len for udp is: 8 + 8 + data length
  51. // len for tcp is: 4+4 + 20 + option len + data length
  52. //
  53. // For more information on how this algorithm works see:
  54. // http://www.netfor2.com/checksum.html
  55. // http://www.msc.uky.edu/ken/cs471/notes/chap3.htm
  56. // The RFC has also a C code example: http://www.faqs.org/rfcs/rfc1071.html
  57. uint16_t checksum(uint8_t *buf, uint16_t len,uint8_t type){
  58.         // type 0=ip
  59.         //      1=udp
  60.         //      2=tcp
  61.         uint32_t sum = 0;
  62.  
  63.         //if(type==0){
  64.         //        // do not add anything
  65.         //}
  66.         if(type==1){
  67.                 sum+=IP_PROTO_UDP_V; // protocol udp
  68.                 // the length here is the length of udp (data+header len)
  69.                 // =length given to this function - (IP.scr+IP.dst length)
  70.                 sum+=len-8; // = real tcp len
  71.         }
  72.         if(type==2){
  73.                 sum+=IP_PROTO_TCP_V;
  74.                 // the length here is the length of tcp (data+header len)
  75.                 // =length given to this function - (IP.scr+IP.dst length)
  76.                 sum+=len-8; // = real tcp len
  77.         }
  78.         // build the sum of 16bit words
  79.         while(len >1){
  80.                 sum += 0xFFFF & (*buf<<8|*(buf+1));
  81.                 buf+=2;
  82.                 len-=2;
  83.         }
  84.         // if there is a byte left then add it (padded with zero)
  85.         if (len){
  86.                 sum += (0xFF & *buf)<<8;
  87.         }
  88.         // now calculate the sum over the bytes in the sum
  89.         // until the result is only 16bit long
  90.         while (sum>>16){
  91.                 sum = (sum & 0xFFFF)+(sum >> 16);
  92.         }
  93.         // build 1's complement:
  94.         return( (uint16_t) sum ^ 0xFFFF);
  95. }
  96.  
  97. // you must call this function once before you use any of the other functions:
  98. void init_ip_arp_udp_tcp(uint8_t *mymac,uint8_t *myip,uint8_t wwwp){
  99.         uint8_t i=0;
  100.         wwwport=wwwp;
  101.         while(i<4){
  102.                 ipaddr[i]=myip[i];
  103.                 i++;
  104.         }
  105.         i=0;
  106.         while(i<6){
  107.                 macaddr[i]=mymac[i];
  108.                 i++;
  109.         }
  110. }
  111.  
  112. uint8_t eth_type_is_arp_and_my_ip(uint8_t *buf,uint16_t len){
  113.         uint8_t i=0;
  114.         //  
  115.         if (len<41){
  116.                 return(0);
  117.         }
  118.         if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V ||
  119.            buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V){
  120.                 return(0);
  121.         }
  122.         while(i<4){
  123.                 if(buf[ETH_ARP_DST_IP_P+i] != ipaddr[i]){
  124.                         return(0);
  125.                 }
  126.                 i++;
  127.         }
  128.         return(1);
  129. }
  130.  
  131. uint8_t eth_type_is_ip_and_my_ip(uint8_t *buf,uint16_t len){
  132.         uint8_t i=0;
  133.         //eth+ip+udp header is 42
  134.         if (len<42){
  135.                 return(0);
  136.         }
  137.         if(buf[ETH_TYPE_H_P]!=ETHTYPE_IP_H_V ||
  138.            buf[ETH_TYPE_L_P]!=ETHTYPE_IP_L_V){
  139.                 return(0);
  140.         }
  141.         if (buf[IP_HEADER_LEN_VER_P]!=0x45){
  142.                 // must be IP V4 and 20 byte header
  143.                 return(0);
  144.         }
  145.         while(i<4){
  146.                 if(buf[IP_DST_P+i]!=ipaddr[i]){
  147.                         return(0);
  148.                 }
  149.                 i++;
  150.         }
  151.         return(1);
  152. }
  153. // make a return eth header from a received eth packet
  154. void make_eth(uint8_t *buf)
  155. {
  156.         uint8_t i=0;
  157.         //
  158.         //copy the destination mac from the source and fill my mac into src
  159.         while(i<6){
  160.                 buf[ETH_DST_MAC +i]=buf[ETH_SRC_MAC +i];
  161.                 buf[ETH_SRC_MAC +i]=macaddr[i];
  162.                 i++;
  163.         }
  164. }
  165. void fill_ip_hdr_checksum(uint8_t *buf)
  166. {      
  167.         //bits 0-3 = version (ipv4 => 4), bits 4-7 = header length (minimum 5 32bit fields => 5)
  168.         buf[IP_HEADER_LEN_VER_P]=0x45;
  169.         uint16_t ck;
  170.         // clear the 2 byte checksum
  171.         buf[IP_CHECKSUM_P]=0;
  172.         buf[IP_CHECKSUM_P+1]=0;
  173.         buf[IP_FLAGS_P]=0x40; // don't fragment
  174.         buf[IP_FLAGS_P+1]=0;  // fragement offset
  175.         buf[IP_TTL_P]=64; // ttl
  176.         // calculate the checksum:
  177.         ck=checksum(&buf[IP_P], IP_HEADER_LEN,0);
  178.         buf[IP_CHECKSUM_P]=ck>>8;
  179.         buf[IP_CHECKSUM_P+1]=ck& 0xff;
  180. }
  181.  
  182. // make a return ip header from a received ip packet
  183. void make_ip(uint8_t *buf)
  184. {
  185.         uint8_t i=0;
  186.         while(i<4){
  187.                 buf[IP_DST_P+i]=buf[IP_SRC_P+i];
  188.                 buf[IP_SRC_P+i]=ipaddr[i];
  189.                 i++;
  190.         }
  191.         fill_ip_hdr_checksum(buf);
  192. }
  193.  
  194. void make_arp_answer_from_request(uint8_t *buf)
  195. {
  196.         uint8_t i=0;
  197.         //
  198.         make_eth(buf);
  199.         buf[ETH_ARP_OPCODE_H_P]=ETH_ARP_OPCODE_REPLY_H_V;
  200.         buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V;
  201.         // fill the mac addresses:
  202.         while(i<6){
  203.                 buf[ETH_ARP_DST_MAC_P+i]=buf[ETH_ARP_SRC_MAC_P+i];
  204.                 buf[ETH_ARP_SRC_MAC_P+i]=macaddr[i];
  205.                 i++;
  206.         }
  207.         i=0;
  208.         while(i<4){
  209.                 buf[ETH_ARP_DST_IP_P+i]=buf[ETH_ARP_SRC_IP_P+i];
  210.                 buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i];
  211.                 i++;
  212.         }
  213.         // eth+arp is 42 bytes:
  214.         enc28j60PacketSend(42,buf);
  215. }
  216.  
  217. void make_echo_reply_from_request(uint8_t *buf,uint16_t len)
  218. {
  219.         make_eth(buf);
  220.         make_ip(buf);
  221.         buf[ICMP_TYPE_P]=ICMP_TYPE_ECHOREPLY_V;
  222.         // we changed only the icmp.type field from request(=8) to reply(=0).
  223.         // we can therefore easily correct the checksum:
  224.         if (buf[ICMP_CHECKSUM_P] > (0xff-0x08)){
  225.                 buf[ICMP_CHECKSUM_P+1]++;
  226.         }
  227.         buf[ICMP_CHECKSUM_P]+=0x08;
  228.         //
  229.         enc28j60PacketSend(len,buf);
  230. }
  231.  
  232. #ifdef useUDP
  233. // you can send a max of 220 bytes of data
  234. void make_udp_reply_from_request(uint8_t *buf,char *data,uint8_t datalen,uint16_t port)
  235. {
  236.         uint8_t i=0;
  237.         uint16_t ck;
  238.         make_eth(buf);
  239.         if (datalen>220){
  240.                 datalen=220;
  241.         }
  242.         // total length field in the IP header must be set:
  243.         buf[IP_TOTLEN_H_P]=0;
  244.         buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
  245.         make_ip(buf);
  246.         buf[UDP_DST_PORT_H_P]=port>>8;
  247.         buf[UDP_DST_PORT_L_P]=port & 0xff;
  248.         // source port does not matter and is what the sender used.
  249.         // calculte the udp length:
  250.         buf[UDP_LEN_H_P]=0;
  251.         buf[UDP_LEN_L_P]=UDP_HEADER_LEN+datalen;
  252.         // zero the checksum
  253.         buf[UDP_CHECKSUM_H_P]=0;
  254.         buf[UDP_CHECKSUM_L_P]=0;
  255.         // copy the data:
  256.         while(i<datalen){
  257.                 buf[UDP_DATA_P+i]=data[i];
  258.                 i++;
  259.         }
  260.         ck=checksum(&buf[IP_SRC_P], 16 + datalen,1);
  261.         buf[UDP_CHECKSUM_H_P]=ck>>8;
  262.         buf[UDP_CHECKSUM_L_P]=ck& 0xff;
  263.         enc28j60PacketSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen,buf);
  264. }
  265.  
  266. void send_udp(uint8_t *buf,char *data,uint8_t datalen,uint16_t port, uint8_t *remotemac, uint8_t *remoteip)
  267. {
  268.  
  269.         uint8_t i=0;
  270.         uint16_t ck;
  271.         //make_eth(buf);
  272.         while(i<6){
  273.             buf[ETH_DST_MAC +i]=remotemac[i];
  274.             buf[ETH_SRC_MAC +i]=macaddr[i];
  275.             i++;
  276.         }
  277.         i=0;
  278.        
  279.         buf[ETH_TYPE_H_P] = 0x08;
  280.         buf[ETH_TYPE_L_P] = 0x00;
  281.  
  282.         if (datalen>220){
  283.                 datalen=220;
  284.         }
  285.         //set protocol
  286.         buf[IP_PROTO_P]=IP_PROTO_UDP_V;
  287.         // total length field in the IP header must be set:
  288.         buf[IP_TOTLEN_H_P]=0;
  289.         buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
  290.         while(i<4){
  291.             buf[IP_DST_P+i]=remoteip[i];
  292.             buf[IP_SRC_P+i]=ipaddr[i];
  293.             i++;
  294.         }
  295.         i=0;
  296.         fill_ip_hdr_checksum(buf);
  297.         //make_ip(buf);
  298.         buf[UDP_DST_PORT_H_P]=port>>8;
  299.         buf[UDP_DST_PORT_L_P]=port & 0xff;
  300.         // source port does not matter and is what the sender used.
  301.         // calculte the udp length:
  302.         buf[UDP_LEN_H_P]=0;
  303.         buf[UDP_LEN_L_P]=UDP_HEADER_LEN+datalen;
  304.         // zero the checksum
  305.         buf[UDP_CHECKSUM_H_P]=0;
  306.         buf[UDP_CHECKSUM_L_P]=0;
  307.         // copy the data:
  308.         while(i<datalen){
  309.                 buf[UDP_DATA_P+i]=data[i];
  310.                 i++;
  311.         }
  312.         ck=checksum(&buf[IP_SRC_P], 16 + datalen,1);
  313.         buf[UDP_CHECKSUM_H_P]=ck>>8;
  314.         buf[UDP_CHECKSUM_L_P]=ck& 0xff;
  315.         enc28j60PacketSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen,buf);
  316. }
  317. #endif //useUDP
  318.  
  319. #ifdef useTCP
  320. // make a return tcp header from a received tcp packet
  321. // rel_ack_num is how much we must step the seq number received from the
  322. // other side. We do not send more than 255 bytes of text (=data) in the tcp packet.
  323. // If mss=1 then mss is included in the options list
  324. //
  325. // After calling this function you can fill in the first data byte at TCP_OPTIONS_P+4
  326. // If cp_seq=0 then an initial sequence number is used (should be use in synack)
  327. // otherwise it is copied from the packet we received
  328. void make_tcphead(uint8_t *buf,uint16_t rel_ack_num,uint8_t mss,uint8_t cp_seq)
  329. {
  330.         uint8_t i=0;
  331.         uint8_t tseq;
  332.         while(i<2){
  333.                 buf[TCP_DST_PORT_H_P+i]=buf[TCP_SRC_PORT_H_P+i];
  334.                 buf[TCP_SRC_PORT_H_P+i]=0; // clear source port
  335.                 i++;
  336.         }
  337.         // set source port  (http):
  338.         buf[TCP_SRC_PORT_L_P]=wwwport;
  339.         i=4;
  340.         // sequence numbers:
  341.         // add the rel ack num to SEQACK
  342.         while(i>0){
  343.                 rel_ack_num=buf[TCP_SEQ_H_P+i-1]+rel_ack_num;
  344.                 tseq=buf[TCP_SEQACK_H_P+i-1];
  345.                 buf[TCP_SEQACK_H_P+i-1]=0xff&rel_ack_num;
  346.                 if (cp_seq){
  347.                         // copy the acknum sent to us into the sequence number
  348.                         buf[TCP_SEQ_H_P+i-1]=tseq;
  349.                 }else{
  350.                         buf[TCP_SEQ_H_P+i-1]= 0; // some preset vallue
  351.                 }
  352.                 rel_ack_num=rel_ack_num>>8;
  353.                 i--;
  354.         }
  355.         if (cp_seq==0){
  356.                 // put inital seq number
  357.                 buf[TCP_SEQ_H_P+0]= 0;
  358.                 buf[TCP_SEQ_H_P+1]= 0;
  359.                 // we step only the second byte, this allows us to send packts
  360.                 // with 255 bytes or 512 (if we step the initial seqnum by 2)
  361.                 buf[TCP_SEQ_H_P+2]= seqnum;
  362.                 buf[TCP_SEQ_H_P+3]= 0;
  363.                 // step the inititial seq num by something we will not use
  364.                 // during this tcp session:
  365.                 seqnum+=2;
  366.         }
  367.         // zero the checksum
  368.         buf[TCP_CHECKSUM_H_P]=0;
  369.         buf[TCP_CHECKSUM_L_P]=0;
  370.  
  371.         // The tcp header length is only a 4 bit field (the upper 4 bits).
  372.         // It is calculated in units of 4 bytes.
  373.         // E.g 24 bytes: 24/4=6 => 0x60=header len field
  374.         //buf[TCP_HEADER_LEN_P]=(((TCP_HEADER_LEN_PLAIN+4)/4)) <<4; // 0x60
  375.         if (mss){
  376.                 // the only option we set is MSS to 1408:
  377.                 // 1408 in hex is 0x580
  378.                 buf[TCP_OPTIONS_P]=2;
  379.                 buf[TCP_OPTIONS_P+1]=4;
  380.                 buf[TCP_OPTIONS_P+2]=0x05;
  381.                 buf[TCP_OPTIONS_P+3]=0x80;
  382.                 // 24 bytes:
  383.                 buf[TCP_HEADER_LEN_P]=0x60;
  384.         }else{
  385.                 // no options:
  386.                 // 20 bytes:
  387.                 buf[TCP_HEADER_LEN_P]=0x50;
  388.         }
  389. }
  390.  
  391. void make_tcp_synack_from_syn(uint8_t *buf)
  392. {
  393.         uint16_t ck;
  394.         make_eth(buf);
  395.         // total length field in the IP header must be set:
  396.         // 20 bytes IP + 24 bytes (20tcp+4tcp options)
  397.         buf[IP_TOTLEN_H_P]=0;
  398.         buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4;
  399.         make_ip(buf);
  400.         buf[TCP_FLAGS_P]=TCP_FLAGS_SYNACK_V;
  401.         make_tcphead(buf,1,1,0);
  402.         // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + 4 (one option: mss)
  403.         ck=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+4,2);
  404.         buf[TCP_CHECKSUM_H_P]=ck>>8;
  405.         buf[TCP_CHECKSUM_L_P]=ck& 0xff;
  406.         // add 4 for option mss:
  407.         enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4+ETH_HEADER_LEN,buf);
  408. }
  409.  
  410. // get a pointer to the start of tcp data in buf
  411. // Returns 0 if there is no data
  412. // You must call init_len_info once before calling this function
  413. uint16_t get_tcp_data_pointer(void)
  414. {
  415.         if (info_data_len){
  416.                 return((uint16_t)TCP_SRC_PORT_H_P+info_hdr_len);
  417.         }else{
  418.                 return(0);
  419.         }
  420. }
  421.  
  422. // do some basic length calculations and store the result in static varibales
  423. void init_len_info(uint8_t *buf)
  424. {
  425.         info_data_len=(buf[IP_TOTLEN_H_P]<<8)|(buf[IP_TOTLEN_L_P]&0xff);
  426.         info_data_len-=IP_HEADER_LEN;
  427.         info_hdr_len=(buf[TCP_HEADER_LEN_P]>>4)*4; // generate len in bytes;
  428.         info_data_len-=info_hdr_len;
  429.         if (info_data_len<=0){
  430.                 info_data_len=0;
  431.         }
  432. }
  433.  
  434. // fill in tcp data at position pos. pos=0 means start of
  435. // tcp data. Returns the position at which the string after
  436. // this string could be filled.
  437. uint16_t fill_tcp_data_p(uint8_t *buf,uint16_t pos, const prog_char *progmem_s)
  438. {
  439.         char c;
  440.         // fill in tcp data at position pos
  441.         //
  442.         // with no options the data starts after the checksum + 2 more bytes (urgent ptr)
  443.         while ((c = pgm_read_byte(progmem_s++))) {
  444.                 buf[TCP_CHECKSUM_L_P+3+pos]=c;
  445.                 pos++;
  446.         }
  447.         return(pos);
  448. }
  449.  
  450. // fill in tcp data at position pos. pos=0 means start of
  451. // tcp data. Returns the position at which the string after
  452. // this string could be filled.
  453. uint16_t fill_tcp_data(uint8_t *buf,uint16_t pos, const char *s)
  454. {
  455.         // fill in tcp data at position pos
  456.         //
  457.         // with no options the data starts after the checksum + 2 more bytes (urgent ptr)
  458.         while (*s) {
  459.                 buf[TCP_CHECKSUM_L_P+3+pos]=*s;
  460.                 pos++;
  461.                 s++;
  462.         }
  463.         return(pos);
  464. }
  465.  
  466. // Make just an ack packet with no tcp data inside
  467. // This will modify the eth/ip/tcp header
  468. void make_tcp_ack_from_any(uint8_t *buf)
  469. {
  470.         uint16_t j;
  471.         make_eth(buf);
  472.         // fill the header:
  473.         buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V;
  474.         if (info_data_len==0){
  475.                 // if there is no data then we must still acknoledge one packet
  476.                 make_tcphead(buf,1,0,1); // no options
  477.         }else{
  478.                 make_tcphead(buf,info_data_len,0,1); // no options
  479.         }
  480.  
  481.         // total length field in the IP header must be set:
  482.         // 20 bytes IP + 20 bytes tcp (when no options)
  483.         j=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN;
  484.         buf[IP_TOTLEN_H_P]=j>>8;
  485.         buf[IP_TOTLEN_L_P]=j& 0xff;
  486.         make_ip(buf);
  487.         // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len
  488.         j=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN,2);
  489.         buf[TCP_CHECKSUM_H_P]=j>>8;
  490.         buf[TCP_CHECKSUM_L_P]=j& 0xff;
  491.         enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN,buf);
  492. }
  493.  
  494. // you must have called init_len_info at some time before calling this function
  495. // dlen is the amount of tcp data (http data) we send in this packet
  496. // You can use this function only immediately after make_tcp_ack_from_any
  497. // This is because this function will NOT modify the eth/ip/tcp header except for
  498. // length and checksum
  499. void make_tcp_ack_with_data(uint8_t *buf,uint16_t dlen)
  500. {
  501.         uint16_t j;
  502.         // fill the header:
  503.         // This code requires that we send only one data packet
  504.         // because we keep no state information. We must therefore set
  505.         // the fin here:
  506.         buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V|TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V;
  507.  
  508.         // total length field in the IP header must be set:
  509.         // 20 bytes IP + 20 bytes tcp (when no options) + len of data
  510.         j=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen;
  511.         buf[IP_TOTLEN_H_P]=j>>8;
  512.         buf[IP_TOTLEN_L_P]=j& 0xff;
  513.         fill_ip_hdr_checksum(buf);
  514.         // zero the checksum
  515.         buf[TCP_CHECKSUM_H_P]=0;
  516.         buf[TCP_CHECKSUM_L_P]=0;
  517.         // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len
  518.         j=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+dlen,2);
  519.         buf[TCP_CHECKSUM_H_P]=j>>8;
  520.         buf[TCP_CHECKSUM_L_P]=j& 0xff;
  521.         enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen+ETH_HEADER_LEN,buf);
  522. }
  523. #endif //useTCP
  524.  
  525. /* end of ip_arp_udp.c */
  526.