On Tuesday, October 17, 2006 at 3:21 AM, Jimmy Mäkelä | Loopia AB wrote:

> We are setting up a cluster of clamd-servers which will be load-balanced
> by a TCP-loadbalancer in front of the servers.
> 
> We want all clamdscan-clients to connect to the load-balancers IP and
> the connections to be evenly distributed across the cluster.
> 
> Since we use stream-scanning and the clamd STREAM-command responds with
> only a port to which clamdscan then tries to connect for sending the
> data, this setup means that clamdscan will try to connect to the
> selected port on the load-balancer instead of on the selected server..
> 
> We want to avoid having to use different port-ranges for the
> clamd-servers in the cluster and mapping them on the load-balancer to
> the corresponding server, so instead we have created a patch which makes
> clamd implement a new command called "NEWSTREAM" to which it responds
> with "PORT portnumber ip_of_control_connection" instead of just "PORT
> portnumber".
> 
> With this patch clamdscan first connects to the load-balancer which
> selects a clamd-server to forward the connection to, then use NEWSTREAM
> for scanning, and connects directly to the chosen port on the selected
> server for sending the stream (since the stream should always go to the
> same server as the main clamd-connection). This lets us have the same
> clamd-config on all clamd-servers in the cluster (and on all clamdscan
> clients as well).
> 
> The reason for using a new command instead of just changing STREAM is to
> allow old clamdscan-clients to connect to the new clamd without getting
> confused by the additional info in the PORT-response, but the patch
> could easily be modified to instead change STREAM if you consider that a
> better solution.
> 
> What do you guys think of this method? Would it be possible to get the
> patch included in some future version of clamav (I'm more than happy to
> do any changes deemed neccesary for that to happen)?
> 
> The patch is attached, and any comments would be greatly appreciated.

This is an interesting idea.

Clearly your suggestion works for your load balancing technology, but it may 
not work for others, since the target IP addresses may not be reachable from 
the client side.

Meanwhile....   A month ago, I proposed to Tomasz Kojm the addition of a 
command which I called "CHUNKEDSTREAM", which would also solve your problem.  
This command passes all the data to be scanned on the same TCP socket as the 
clamd command and its response.  The data is passed in size prefixed chunks 
terminated with a zero sized chunk.

Your use case adds another reason to my list as to why it would be a good idea 
to add:

I've occasionally had issues with: 

   1) firewall configuration protecting the clamd host, 
   2) issues tracing the network traffic relating to a Clamd stream request
 
This approach has the following advantages:

   1) easier to manage a firewall configuration (no extra ports to transport 
STREAM data)
   2) easy to trace with network sniffing to debug since all traffic uses the 
same TCP socket
   3) uses fewer kernel resources (no extra socket along with listen() and 
accept() calls)
   4) has slightly simpler client implementation due to fewer networking calls.

The attached patch can be applied against the current CVS or clamav-0.90RC1 to 
provide CHUNKEDSTREAM functionality.

- Mark Pizzolato

diff -c -r -X excluding.txt clamav-devel-20060917/clamd/scanner.c 
clamav-devel-20060917.chunked-stream/clamd/scanner.c
*** clamav-devel-20060917/clamd/scanner.c       Wed Sep 13 18:30:05 2006
--- clamav-devel-20060917.chunked-stream/clamd/scanner.c        Wed Sep 20 
11:56:57 2006
***************
*** 490,492 ****
--- 490,618 ----
  
      return ret;
  }
+ 
+ int scanchunkedstream(int odesc, unsigned long int *scanned, const struct 
cl_node *root, const struct cl_limits *limits, int options, const struct 
cfgstruct *copt)
+ {
+       int ret;
+       int tmpd, bread, bwrite, bwriting, retval = 1, timeout, btread, btdata;
+       long int size = 0, maxsize = 0;
+       const char *virname;
+       char buff[FILEBUFF];
+       struct cfgstruct *cpt;
+       char *tmpname;
+ 
+     timeout = cfgopt(copt, "ReadTimeout")->numarg;
+     if(timeout == 0)
+       timeout = -1;
+ 
+     maxsize = cfgopt(copt, "StreamMaxLength")->numarg;
+ 
+     if(mdprintf(odesc, "%d bytes of data will be scanned.  PROCEED, send 
counted chunks, end with a 0 sized chunk\n", maxsize) <= 0) {
+       logg("!ScanStream: error transmitting proceed.\n");
+       return -1;
+     }
+ 
+     logg("*Scanning Chunked Stream\n");
+ 
+     if ((tmpname = cli_gentempdesc(NULL, &tmpd)) == NULL) {
+       mdprintf(odesc, "tempfile() failed. ERROR\n");
+       logg("!ScanStream: Can't create temporary file.\n");
+       return -1;
+     }
+ 
+     bwriting = 1;
+ 
+     while (1) {
+       btread = readsock(odesc, buff, sizeof(buff)-1, '\n', timeout, 1, 0);
+       if (btread <= 0) {
+           mdprintf(odesc, "read data size failed. ERROR\n");
+           logg("!ScanStream: Can't read data size.\n");
+           close(tmpd);
+           if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
+               unlink(tmpname);
+           free(tmpname);
+           return -1;
+       }
+         buff[btread] = '\0';
+       cli_chomp(buff);
+       btdata = atoi(buff);
+       if (0 == btdata)
+           break;
+ 
+       while((retval = poll_fd(odesc, timeout)) == 1) {
+           btread = sizeof(buff);
+           if (btread > btdata)
+               btread = btdata;
+           bread = recv(odesc, buff, btread, 0);
+           if(bread <= 0) {
+               mdprintf(odesc, "read stream data failed. ERROR\n");
+               logg("!ScanStream: Can't read stream data.\n");
+               close(tmpd);
+               if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
+                   unlink(tmpname);
+               free(tmpname);
+               return -1;
+           }
+           size += bread;
+           btdata -= bread;
+           if (bwriting) {
+               bwrite = bread;
+               if(maxsize < size) {
+                   bwrite = maxsize-(size-bread);
+                   bwriting = 0;
+                   logg("^ScanStream: Size limit reached (max: %d)\n", 
maxsize);
+               }
+               if(writen(tmpd, buff, bwrite) != bwrite) {
+                   mdprintf(odesc, "Temporary file -> write ERROR\n");
+                   logg("!ScanStream: Can't write to temporary file.\n");
+                   close(tmpd);
+                   if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
+                       unlink(tmpname);
+                   free(tmpname);
+                   return -1;
+               }
+           }
+           if (btdata == 0)
+               break;
+       }
+ 
+       switch(retval) {
+           case 0: /* timeout */
+               mdprintf(odesc, "read timeout ERROR\n");
+               logg("!ScanStream: read timeout.\n");
+               break;
+           case -1:
+               mdprintf(odesc, "read poll ERROR\n");
+               logg("!ScanStream: read poll failed.\n");
+               break;
+       }
+     }
+ 
+     if(retval == 1) {
+       lseek(tmpd, 0, SEEK_SET);
+       ret = cl_scandesc(tmpd, &virname, scanned, root, limits, options);
+     } else {
+       ret = -1;
+     }
+     close(tmpd);
+     if(!cfgopt(copt, "LeaveTemporaryFiles")->enabled)
+       unlink(tmpname);
+     free(tmpname);
+ 
+     if(ret == CL_VIRUS) {
+       mdprintf(odesc, "stream: %s FOUND\n", virname);
+       logg("stream: %s FOUND\n", virname);
+       virusaction("stream", virname, copt);
+     } else if(ret != CL_CLEAN) {
+       if(retval == 1) {
+           mdprintf(odesc, "stream: %s ERROR\n", cl_strerror(ret));
+           logg("stream: %s ERROR\n", cl_strerror(ret));
+       }
+     } else {
+       mdprintf(odesc, "stream: OK\n");
+         if(logok)
+           logg("stream: OK\n"); 
+     }
+ 
+     return ret;
+ }
diff -c -r -X excluding.txt clamav-devel-20060917/clamd/scanner.h 
clamav-devel-20060917.chunked-stream/clamd/scanner.h
*** clamav-devel-20060917/clamd/scanner.h       Mon Sep 11 18:30:04 2006
--- clamav-devel-20060917.chunked-stream/clamd/scanner.h        Sun Sep 17 
08:01:14 2006
***************
*** 33,36 ****
--- 33,38 ----
  
  int checksymlink(const char *path);
  
+ int scanchunkedstream(int odesc, unsigned long int *scanned, const struct 
cl_node *root, const struct cl_limits *limits, int options, const struct 
cfgstruct *copt);
+ 
  #endif
diff -c -r -X excluding.txt clamav-devel-20060917/clamd/session.c 
clamav-devel-20060917.chunked-stream/clamd/session.c
*** clamav-devel-20060917/clamd/session.c       Wed Sep 13 18:30:05 2006
--- clamav-devel-20060917.chunked-stream/clamd/session.c        Sun Sep 17 
08:07:51 2006
***************
*** 360,365 ****
--- 360,370 ----
            }
        }
  
+     } else if(!strncmp(buff, CMD14, strlen(CMD14))) { /* CHUNKEDSTREAM */
+       if(scanchunkedstream(desc, NULL, root, limits, options, copt) == 
CL_EMEM)
+           if(cfgopt(copt, "ExitOnOOM")->enabled)
+               return COMMAND_SHUTDOWN;
+ 
      } else {
        mdprintf(desc, "UNKNOWN COMMAND\n");
      }
diff -c -r -X excluding.txt clamav-devel-20060917/clamd/session.h 
clamav-devel-20060917.chunked-stream/clamd/session.h
*** clamav-devel-20060917/clamd/session.h       Mon Sep 11 18:30:05 2006
--- clamav-devel-20060917.chunked-stream/clamd/session.h        Sun Sep 17 
08:03:11 2006
***************
*** 38,43 ****
--- 38,44 ----
  #define CMD11 "SHUTDOWN"
  #define CMD12 "FD"
  #define CMD13 "MULTISCAN"
+ #define CMD14 "CHUNKEDSTREAM"
  
  #include "libclamav/clamav.h"
  #include "shared/cfgparser.h"
diff -c -r -X excluding.txt clamav-devel-20060917/clamdscan/client.c 
clamav-devel-20060917.chunked-stream/clamdscan/client.c
*** clamav-devel-20060917/clamdscan/client.c    Mon Sep 11 18:30:05 2006
--- clamav-devel-20060917.chunked-stream/clamdscan/client.c     Thu Sep 21 
10:40:15 2006
***************
*** 60,65 ****
--- 60,68 ----
  int notremoved = 0, notmoved = 0;
  static int hwaccel = 0;
  
+ static int sendn(int fd, void *buff, size_t count);
+ static int recvdelim(int fd, void *buff, size_t count, int delim);
+ 
  static int dsresult(int sockd, const struct optstruct *opt)
  {
        int infected = 0, waserror = 0;
***************
*** 195,276 ****
  
  static int dsstream(int sockd, const struct optstruct *opt)
  {
!       int wsockd, loopw = 60, bread, port, infected = 0;
!       struct sockaddr_in server;
!       struct sockaddr_in peer;
!       int peer_size;
!       char buff[4096], *pt;
! 
  
!     if(write(sockd, "STREAM", 6) <= 0) {
        logg("^Can't write to the socket.\n");
        return 2;
      }
  
!     memset(buff, 0, sizeof(buff));
!     while(loopw) {
!       read(sockd, buff, sizeof(buff));
!       if((pt = strstr(buff, "PORT"))) {
!           pt += 5;
!           sscanf(pt, "%d", &port);
!           break;
!       }
!       loopw--;
!     }
  
!     if(!loopw) {
!       logg("^Daemon not ready for stream scanning.\n");
        return -1;
      }
  
!     /* connect to clamd */
! 
!     if((wsockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0) {
!       perror("socket()");
!       logg("^Can't create the socket.\n");
!       return -1;
!     }
! 
!     server.sin_family = AF_INET;
!     server.sin_port = htons(port);
! 
!     peer_size = sizeof(peer);
!     if(getpeername(sockd, (struct sockaddr *) &peer, &peer_size) < 0) {
!       perror("getpeername()");
!       logg("^Can't get socket peer name.\n");
!       return -1;
!     }
! 
!     switch (peer.sin_family) {
!       case AF_UNIX:
!           server.sin_addr.s_addr = inet_addr("127.0.0.1");
            break;
!       case AF_INET:
!           server.sin_addr.s_addr = peer.sin_addr.s_addr;
!           break;
!       default:
!           mprintf("^Unexpected socket type: %d.\n", peer.sin_family);
            return -1;
!     }
! 
!     if(connect(wsockd, (struct sockaddr *) &server, sizeof(struct 
sockaddr_in)) < 0) {
!       close(wsockd);
!       perror("connect()");
!       logg("^Can't connect to clamd [port: %d].\n", port);
!       return -1;
!     }
! 
!     while((bread = read(0, buff, sizeof(buff))) > 0) {
!       if(write(wsockd, buff, bread) <= 0) {
            logg("^Can't write to the socket.\n");
-           close(wsockd);
            return -1;
        }
      }
-     close(wsockd);
  
      memset(buff, 0, sizeof(buff));
!     while((bread = read(sockd, buff, sizeof(buff))) > 0) {
        logg("%s", buff);
        if(strstr(buff, "FOUND\n")) {
            infected++;
--- 198,244 ----
  
  static int dsstream(int sockd, const struct optstruct *opt)
  {
!       int bread, readsize, datatosend, infected = 0;
!       char buff[32768], sizebuf[64];
  
!     if(sendn(sockd, "nCHUNKEDSTREAM\n", 15) <= 0) {
        logg("^Can't write to the socket.\n");
        return 2;
      }
  
!     buff[sizeof(buff)-1] = '\0';
!     bread = recvdelim(sockd, buff, sizeof(buff)-1, '\n');
  
!     if((bread <= 0) || (!strstr(buff, "PROCEED"))) {
!       logg("^Daemon not ready for stream scanning: %s", buff);
        return -1;
      }
+     datatosend = atoi(buff);
  
!     while(datatosend > 0) {
!       readsize = (sizeof(buff) > datatosend) ? datatosend : sizeof(buff);
!       bread = read(0, buff, sizeof(buff));
!       if(bread <= 0)
            break;
!       snprintf(sizebuf, sizeof(sizebuf), "%d\n", bread);
!       if(sendn(sockd, sizebuf, strlen(sizebuf)) <= 0) {
!           logg("^Can't write to the socket.\n");
            return -1;
!       }
!       if(sendn(sockd, buff, bread) <= 0) {
            logg("^Can't write to the socket.\n");
            return -1;
        }
+       datatosend -= bread;
+     }
+     if(sendn(sockd, "0\n", 2) <= 0) {
+       logg("^Can't write to the socket.\n");
+       return -1;
      }
  
      memset(buff, 0, sizeof(buff));
!     bread = recvdelim(sockd, buff, sizeof(buff)-1, '\n');
!     if(bread > 0) {
        logg("%s", buff);
        if(strstr(buff, "FOUND\n")) {
            infected++;
***************
*** 279,285 ****
            logg("%s", buff);
            return -1;
        }
-       memset(buff, 0, sizeof(buff));
      }
  
      return infected;
--- 247,252 ----
***************
*** 616,618 ****
--- 583,647 ----
  
      free(movefilename);
  }
+ 
+ /* Function: sendn
+       Try hard to send the specified number of bytes
+ */
+ static int sendn(int fd, void *buff, size_t count)
+ {
+       int retval;
+       unsigned int todo;
+       unsigned char *current;
+  
+     todo = count;
+     current = (unsigned char *) buff;
+  
+     do {
+       retval = send(fd, current, todo, 0);
+       if (retval < 0) {
+           if (errno == EINTR) {
+               continue;
+           }
+           return -1;
+       }
+       todo -= retval;
+       current += retval;
+     } while (todo > 0);
+  
+     return count;
+ }
+ 
+ /* Function: recvdelim
+       recieve data up until a delimiter is reached (or the recieve buffer is 
full)
+ */
+ static int recvdelim(int fd, void *buff, size_t count, int delim)
+ {
+       char *buf = (char *)buff;
+       int bread, breturn = 0;
+       char *loc;
+ 
+     while(1) {
+       bread = recv(fd, buf, count, MSG_PEEK);
+       if(bread < 0)
+           return bread;
+       if(bread == 0)
+           return breturn;
+       loc = memchr(buf, delim, bread);
+       if(loc) {
+           if(count > (loc-buf+1)) /* nul terminate the response if we have 
room */
+               loc[1] = '\0';
+           return recv(fd, buf, loc-buf+1, 0);
+       }
+       if(bread == count)
+           return recv(fd, buf, count, 0);
+       bread = recv(fd, buf, bread, 0);
+       if(bread < 0)
+           return bread;
+       if(bread == 0)
+           return breturn;
+       buf += bread;
+       count -= bread;
+       breturn += bread;           
+     }
+ }
+ 
diff -c -r -X excluding.txt clamav-devel-20060917/docs/man/clamd.8.in 
clamav-devel-20060917.chunked-stream/docs/man/clamd.8.in
*** clamav-devel-20060917/docs/man/clamd.8.in   Mon Sep 11 18:30:05 2006
--- clamav-devel-20060917.chunked-stream/docs/man/clamd.8.in    Wed Sep 20 
11:57:43 2006
***************
*** 41,46 ****
--- 41,49 ----
  .TP 
  \fBSTREAM\fR
  Scan stream \- on this command clamd will return "PORT number" and you can 
connect to that port and send a data to scan.
+ .TP 
+ \fBCHUNKEDSTREAM\fR
+ Scan chunkedstream \- on this command clamd will return "nnn bytes of data 
will be scanned.  PROCEED, send counted chunks, end with a 0 sized chunk". Data 
is presented as a series of counted chunks on the same socket as the command.  
A chunk is presented as an ascii decimal number terminated by a newline 
followed by the indicated number of octets of data to scan.  A chunk size of 0 
indicates the end of the data to scan.  Clamd indicates as nnn the maximum 
number of bytes that will be processed.  A client should send no more than nnn 
bytes of data to scan since sending more will return the same scan result as 
scanning nnn bytes.
  .SH "OPTIONS"
  .LP 
  
_______________________________________________
http://lurker.clamav.net/list/clamav-devel.html

Reply via email to