#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sys_domain.h>
#include <sys/kern_control.h>
#include <net/if_utun.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h> // malloc
#include <arpa/inet.h> // inet_ntop
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <netinet/in.h>
#include <time.h>
#include "lsock.h"
#include <ncurses.h>
int wantCurses =1;
int wantUDP = 1;
int wantListen = 1;
int wantTCP = 1;
int wantColors = 1;
int wantRefresh = 1;
/**
* lsock.c : A TCPView inspired netstat(1)-clone (in text mode) for OS X/iOS
*
* Coded by Jonathan Levin - but may be freely distributed (not to mention improved!)
*
*
* Arguments: "nc" - disable full-screen (will automatically disable curses if piping output)
* "tcp" - start in TCP-only mode
* "udp" - start in UDP-only mode
*
*
* Commands for full screen mode: 'T' - TCP, 'U' - UDP, 'L' - Listeners, 'C' - Colors
*
* Possible improvements (which I may actually get to, if I have the time/space)
*
* - GUI: Make this more like Windows' TCPView and Procmon
* - Remote monitoring
* - Periodic polling on sources: Currently only when a source notification is received do
* I poll its description
* - Add routing provider (prov #1)
* - Show provider statistics, too
*
* BUGS: When not filtering on TCP and/or UDP, sometimes not all sockets show. Need to fix that
* On iOS6, you need to kill UserEventAgent; some notifications don't make it.
*
* Note: To use curses on iOS, you will need to copy over /usr/share/terminfo from OS X.
* If you don't, ncurses will fail on initscr() - you will need "nc" instead
*
*
*/
int fd;
void
print_header (WantCurses)
{
char *header = "Time Local Addr \tRemote Addr \tIf\tState \tPID (Name)\n";
if (WantCurses) {
attrset(COLOR_PAIR(0));
attrset(A_UNDERLINE| A_BOLD);
mvwaddstr(stdscr, 1,0 ,header);
attroff(A_UNDERLINE); attron(A_NORMAL);
attrset(COLOR_PAIR(0));
}
else printf ("%s\n", header);
}
int
setupSocket(void)
{
struct sockaddr_ctl sc;
struct ctl_info ctlInfo;
unsigned int num = 0;
int fd;
memset(&ctlInfo, 0, sizeof(ctlInfo));
if (strlcpy(ctlInfo.ctl_name, "com.apple.network.statistics", sizeof(ctlInfo.ctl_name)) >=
sizeof(ctlInfo.ctl_name)) {
fprintf(stderr,"CONTROL NAME too long");
return -1;
}
if ((fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL)) == -1) {
fprintf(stderr,"socket(SYSPROTO_CONTROL): %s", strerror(errno));
return -1;
}
if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) {
fprintf(stderr,"ioctl(CTLIOCGINFO): %s", strerror(errno));
close(fd);
return -1;
}
sc.sc_id = ctlInfo.ctl_id;
sc.sc_len = sizeof(sc);
sc.sc_family = AF_SYSTEM;
sc.ss_sysaddr = AF_SYS_CONTROL;
sc.sc_unit = num + 1; /* zero means unspecified */
if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) == -1) {
fprintf(stderr,"connect(AF_SYS_CONTROL): %s\n", strerror(errno));
if (errno == EBUSY )
{
printf("Apparently some other process is using the system socket. "
"On iOS, this is usually UserEventAgent. Kill it (-9!) and immediately start this program\n");
}
close(fd);
return -1;
}
return fd;
}
const char *
stateToText(int State)
{
switch(State)
{
case TCPS_CLOSED: return ("CLOSED");
case TCPS_LISTEN: return ("LISTENING");
case TCPS_ESTABLISHED: return ("ESTABLISHED");
case TCPS_CLOSING: return ("CLOSING");
case TCPS_SYN_SENT: return ("SYN_SENT");
case TCPS_LAST_ACK: return ("LAST_ACK");
case TCPS_CLOSE_WAIT: return ("CLOSE_WAIT");
case TCPS_TIME_WAIT: return ("TIME_WAIT");
case TCPS_FIN_WAIT_1 : return ("FIN_WAIT_1");
case TCPS_FIN_WAIT_2 : return ("FIN_WAIT_2");
default:
return("?");
}
} // stateToText
#define MAX_DESC 2000
char *descriptors[MAX_DESC];
int descriptorsModded[MAX_DESC];
int maxDesc = 0;
char *message = " ";
void
setMessage (char *Message)
{
message = Message;
}
void
print_descriptors()
{
int i;
int j = 3;
if (!wantUDP && !wantTCP) { setMessage ( "Warning: Both TCP and UDP are filtered");}
if (wantCurses) {
attrset(COLOR_PAIR(0) | A_BOLD);
mvwaddstr(stdscr, 2,0 ,message);
attroff(A_BOLD);
}
if (wantCurses && wantRefresh)
{
wantRefresh = 0; erase();print_header(wantCurses);
}
for (i = 0; i < MAX_DESC; i++)
{
if (descriptors[i])
{
// This is a quick and dirty example - So instead of mucking around with
// descriptor data structures, I look at the text output of each, and figure
// out from it whether or not it's UDP, or the TCP state, etc..
if (strstr(descriptors[i], "N/A")) // UDP have a state of "N/A"
{
if ( ! wantUDP) continue;
if (wantColors) {
if (wantCurses) { init_pair(4,COLOR_CYAN, COLOR_BLACK); attrset(COLOR_PAIR(4)); }
else
printf (GREEN);
}
}
else // TCP (not showing route for now)
{
if (!wantTCP) continue;
if (wantColors)
{
if (wantCurses) attrset(COLOR_PAIR(0)); else printf(NORMAL);
}
if (strstr(descriptors[i], "ESTABLISHED") ) {
if (wantColors)
{
if (wantCurses) { init_pair(3, COLOR_GREEN, COLOR_BLACK); attrset(COLOR_PAIR(3));}
else printf(GREEN);
}
} // ESTABLISHED
if (strstr(descriptors[i], "LISTEN") ) {
if (!wantListen) continue;
if (wantColors)
{
if (wantCurses) { init_pair(2, COLOR_BLUE, COLOR_BLACK); attrset(COLOR_PAIR(2));}
else printf(BLUE);
}
} // ESTABLISHED
if (strstr(descriptors[i], "SYN_SENT") ) {
if (wantColors) {
if (wantCurses) { init_pair(5, COLOR_RED, COLOR_BLACK); attrset(COLOR_PAIR(5));}
else printf(RED);
}
} // SYN_SENT
} // TCP
if (wantCurses) {
mvwaddstr(stdscr, j++,0 ,descriptors[i]);
}
else
{ if (descriptorsModded[i]) printf( "%s\n", descriptors[i]); }
descriptorsModded[i] = 0;
if (wantColors && !wantCurses) printf (NORMAL);
}
}
clrtobot();
refresh();
}
char *
print_udp_descriptor (char *desc, int prov, int num)
{
nstat_udp_descriptor *nud = (nstat_udp_descriptor *) desc;
int f;
int ipv6 = 0;
char localAddrPrint[40];
char remoteAddrPrint[40];
const char *localPrint, *remotePrint;
unsigned short localPort, remotePort;
char timeBuf[30];
time_t now;
struct tm *n;
char *returned = (char *) malloc(1024);
returned[0] ='\0'; // Dont need AF_ because we have this already in the address, right?
time(&now);
n = localtime(&now);
memset(timeBuf,'\0', 30);
strftime(timeBuf, 29, "%H:%M:%S", n);
// sprintf(timeBuf, "%d......", num);
sprintf (returned + strlen(returned), "%-8s ", timeBuf);
if (nud->local.v4.sin_family == AF_INET6) {
ipv6++; }
if (!ipv6)
{
localPort = ntohs(nud->local.v4.sin_port);
remotePort = ntohs(nud->remote.v4.sin_port);
}
else {
localPort = ntohs(nud->local.v6.sin6_port);
remotePort = ntohs(nud->remote.v6.sin6_port);
}
if (nud->remote.v6.sin6_family == AF_INET6) {
localPrint = inet_ntop(nud->local.v6.sin6_family,&(nud->local.v6.sin6_addr), localAddrPrint, 40);
remotePrint = inet_ntop(nud->remote.v6.sin6_family,&(nud->remote.v6.sin6_addr), remoteAddrPrint, 40);
}
if (nud->remote.v4.sin_family == AF_INET) {
localPrint = inet_ntop(nud->local.v4.sin_family,&(nud->local.v4.sin_addr), localAddrPrint, 40);
remotePrint = inet_ntop(nud->remote.v4.sin_family,&(nud->remote.v4.sin_addr), remoteAddrPrint, 40);
}
if (localAddrPrint) {
sprintf (localAddrPrint + strlen(localAddrPrint),":%-5hu", (unsigned short) localPort);
if (remotePort == 0)
{
sprintf(returned + strlen(returned),"%-21s\t*.* \t ", localAddrPrint);
}
else
sprintf(returned + strlen(returned), "%-21s\t%-21s %-5hu\t", localAddrPrint, remoteAddrPrint, (unsigned short) remotePort);
}
sprintf(returned + strlen(returned), "%d\tN/A \t", nud->ifindex);
sprintf(returned + strlen(returned), "%-5d ", nud->pid);
if (nud->pname[0]) { sprintf(returned + strlen(returned), "(%s)", nud->pname );}
else { sprintf(returned + strlen(returned), " ");}
return (returned);
}
void refreshSrc (int Prov, int Num)
{
int rc ;
nstat_msg_get_src_description gsdreq;
gsdreq.hdr.type= 1005; //NSTAT_MSG_TYPE_GET_SRC_DESC
gsdreq.srcref=Num;
gsdreq.hdr.context = Prov; ;//;nmsa->provider;
rc = write (fd, &gsdreq, sizeof(gsdreq));
}
char *
print_tcp_descriptor (char *desc, int prov, int num)
{
nstat_tcp_descriptor *ntd = (nstat_tcp_descriptor *) desc;
int ipv6 = 0;
char localAddrPrint[40];
char remoteAddrPrint[40];
const char *localPrint, *remotePrint;
unsigned short localPort, remotePort;
char timeBuf[30];
time_t now;
struct tm *n;
char *returned = (char *) malloc(1024);
returned[0] ='\0'; // Dont need AF_ because we have this already in the address, right?
time(&now);
n = localtime(&now);
strftime(timeBuf, 29, "%H:%M:%S", n);
//sprintf(timeBuf, "%d......", num);
sprintf (returned + strlen(returned), "%-8s ", timeBuf);
// sprintf(returned+strlen(returned), "%d\t", ntd->sndbufused);
if (ntd->local.v4.sin_family == AF_INET6) {
//printf("IPv6\t");
ipv6++; }
if (!ipv6)
{
localPort = ntohs(ntd->local.v4.sin_port);
remotePort = ntohs(ntd->remote.v4.sin_port);
}
else {
localPort = ntohs(ntd->local.v6.sin6_port);
remotePort = ntohs(ntd->remote.v6.sin6_port);
}
if (ntd->remote.v6.sin6_family == AF_INET6) {
localPrint = inet_ntop(ntd->local.v6.sin6_family,&(ntd->local.v6.sin6_addr), localAddrPrint, 40);
remotePrint = inet_ntop(ntd->remote.v6.sin6_family,&(ntd->remote.v6.sin6_addr), remoteAddrPrint, 40);
}
if (ntd->remote.v4.sin_family == AF_INET) {
localPrint = inet_ntop(ntd->local.v4.sin_family,&(ntd->local.v4.sin_addr), localAddrPrint, 40);
remotePrint = inet_ntop(ntd->remote.v4.sin_family,&(ntd->remote.v4.sin_addr), remoteAddrPrint, 40);
}
if (remoteAddrPrint)
{
sprintf (remoteAddrPrint + strlen(remoteAddrPrint),":%-5hu", (unsigned short) remotePort);
}
else sprintf (remoteAddrPrint, "*.*");
if (localAddrPrint) {
sprintf (localAddrPrint + strlen(localAddrPrint),":%-5hu", (unsigned short) localPort);
if (remotePort == 0)
{
sprintf(returned + strlen(returned),"%-21s\t*.* \t ", localAddrPrint);
}
else
sprintf(returned + strlen(returned), "%-21s\t%-25s\t", localAddrPrint, remoteAddrPrint);
}
sprintf(returned + strlen(returned), "%d\t%-10s\t", ntd->ifindex, stateToText(ntd->state));
sprintf(returned + strlen(returned), "%-5d ", ntd->pid);
if (ntd->pname[0]) { sprintf(returned + strlen(returned), "(%s)", ntd->pname );}
else { sprintf(returned + strlen(returned), " ");}
if ( strstr(returned, "CLOSED") && strstr(returned,"("))
{
refreshSrc(prov,num);
}
return (returned);
}
int
print_nstat_msg (void *msg, int size)
{
nstat_msg_hdr *ns = (nstat_msg_hdr *) msg;
switch (ns->type)
{
case 10001: // NSTAT_MSG_TYPE_SRC_ADDED = 10001
{
nstat_msg_src_added *nmsa = (nstat_msg_src_added *) (ns);
// printf("Src Added - %ld\n", sizeof(nstat_msg_src_added));
// printf ("PRovider: %d, SrcRef: %d\n", nmsa->provider, nmsa->srcref);
return (10001);
}
break;
case 10002: //NSTAT_MSG_TYPE_SRC_REMOVED = 10002
// remove this descriptor
return (10002);
//remove_descriptor(...)
break;
case 10003: // ,NSTAT_MSG_TYPE_SRC_DESC = 10003
{
nstat_msg_src_description *nmsd = (nstat_msg_src_description *) (ns );
if (nmsd->provider == 2)
descriptors[nmsd->srcref] =print_tcp_descriptor((char *)nmsd->data, nmsd->provider,nmsd->srcref);
else if (nmsd->provider == 3)
{descriptors[nmsd->srcref] = print_udp_descriptor((char *) nmsd->data, nmsd->provider, nmsd->srcref);
}
descriptorsModded[nmsd->srcref] = 10;
}
break;
case 10004: //NSTAT_MSG_TYPE_SRC_COUNTS = 10004
{
nstat_msg_src_counts *nmsc = (nstat_msg_src_counts *) (ns );
// printf ("RX: %d TX:%d\n", nmsc->counts.nstat_rxpackets , nmsc->counts.nstat_txpackets );
} break;
case 1:
{
if (ns->context < MAX_DESC && !descriptors[ns->context]) break;
printf ("ERROR for context %lld - should be available\n", ns->context); break;
}
case 0: // printf("Success\n"); break;
break;
default:
printf("Type : %d\n", ns->type);
}
fflush(NULL);
return (ns->type) ;
}
int main (int argc, char **argv)
{
int rc = 0;
char c[2048];
memset (&descriptors, '\0', sizeof(char *) * MAX_DESC);
memset (&descriptorsModded, '\0', sizeof(int) * MAX_DESC);
fd =setupSocket ();
if (fd == -1)
{
fprintf(stderr,"Unable to continue without socket\n"); exit(1);
}
struct stat stbuf;
fstat(1, &stbuf);
if (stbuf.st_mode & S_IFCHR) { wantColors = 1; wantCurses = 1;} else {wantColors = 0; wantCurses = 0;}
int arg = 1;
for (arg = 1; arg < argc; arg++)
{
if (strcmp(argv[arg], "nc") == 0) wantCurses = 0;
if (strcmp(argv[arg], "udp") == 0) { wantUDP = 1; wantTCP = 0;}
if (strcmp(argv[arg], "tcp") == 0) { wantTCP = 1; wantUDP = 0;}
}
nstat_msg_query_src_req qsreq;
nstat_msg_add_all_srcs aasreq;
nstat_msg_get_src_description gsdreq;
char ch;
if (wantUDP)
{
aasreq.provider = 3; //
aasreq.hdr.type = 1002; //NSTAT_MSG_TYPE_ADD_ALL_SRCS
aasreq.hdr.context = 3;
rc = write (fd, &aasreq, sizeof(aasreq));
}
if (wantTCP)
{
aasreq.provider = 2 ;
aasreq.hdr.type = 1002; //NSTAT_MSG_TYPE_ADD_ALL_SRCS
aasreq.hdr.context = 2;
rc = write (fd, &aasreq, sizeof(aasreq));
}
if (wantCurses)
{
initscr(); cbreak(); noecho(); start_color();
nodelay (stdscr,1);
}
print_header(wantCurses);
while (1) { //rc > 0) {
if (wantCurses)
{
fd_set fds;
struct timeval to;
to.tv_sec = 0;
to.tv_usec = 100;
FD_ZERO (&fds);
FD_SET (fd, &fds);
// select on socket, rather than read..
rc = select(fd +1, &fds, NULL, NULL, &to);
if (rc > 0) rc = read(fd,c,2048);
}
else
rc = read (fd, c, 2048);
// check rc. Meh.
if (rc > 0)
{
nstat_msg_hdr *ns = (nstat_msg_hdr *) c;
switch (ns->type)
{
case 10001:
{
nstat_msg_src_added *nmsa = (nstat_msg_src_added *) (c);
gsdreq.hdr.type= 1005; //NSTAT_MSG_TYPE_GET_SRC_DESC
gsdreq.srcref=nmsa->srcref;
gsdreq.hdr.context = 1005; // This way I can tell if errors get returned for dead sources
rc = write (fd, &gsdreq, sizeof(gsdreq));
descriptorsModded[nmsa->srcref] = 1;
break;
}
case 10002:
{
//struct nstat_msg_src_removed *rem;
nstat_msg_src_removed *nmsr = (nstat_msg_src_removed *) (c);
if(wantCurses) mvhline(nmsr->srcref + 2, 1, '-' , strlen(descriptors[nmsr->srcref]));
descriptors[nmsr->srcref] = NULL; // or free..
descriptorsModded [nmsr->srcref] = 1; // or free..
break;
}
case 10003:
rc = print_nstat_msg(c,rc);
break;
case 10004:
rc = print_nstat_msg(c,rc);
break;
case 0:
break;
case 1:
//Error message - these are usually for dead sources (context will be 1005)
break;
default:
break;
}
}
if (wantCurses)
{
int i;
ch = getch();
if (ch != ERR) {
if (ch == 'H' || ch =='h') printf("HELP\n");
if (ch == 'U' || ch =='u') {wantUDP = !wantUDP; setMessage("Toggling UDP sockets"); }
if (ch == 'T' || ch == 't') { wantTCP = !wantTCP; setMessage("Toggling TCP sockets"); }
if (ch == 'L' || ch == 'l') { wantListen= !wantListen; setMessage("Toggling TCP listeners"); }
if (ch == 'C' || ch == 'c') { wantColors = !wantColors; setMessage("Toggling color display"); }
wantRefresh = 1;
}
}
print_descriptors();
}
return (0);
}