This is simple Port Scanner application implemented in C.

It allows one to scan range of target ports using either TCP or UDP packets.

Application requires following input: host, protocol (tcp or udp), port range

geo@fermat:/home/work/net$ ./geoscan 
Usage: ./geoscan < hostname > < protocol > < portlow > < porthigh >
geo@fermat:/home/work/net$

The scanner first parses input parameters and resolves host:

  host = argv[1];
  pro = argv[2];
  portlow  = atoi(argv[3]);
  porthigh = atoi(argv[4]);
  
  fprintf(stderr, "n Scanning host=%s, protocol=%s, ports: %d -> %dnn",
  	  			host, pro, portlow, porthigh);
  
  if(strcmp(pro, protoc[0])==0)
	pro = protoc[0];
  else if (strcmp(pro, protoc[1])==0)
	pro = protoc[1];
  else
  {
	herror("n specify valid protocol - tcp or udpn");
	exit(-1);
  }

  
  if((he = gethostbyname(argv[1])) == NULL)
  {
    herror("n *** gethostbyname() failed ***n");
    exit(-1);
  }

In case TCP protocol is selected for scan, app opens streaming socket

for every port to be scanned, tries to connect to it, and if successful

it displays information about service using struct servent.

  if(strcmp(pro, protoc[0])==0) // tcp scan
  {
	for(port = portlow; port <= porthigh; port++)
  	{
	  // open stream socket
	  if((sendfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
	  {
	    perror("*** socket(,SOCK_STREAM,) failed ***n");
	    exit(-1);
	  }

	  bzero(&servaddr, sizeof(servaddr));
    	  servaddr.sin_family = AF_INET;
    	  servaddr.sin_port = htons(port);
    	  servaddr.sin_addr = *((struct in_addr *)he->h_addr);

    	  if(connect(sendfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0)
	  {
         srvport = getservbyport(htons(port), protoc[0]);

	    if(srvport != NULL)
		printf("tport %d: %sn", port, srvport->s_name);

	    fflush(stdout); 
	  }
    	  close(sendfd);
  	 }//end of for()
    }
Here's the output from scanning localhost (which is Debian):
geo@fermat:/home/work/net$ ./geoscan localhost tcp 1 120 

 Scanning host=localhost, protocol=tcp, ports: 1 -> 120

	port 111: sunrpc

geo@fermat:/home/work/net$

In case of UDP protocol scanner opens two sockets: one - datagram socket to send UDP packets, another - raw socket to receive ICMP response packets:

    else // udp scan
    {
	 // open send UDP socket
	 if((sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
	 {
	   perror("*** socket(,,IPPROTO_UDP) failed ***n");
	   exit(-1);
	 }
	 // open receive ICMP socket
	 if((recvfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
	 {
	   perror("*** socket(,,IPPROTO_ICMP) failed ***n");
	   exit(-1);
	 }

	 for(port = portlow; port <= porthigh; port++)
  	 {
    		tx_packet(sendfd, port, he);

    		if(rx_packet(recvfd) == 1)
    		{
		  srvport = getservbyport(htons(port), protoc[1]);

		  if (srvport != NULL)
		    printf("tport %d: %sn", port, srvport->s_name);
	
		  fflush(stdout); 
    		}
  	 }
    }

Transmit packet function (tx_packet above) essentially sends (default) buffer

to send socket descriptor using port, hostent and sendto() syscall:

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(port);
  servaddr.sin_addr = *((struct in_addr *)he->h_addr);

  if(sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
  {
    perror("*** sendto() failed ***");
  }

Receive packet function (rx_packet) makes use of standard Unix/Linux fd_set,

associated macroses FD_ZERO, FD_SET and select() syscall to poll receiving

socket on a regular basis (1 sec) and receive packets using syscall recvfrom():

  poll.tv_sec = 1;
  poll.tv_usec = 0;

  while(1)
  {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    if(select(fd + 1, &fds, NULL, NULL, &poll) > 0)
    {
      recvfrom(fd, &buf, sizeof(buf), 0x0, NULL, NULL);
    }
    else if(!FD_ISSET(fd, &fds))
      return 1;
    else
      perror("*** recvfrom() failed ***");

    iphdr = (struct ip *)buf;
    iplen = iphdr->ip_hl << 2;
		  
    icmp = (struct icmp *)(buf + iplen);

    if((icmp->icmp_type == ICMP_UNREACH) && (icmp->icmp_code == ICMP_UNREACH_PORT))
      return 0;
  }

Since opening raw sockets requires special permissions one needs

to run scan from root account, here's result of scanning localhost:

fermat:/home/work/net# ./geoscan localhost udp 1 120

 Scanning host=localhost, protocol=udp, ports: 1 -> 120

	port 68: bootpc
	port 111: sunrpc

fermat:/home/work/net#

Scan of Wireless AP (Belkin54g) produced following results:

fermat:/home/work/net# ./geoscan 192.168.2.1 udp 1 120

 Scanning host=192.168.2.1, protocol=udp, ports: 1 -> 120

	port 7: echo
	port 9: discard
	port 13: daytime
	port 18: msp
	port 19: chargen
	port 21: fsp
	port 22: ssh
	port 37: time
	port 39: rlp
	port 49: tacacs
	port 50: re-mail-ck
	port 53: domain
	port 65: tacacs-ds
	port 67: bootps
	port 68: bootpc
	port 69: tftp
	port 70: gopher
	port 80: www
	port 88: kerberos
	port 104: acr-nema
	port 105: csnet-ns
	port 106: poppassd
	port 107: rtelnet
	port 109: pop2
	port 110: pop3
	port 111: sunrpc

fermat:/home/work/net#

Scan of the same AP using tcp protocol does not seem to be working

(can see neither results nor error messages) for obvious reason:

this scanner is using blocking tcp connection which is not gonna work

without direct link to the host:

fermat:/home/work/net$ ./geoscan 192.168.2.1 tcp 1 120

 Scanning host=192.168.2.1, protocol=tcp, ports: 1 -> 120


And non-blocking tcp scanner requires quite different design to work correctly

and it will be discussed later in another application.

You can download this port scanner build on Debian Lenny, GCCv4.3.2 from here.