/* Socks Server 5
* Copyright (C) 2002 - 2006 by Matteo Ricchetti - <matteo.ricchetti@libero.it>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include"SS5Main.h"
#include"SS5Defs.h"
#include"SS5Mod_socks4.h"
#include"SS5Mod_log.h"
S5RetCode InitModule( struct _module *m )
{
m->V4RequestParsing=RequestParsing;
m->V4UpstreamServing=UpstreamServing;
m->V4ConnectServing=ConnectServing;
m->V4BindServing=BindServing;
m->V4AddRoute=AddRoute;
m->V4FreeRoute=FreeRoute;
m->V4GetRoute=GetRoute;
return OK;
}
S5RetCode RequestParsing(struct _SS5AuthInfo *ai, struct _SS5MethodInfo *mi, struct _SS5Socks5Data *sd, struct _SS5RequestInfo *ri)
{
unsigned int i,
c;
memcpy(sd->TcpRequest,sd->MethodRequest,sd->MethodBytesReceived);
ri->Ver=mi->Ver;
ri->Cmd=(unsigned char)sd->TcpRequest[1];
if( !(unsigned char)sd->TcpRequest[4] && !(unsigned char)sd->TcpRequest[5] && !(unsigned char)sd->TcpRequest[6] && (unsigned char)sd->TcpRequest[7] ) {
/*
* Destination address is fqdn
*/
ri->DstPort=0;
ri->DstPort +=(unsigned char)sd->TcpRequest[2];
ri->DstPort <<=8;
ri->DstPort +=(unsigned char)sd->TcpRequest[3];
for(i=0,c=8;(ai->Username[i]=sd->TcpRequest[c]);c++,i++);
ai->Username[i]='\0';
for(i=0;(ri->DstAddr[i]=sd->TcpRequest[c]);i++,c++ );
ri->DstAddr[c]='\0';
ri->ATyp=DOMAIN;
}
else {
/*
* Destination address is dot notation
*/
ri->ATyp=IPV4;
ri->DstPort=0;
ri->DstPort +=(unsigned char)sd->TcpRequest[2];
ri->DstPort <<=8;
ri->DstPort +=(unsigned char)sd->TcpRequest[3];
for(i=0,c=8;(ai->Username[i]=sd->TcpRequest[c]);c++,i++);
ai->Username[i]='\0';
snprintf(ri->DstAddr,sizeof(ri->DstAddr),"%hu.%hu.%hu.%hu",(unsigned char)sd->TcpRequest[4],
(unsigned char)sd->TcpRequest[5],
(unsigned char)sd->TcpRequest[6],
(unsigned char)sd->TcpRequest[7]);
}
return OK;
}
S5RetCode UpstreamServing(struct _SS5UpstreamInfo *ui, struct _SS5ClientInfo *ci, struct _SS5RequestInfo *ri, int *applicationSocket,
struct _SS5Socks5Data *sd, struct _SS5AuthInfo *ai)
{
struct sockaddr_in applicationSsin,
bindInterfaceSsin;
struct in_addr in;
pid_t pid;
char logString[128];
unsigned short ipA,
ipB,
ipC,
ipD;
/*
* Get child/thread pid
*/
if( NOTTHREADED() )
pid=getpid();
else
pid=(unsigned int)pthread_self();
if ( (*applicationSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
ERRNO(pid)
return( -1 * S4REQUEST_REJECTED );
}
/*
* SS5: set bind interface if configured
*/
if( ROUTE() ) {
if( (in.s_addr=(unsigned long int)GetRoute(inet_network(ci->SrcAddr), ai->Username)) ) {
memset((char *)&bindInterfaceSsin, 0, sizeof(struct sockaddr_in));
if( (bindInterfaceSsin.sin_addr.s_addr=in.s_addr) ) {
bindInterfaceSsin.sin_family = AF_INET;
bindInterfaceSsin.sin_port = htons(0);
if ( bind(*applicationSocket, (struct sockaddr *)&bindInterfaceSsin, sizeof(struct sockaddr_in)) == -1 ) {
ERRNO(pid)
return( -1 * S4REQUEST_REJECTED );
}
}
}
}
memset((char *)&applicationSsin, 0, sizeof(struct sockaddr_in));
applicationSsin.sin_family = AF_INET;
applicationSsin.sin_port = htons(ui->DstPort);
applicationSsin.sin_addr.s_addr = (unsigned long int)ui->DstAddr;
if( connect(*applicationSocket,(struct sockaddr *)&applicationSsin,sizeof(struct sockaddr_in)) != -1 ) {
ERRNO(pid)
/*
* Proxy client connect request towards upstream socks server
*/
if( send(*applicationSocket,sd->TcpRequest,sd->TcpRBytesReceived,SS5_SEND_OPT) == -1) {
ERRNO(pid)
return( -1 * S4REQUEST_REJECTED );
}
if( ri->Cmd == BIND ) {
/*
* Proxy client bind request towards upstream socks server
*/
if( (sd->TcpRBytesReceived=recv(*applicationSocket,sd->Response,sizeof(sd->Response),0)) <= 0 ) {
ERRNO(pid)
return( -1 * S4REQUEST_REJECTED );
}
/*
* SS5 intercepts the bind ip address: if equal 0.0.0.0, replaces it
*/
if( (unsigned char)sd->Response[4] == 0 && (unsigned char)sd->Response[5] == 0 &&
(unsigned char)sd->Response[6] == 0 && (unsigned char)sd->Response[7] == 0 ) {
sscanf((const char *)ui->DstAddr,"%hu.%hu.%hu.%hu",&ipA,&ipB,&ipC,&ipD);
sd->Response[4]=ipA;
sd->Response[5]=ipB;
sd->Response[6]=ipC;
sd->Response[7]=ipD;
}
if( send(ci->Socket,sd->Response,sd->TcpRBytesReceived,SS5_SEND_OPT) == -1) {
ERRNO(pid)
return( -1 * S4REQUEST_REJECTED );
}
}
return OK;
}
else {
return( -1 * S4REQUEST_REJECTED );
}
return OK;
}
S5RetCode ConnectServing(struct _SS5ClientInfo *ci, struct _SS5RequestInfo *ri, struct _SS5AuthInfo *ai, int *applicationSocket, struct _SS5Socks5Data *sd)
{
register unsigned int index;
S5RetCode err=S4REQUEST_GRANTED;
S5Limit resolvedHostNumber = 0;
struct in_addr in;
pid_t pid;
char logString[128];
struct _S5HostList resolvedHostList[MAXDNS_RESOLV];
struct sockaddr_in applicationSsin,
bindInterfaceSsin;
unsigned short ipA,
ipB,
ipC,
ipD;
/*
* Get child/thread pid
*/
if( NOTTHREADED() )
pid=getpid();
else
pid=(unsigned int)pthread_self();
/*
* SS5: Resolve hostname before connecting
*/
if( ri->ATyp == DOMAIN ) {
if( S5ResolvHostName(ri, (struct _S5HostList *)resolvedHostList, &resolvedHostNumber) == ERR )
err=S4REQUEST_REJECTED;
}
if( err == S4REQUEST_GRANTED ) {
if ((*applicationSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
else {
/*
* SS5: set bind interface if configured
*/
if( ROUTE() ) {
if( (in.s_addr=(unsigned long int)GetRoute(inet_network(ci->SrcAddr), ai->Username)) ) {
memset((char *)&bindInterfaceSsin, 0, sizeof(struct sockaddr_in));
if( (bindInterfaceSsin.sin_addr.s_addr=in.s_addr) )
{
bindInterfaceSsin.sin_family = AF_INET;
bindInterfaceSsin.sin_port = htons(0);
if ( bind(*applicationSocket, (struct sockaddr *)&bindInterfaceSsin, sizeof(struct sockaddr_in)) == -1 ) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
}
}
}
if( err == S4REQUEST_GRANTED ) {
bzero((char *)&applicationSsin, sizeof(struct sockaddr_in));
applicationSsin.sin_family = AF_INET;
applicationSsin.sin_port = htons(ri->DstPort);
applicationSsin.sin_addr.s_addr = inet_addr(ri->DstAddr);
if( connect(*applicationSocket,(struct sockaddr *)&applicationSsin,sizeof(struct sockaddr_in))==-1 ) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
/*
* Try connecting to other destinations in case of multiple dns answers
*/
for(index=1;index<resolvedHostNumber;index++) {
strncpy(ri->DstAddr,resolvedHostList[index].NextHost,sizeof(ri->DstAddr));
applicationSsin.sin_addr.s_addr = inet_addr(ri->DstAddr);
if( connect(*applicationSocket,(struct sockaddr *)&applicationSsin,sizeof(struct sockaddr_in))!=-1 ) {
ERRNO(pid)
err=S4REQUEST_GRANTED;
break;
}
}
}
}
}
}
/*
* Prepare and send socks V4 response
*/
memcpy(sd->Response,sd->TcpRequest,32);
sscanf((const char *)ri->DstAddr,"%hu.%hu.%hu.%hu",&ipA,&ipB,&ipC,&ipD);
sd->Response[0]=0;
sd->Response[1]=err;
sd->Response[2]=ri->DstPort >> 8;
sd->Response[3]=(ri->DstPort << 8) >> 8;
sd->Response[4]=ipA;
sd->Response[5]=ipB;
sd->Response[6]=ipC;
sd->Response[7]=ipD;
if( send(ci->Socket,sd->Response,8,SS5_SEND_OPT) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
if( err != S4REQUEST_GRANTED )
return (-1 * err);
else
return OK;
}
S5RetCode BindServing(struct _SS5ClientInfo *ci, struct _SS5RequestInfo *ri, struct _SS5AuthInfo *ai, int *applicationSocket, struct _SS5Socks5Data *sd)
{
unsigned int len,
fd;
int cb = 0;
unsigned short ipA,
ipB,
ipC,
ipD;
char addr[16];
char logString[256];
fd_set fdset;
S5Limit resolvedHostNumber=1;
struct _S5HostList resolvedHostList[MAXDNS_RESOLV];
struct in_addr in;
struct timeval tv;
struct sockaddr_in applicationSsin,
clientBindSsin;
S5RetCode err=S4REQUEST_GRANTED;
pid_t pid;
/*
* Get child/thread pid
*/
if( NOTTHREADED() )
pid=getpid();
else
pid=(unsigned int)pthread_self();
/*
* SS5: Resolve hostname before binding
*/
if( ri->ATyp == DOMAIN ) {
if( S5ResolvHostName(ri, (struct _S5HostList *)resolvedHostList, &resolvedHostNumber) == ERR ) {
err=S4REQUEST_REJECTED;
}
}
if( err == S4REQUEST_GRANTED ) {
/*
* Create application socket
*/
if ((*applicationSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
else {
/*
* Create client socket for bind
*/
if ((cb = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
else {
memset((char *)&clientBindSsin, 0, sizeof(struct sockaddr_in));
clientBindSsin.sin_family = AF_INET;
clientBindSsin.sin_port = htons(0);
/*
* Look for the right interface for binding
*/
if( S5GetBindIf(ri->DstAddr,addr) == ERR ) {
/* Match with destination address in socks request */
clientBindSsin.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
clientBindSsin.sin_addr.s_addr = inet_addr(addr);
/*
* SS5: set bind interface if configured
*/
if( ROUTE() ) {
if( (in.s_addr=(unsigned long int)GetRoute(inet_network(ci->SrcAddr), ai->Username)) )
clientBindSsin.sin_addr.s_addr = in.s_addr;
}
if (bind(cb, (struct sockaddr *)&clientBindSsin, sizeof(struct sockaddr_in)) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
else {
/*
* Get clientbind info
*/
len=sizeof(struct sockaddr);
getsockname(cb,(struct sockaddr *)&clientBindSsin,&len);
/*
* SS5: listen for a queue length equal to one
*/
if (listen(cb, 1) == -1) {
ERRNO(pid)
err=S4REQUEST_REJECTED;
}
}
}
}
}
in.s_addr=clientBindSsin.sin_addr.s_addr;
strncpy(addr,(char *)inet_ntoa(in),sizeof(addr));
sscanf((const char *)addr,"%hu.%hu.%hu.%hu",&ipA,&ipB,&ipC,&ipD);
/*
* Send socks response
*/
memcpy(sd->Response,sd->TcpRequest,32);
sd->Response[0]=0;
sd->Response[1]=err;
sd->Response[2]=(ntohs(clientBindSsin.sin_port) >> 8);
sd->Response[3]=((ntohs(clientBindSsin.sin_port) << 8) >> 8);
sd->Response[4]=ipA;
sd->Response[5]=ipB;
sd->Response[6]=ipC;
sd->Response[7]=ipD;
switch( ri->ATyp ) {
/*
* Socks V4 Header is 8 bytes
*/
case IPV4:
case DOMAIN:
if( send(ci->Socket,sd->Response,8,SS5_SEND_OPT) == -1) {
ERRNO(pid)
return (-1 * S4REQUEST_REJECTED);
}
break;
/*
* Socks V5 Header is 22 bytes but IPV6 is not supported
*/
case IPV6: return (-1 * S4REQUEST_REJECTED); break;
}
if( err == S4REQUEST_GRANTED ) {
/*
* Wait for BIND_TIMEOUT before closing listen port
*/
bzero((char *)&applicationSsin, sizeof(struct sockaddr_in));
len = sizeof (struct sockaddr_in);
FD_ZERO(&fdset);
FD_SET(cb,&fdset);
tv.tv_sec=BIND_TIMEOUT;
tv.tv_usec=0;
if( (fd=select(cb+1,&fdset,NULL,NULL,&tv)) ) {
if( FD_ISSET(cb,&fdset) ) {
if ((*applicationSocket = accept(cb, (struct sockaddr *)&applicationSsin, &len)) == -1) {
ERRNO(pid)
close(cb);
return (-1 * S4REQUEST_REJECTED);
}
}
}
else {
/*
* Timeout expired accepting connection from remote application
*/
close(cb);
return (-1 * S4REQUEST_REJECTED);
}
/*
* Socks response packet
*/
sd->Response[1]=S4REQUEST_GRANTED;
switch( ri->ATyp ) {
/*
* Socks V4 Header is 8 bytes
*/
case IPV4:
case DOMAIN:
if( send(ci->Socket,sd->Response,8,SS5_SEND_OPT) == -1) {
ERRNO(pid)
return (-1 * S4REQUEST_REJECTED);
}
break;
/*
* Socks V5 Header is 22 bytes but IPV6 is not supported
*/
case IPV6: return (-1 * S4REQUEST_REJECTED); break;
}
}
if( err != S4REQUEST_GRANTED )
return (-1 * err);
else
return OK;
}
S5RetCode S5GetBindIf( char *s5application, char *s5clientbind )
{
int index;
if( (index=S5IfMatch(s5application)) != -1 ) {
strncpy(s5clientbind,S5Interface[index]->IP,sizeof(S5Interface[index]->IP) - 1);
return OK;
}
return ERR;
}
S5RetCode S5ResolvHostName( struct _SS5RequestInfo *ri, struct _S5HostList *resolvedHostList, S5Limit *resolvedHostNumber)
{
register unsigned int index,count;
struct hostent *dhost;
char logString[256];
pid_t pid;
/*
* Get child/thread pid
*/
if( NOTTHREADED() )
pid=getpid();
else
pid=(unsigned int)pthread_self();
if( (dhost=(struct hostent *)gethostbyname(ri->DstAddr)) == NULL ) {
/* TODO: handle h_error */
return ERR;
}
/*
* In case of multiple answers, save all
*/
*resolvedHostNumber=0;
for (index=0; dhost->h_addr_list[index] && index < MAXDNS_RESOLV; index++) {
strncpy(resolvedHostList[index].NextHost,(char *)inet_ntoa(*(struct in_addr *)dhost->h_addr_list[index]),sizeof(resolvedHostList[index].NextHost));
*resolvedHostNumber=*resolvedHostNumber + 1;
}
if( index == MAXDNS_RESOLV ) {
if( VERBOSE() ) {
snprintf(logString,128,"[%u] [VERB] Max number of multiple dns records reached while resolving destination: %d.",pid, MAXDNS_RESOLV);
LOGUPDATE()
}
}
/*
* If request, order dns answers
*/
if( SS5SocksOpt.DnsOrder ) {
S5OrderIP(resolvedHostList, resolvedHostNumber);
if( VERBOSE() ) {
snprintf(logString,128,"[%u] [VERB] Ordering multiple records dns.",pid);
LOGUPDATE()
for(count=0;count<*resolvedHostNumber; count++) {
snprintf(logString,128,"[%u] [VERB] Resolved %s to %s.",pid,ri->DstAddr,resolvedHostList[count].NextHost);
LOGUPDATE()
}
}
}
strncpy(ri->DstAddr,resolvedHostList[0].NextHost,sizeof(ri->DstAddr));
return OK;
}
S5RetCode S5OrderIP( struct _S5HostList *resolvedHostList, S5Limit *resolvedHostNumber )
{
register unsigned int index;
unsigned int swap;
char hostTmp[16];
do {
swap=0;
for(index=0;index<(*resolvedHostNumber)-1;index++)
if( S5CompIP(resolvedHostList[index].NextHost,resolvedHostList[index+1].NextHost) ) {
strncpy(hostTmp,resolvedHostList[index+1].NextHost,sizeof(resolvedHostList[index+1].NextHost) - 1);
strncpy(resolvedHostList[index+1].NextHost,resolvedHostList[index].NextHost,sizeof(resolvedHostList[index].NextHost) - 1);
strncpy(resolvedHostList[index].NextHost,hostTmp,sizeof(hostTmp) - 1);
swap=1;
}
} while(swap);
return OK;
}
S5RetCode S5CompIP(char src[16],char dst[16] )
{
unsigned short ips_a,
ips_b,
ips_c,
ips_d,
ipd_a,
ipd_b,
ipd_c,
ipd_d;
sscanf((const char *)src,"%hu.%hu.%hu.%hu",&ips_a,&ips_b,&ips_c,&ips_d);
sscanf((const char *)dst,"%hu.%hu.%hu.%hu",&ipd_a,&ipd_b,&ipd_c,&ipd_d);
if( (ips_a > ipd_a) )
return OK;
else if( (ipd_a >ips_a) )
return ERR;
else if( (ips_b >ipd_b) )
return OK;
else if( (ipd_b >ips_b) )
return ERR;
else if( (ips_c >ipd_c) )
return OK;
else if( (ipd_c >ips_c) )
return ERR;
else if( (ips_d >ipd_d) )
return OK;
else if( (ipd_d >ips_d) )
return ERR;
return ERR;
}
S5RetCode S5VerifyBind(struct _SS5UdpClientInfo *uci, struct _SS5RequestInfo *ri )
{
if( STREQ(uci->SrcAddr,ri->DstAddr,sizeof(uci->SrcAddr) - 1) && (uci->SrcPort == ri->DstPort) )
return OK;
else if ( STREQ(ri->DstAddr,"0.0.0.0",sizeof("0.0.0.0") - 1) && (uci->SrcPort == ri->DstPort) )
return OK;
else
return ERR;
}
S5RetCode S5IfMatch(char ip[16])
{
unsigned int count;
unsigned short ipb_a,
ipb_b,
ipb_c,
ipb_d;
unsigned short ips_a,
ips_b,
ips_c,
ips_d;
unsigned short msk_a,
msk_b,
msk_c,
msk_d;
sscanf((const char *)ip,"%hu.%hu.%hu.%hu",&ipb_a,&ipb_b,&ipb_c,&ipb_d);
for(count=0;count<NInterF;count++) {
sscanf((const char *)S5Interface[count]->IP,"%hu.%hu.%hu.%hu",&ips_a,&ips_b,&ips_c,&ips_d);
sscanf((const char *)S5Interface[count]->NetMask,"%hu.%hu.%hu.%hu",&msk_a,&msk_b,&msk_c,&msk_d);
if( !((ips_a & msk_a) - (ipb_a & msk_a) + (ips_b & msk_b) - (ipb_b & msk_b) + (ips_c & msk_c) - \
(ipb_c & msk_c) + (ips_d & msk_d) - (ipb_d & msk_d)) )
return count;
}
return -1;
}
inline S5RetCode FileCheck( char *group, char *user )
{
FILE *groupFile;
pid_t pid;
char groupFileName[192];
char userName[64];
char logString[128];
/*
* Get child/thread pid
*/
if( NOTTHREADED() )
pid=getpid();
else
pid=(unsigned int)pthread_self();
if( SS5SocksOpt.Profiling == FILE_PROFILING ) {
strncpy(groupFileName,S5ProfilePath,sizeof(groupFileName));
strncat(groupFileName,"/",strlen("/"));
strncat(groupFileName,group,strlen(group));
strncat(groupFileName,"\0",strlen("\0"));
if( (groupFile = fopen(groupFileName,"r")) == NULL ) {
ERRNO(pid)
return ERR;
}
/*
* Check for username into configuration file for access profile
*/
while( fscanf(groupFile,"%64s",userName) != EOF ) {
if( userName[0] != '#' )
if( STRCASEEQ(userName,user,64) ) {
fclose(groupFile);
return OK;
}
}
fclose(groupFile);
return ERR;
}
return ERR;
}
/* ***************************** HASH for ROUTING TABLE **************************** */
inline S5Limit S5RouteHash( unsigned long int sa )
{
return ( sa % MAXROUTELIST );
}
unsigned long int GetRoute(unsigned long int sa, char uname[64])
{
S5Limit index,nm;
S5RetCode err = ERR;
struct _S5RouteNode *node;
unsigned long int n_sa;
for(nm=0;nm<=32;nm +=1) {
if( nm < 32)
n_sa=((sa >> nm) << nm);
else
n_sa=0;
index=S5RouteHash( n_sa );
if( S5RouteList[index] != NULL ) {
node=S5RouteList[index];
do {
if( (node->SrcAddr == n_sa) && (node->Mask == (nm)) ) {
if( node->Group[0] != '-' ) {
/*
* Look for username into group (file or directory) defined in permit line
*/
if( SS5SocksOpt.Profiling == FILE_PROFILING )
err=FileCheck(node->Group,uname);
else if( SS5SocksOpt.Profiling == LDAP_PROFILING )
err=DirectoryCheck(node->Group,uname);
if( err ) {
return node->SrcIf;
}
}
else
return node->SrcIf;
}
node=node->next;
} while(node != NULL );
}
}
return ERR;
}
S5RetCode AddRoute(unsigned long int sa, unsigned long int si, unsigned int mask )
{
int index;
struct _S5RouteNode *node;
index=S5RouteHash( sa );
if( S5RouteList[index]== NULL ) {
S5RouteList[index]=(struct _S5RouteNode *)calloc(1,sizeof(struct _S5RouteNode));
S5RouteList[index]->Mask=mask;
S5RouteList[index]->SrcAddr=sa;
S5RouteList[index]->SrcIf=si;
S5RouteList[index]->next=NULL;
}
else {
node=S5RouteList[index];
while( node->next != NULL ){
node=node->next;
}
node->next=(struct _S5RouteNode *)calloc(1,sizeof(struct _S5RouteNode));
node->next->Mask=mask;
node->next->SrcAddr=sa;
node->next->SrcIf=si;
node->next->next=NULL;
}
return OK;
}
S5RetCode FreeRoute( struct _S5RouteNode **node )
{
struct _S5RouteNode *lnode;
lnode=*node;
while( (lnode) != NULL ) {
while( lnode->next != NULL )
lnode=lnode->next;
free(lnode);
lnode=NULL;
}
*node=NULL;
return OK;
}
S5RetCode S5BrowseRouteList( struct _S5RouteNode *node )
{
struct _S5RouteNode *lnode;
int found=0;
lnode=node;
do {
if(lnode != NULL ) {
printf("Browse: %lu %u %lu\f", lnode->SrcAddr,lnode->Mask,lnode->SrcIf);
found++;
lnode=lnode->next;
}
} while( lnode != NULL );
return found;
}
syntax highlighted by Code2HTML, v. 0.9.1