/*******************************************************************************/
/* */
/* Copyright 2004 Pascal Gloor */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); */
/* you may not use this file except in compliance with the License. */
/* You may obtain a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* */
/*******************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <p_defs.h>
#include <p_piranha.h>
#include <p_log.h>
#include <p_config.h>
#include <p_socket.h>
#include <p_dump.h>
/* init the global structures */
struct config_t config;
struct peer_t peer[MAX_PEERS];
struct timeval ts;
/* 00 BEGIN ;) */
int main(int argc, char *argv[])
{
#ifdef DEBUG
/* say hello */
printf("Piranha v%s.%s.%s BGP Daemon, Copyright(c) 2004 Pascal Gloor\n",P_VER_MA,P_VER_MI,P_VER_PL);
#endif
/* set initial time */
gettimeofday(&ts,NULL);
{
char logline[100];
snprintf(logline, sizeof(logline), "Piranha v%s.%s.%s started.\n",P_VER_MA,P_VER_MI,P_VER_PL);
p_log_add((time_t)ts.tv_sec, logline);
}
/* check the cmd line options */
if ( argc != 2 ) { p_main_syntax(argv[0]); return -1; }
/* init some stuff and load the config */
config.file = argv[1];
if ( p_config_load((struct config_t*)&config,(struct peer_t*)peer, (time_t)ts.tv_sec) == -1 )
{ fprintf(stderr,"error while parsing configuration file %s\n", config.file); return -1; }
/* set config reload for signal HUP */
signal(SIGHUP, p_main_sighup);
/* init the socket */
if ( p_socket_start((struct config_t*)&config) == -1 )
{
fprintf(stderr,"socket error, aborting\n");
return -1;
}
#ifndef DEBUG
/* we dont use daemon() here, it doesnt exist on solaris/suncc ;-) */
/* daemon(1,0); */
if ( mydaemon(1,0) ) { fprintf(stderr,"daemonization error.\n"); }
#endif
/* log the pid */
p_log_pid();
while ( p_main_loop() == 0 )
{
#ifdef DEBUG
printf("accept() loop\n");
#endif
/* we want to sessions to come up slowly */
/* therefor we sleep a bit here. */
sleep(1);
p_log_status((struct config_t*)&config,(struct peer_t*)peer, (time_t)ts.tv_sec);
/* we update a global var with the actual timestamp */
gettimeofday(&ts,NULL);
}
#ifdef DEBUG
printf("accept() failed, aborting\n");
#endif
return -1;
}
/* check for accept() and start thread() */
int p_main_loop()
{
int sock;
if ( ( sock = p_socket_accept((struct config_t*)&config) ) == -1 )
{
return 0;
}
else
{
pthread_t thread;
int *mysock;
mysock = malloc(sizeof(sock));
memcpy(mysock,&sock,sizeof(mysock));
pthread_create(&thread, NULL, p_main_peer, (void *)mysock);
}
return 0;
}
/* peer thread */
void *p_main_peer(void *data)
{
int sock;
int a;
int allow = 0;
int peerid = -1;
struct sockaddr_in sockaddr;
unsigned int socklen = sizeof(sockaddr);
memcpy(&sock,data,sizeof(sock));
#ifdef DEBUG
printf("I'm thready, my socket is %i\n",sock);
#endif
if ( getpeername(sock,(struct sockaddr*)&sockaddr, &socklen) == -1 )
{
#ifdef DEBUG
printf("failed to get peeraddr!\n");
#endif
p_main_peer_exit(data,sock);
}
#ifdef DEBUG
printf("peer ip is: %s\n",inet_ntoa(sockaddr.sin_addr));
#endif
/* does the peer exist? */
for(a=0; a<MAX_PEERS; a++)
{
if ( peer[a].ip == sockaddr.sin_addr.s_addr && peer[a].allow == 1 )
{
char logline[100];
if ( peer[a].status == 0 )
{
snprintf(logline,sizeof(logline), "%s connection (known)\n",inet_ntoa(sockaddr.sin_addr));
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUG
printf("peer ip allowed id %i\n",a);
#endif
allow = 1;
peer[a].sock = sock;
peer[a].status = 1;
peer[a].rhold = BGP_DEFAULT_HOLD;
peer[a].shold = BGP_DEFAULT_HOLD;
peer[a].ilen = 0;
peer[a].olen = 0;
peer[a].rts = ts.tv_sec;
peer[a].sts = ts.tv_sec;
peer[a].cts = ts.tv_sec;
peer[a].rmsg = 0;
peer[a].smsg = 0;
peer[a].filets = 0;
peer[a].fh = NULL;
peer[a].ucount = 0;
peerid = a;
}
else
{
snprintf(logline, sizeof(logline), "%s connection (already connected)\n", inet_ntoa(sockaddr.sin_addr));
}
break;
}
}
if ( allow == 0 )
{
char logline[100];
snprintf(logline, sizeof(logline), "%s connection (unknown)\n",inet_ntoa(sockaddr.sin_addr));
p_log_add((time_t)ts.tv_sec, logline);
p_main_peer_exit(data,sock);
}
#ifdef DEBUG
printf("peerid %i\n",peerid);
#endif
p_main_peer_loop(peerid);
p_dump_add_close(peer, peerid, ts.tv_sec);
/* peer disconnected, lets wait a bit to avoid direct reconnection */
/* we'll wait DUMPINTERVAL time! */
peer[peerid].status = 1;
sleep(DUMPINTERVAL);
peer[peerid].status = 0;
p_main_peer_exit(data, sock);
return NULL;
}
/* peer looop */
void p_main_peer_loop(int id)
{
char logline[100];
struct in_addr addr;
char *ibuf;
char *obuf;
uint8_t marker[16];
{ int a; for(a=0; a<sizeof(marker); a++) { marker[a] = 0xff; } }
peer[id].ilen = 0;
peer[id].olen = 0;
addr.s_addr = peer[id].ip;
ibuf = malloc(INPUT_BUFFER);
obuf = malloc(OUTPUT_BUFFER);
#ifdef DEBUG
printf("peer status %u\n",peer[id].status);
printf("starting peer loop\n");
#endif
p_main_peer_open(id, obuf);
while((peer[id].status>0))
{
/* receiving new datas, sleep 1sec if nothing */
int tlen = 0;
int maxlen = INPUT_BUFFER - peer[id].ilen;
if ( TEMP_BUFFER < maxlen ) { maxlen = TEMP_BUFFER; }
tlen = recv(peer[id].sock, ibuf+peer[id].ilen, INPUT_BUFFER-peer[id].ilen, 0);
if ( tlen == -1 )
{
p_dump_check_file(peer,id,ts.tv_sec);
sleep(1);
}
else if ( tlen > 0 )
{
#ifdef DEBUG
printf("got data\n");
#endif
peer[id].ilen += tlen;
}
else
{
snprintf(logline, sizeof(logline), "%s socket went down\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUG
printf("something failed on recv()\n");
#endif
peer[id].status = 0;
}
/* working on datas */
if ( peer[id].ilen > 0 )
{
p_main_peer_work(ibuf, obuf, id);
}
/* check if the peer timed out (note: 0 == no keepalive!) */
if ( peer[id].rhold != 0 && ( (ts.tv_sec - peer[id].rts) > peer[id].rhold ) )
{
/* timeout ;( */
snprintf(logline, sizeof(logline), "%s holdtime expired\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
}
/* time to send a keepalive message ? (note: 0 == no keepalive!) */
if ( peer[id].shold != 0 && ( (ts.tv_sec - peer[id].sts) > (peer[id].shold / 3) ) )
{
/* yeah */
struct bgp_header r_header;
memcpy(&r_header, marker, sizeof(marker));
r_header.len = htons(BGP_HEADER_LEN);
r_header.type = 4;
memcpy(obuf+peer[id].olen, &r_header, BGP_HEADER_LEN);
peer[id].olen += BGP_HEADER_LEN;
peer[id].sts = ts.tv_sec;
peer[id].smsg++;
p_main_peer_send(id, obuf);
}
/* sending */
while((peer[id].olen>0 && peer[id].status))
{
peer[id].smsg++;
p_main_peer_send(id, obuf);
if ( peer[id].olen == -1 )
{
#ifdef DEBUG
printf("failed to send!\n");
#endif
snprintf(logline, sizeof(logline), "%s failed to send\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
}
}
if ( peer[id].olen == -1 )
{
printf("%u error while sending\n",peer[id].ip);
}
}
free(ibuf);
free(obuf);
snprintf(logline, sizeof(logline), "%s down\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].cts = ts.tv_sec;
#ifdef DEBUG
printf("peer is gone\n");
#endif
}
/* sending BGP open and first keepalive */
void p_main_peer_open(int id, char *obuf)
{
/* reply with my open msg */
struct bgp_header r_header;
struct bgp_open r_open;
uint8_t marker[16];
{ int a; for(a=0; a<sizeof(marker); a++) { marker[a] = 0xff; } }
memcpy(r_header.marker, marker, sizeof(r_header.marker));
r_header.len = htons(BGP_HEADER_LEN+BGP_OPEN_LEN);
r_header.type = 1;
r_open.version = 4;
r_open.as = htons(config.as);
r_open.holdtime = htons(peer[id].shold);
r_open.bgp_id = htonl(config.routerid);
r_open.param_len = 0;
memcpy(obuf+peer[id].olen,&r_header, BGP_HEADER_LEN);
peer[id].olen += BGP_HEADER_LEN;
memcpy(obuf+peer[id].olen,&r_open, BGP_OPEN_LEN);
peer[id].olen += BGP_OPEN_LEN;
peer[id].smsg++;
p_main_peer_send(id,obuf);
/* we add directly the first keepalive msg */
r_header.len = htons(BGP_HEADER_LEN);
r_header.type = 4;
memcpy(obuf+peer[id].olen, &r_header, BGP_HEADER_LEN);
peer[id].olen += BGP_HEADER_LEN;
/* update the keepalive sent timestamp */
peer[id].sts = ts.tv_sec;
/* send the packet */
peer[id].smsg++;
p_main_peer_send(id,obuf);
}
/* bgp decoding stuff */
void p_main_peer_work(char *ibuf, char *obuf, int id)
{
char logline[100];
uint8_t marker[16];
struct in_addr addr;
{ int a; for(a=0; a<sizeof(marker); a++) { marker[a] = 0xff; } }
addr.s_addr = peer[id].ip;
/* show packet */
#ifdef DEBUG
{
int a;
for(a=0; a<peer[id].ilen; a++)
{
if ( !(a%8) ) { printf("\n"); }
printf("%3u ",(uint8_t)*(ibuf+a));
}
printf("\n");
}
#endif
while(1)
{
int pos = 0;
struct bgp_header *header;
if ( peer[id].ilen < sizeof(struct bgp_header) )
{
#ifdef DEBUG
printf("not all header\n");
#endif
return;
}
header = (struct bgp_header*)ibuf;
if ( memcmp(header->marker, marker, sizeof(marker)) != 0 )
{
snprintf(logline, sizeof(logline), "%s packet decoding error\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUG
printf("invalid marker\n");
#endif
peer[id].status = 0;
return;
}
#ifdef DEBUG
printf("len: %u type: %u (buffer %u)\n",htons(header->len),header->type,peer[id].ilen);
#endif
if ( peer[id].ilen < htons(header->len) )
{
#ifdef DEBUG
printf("bgp message not complete msg len %u, buffer len %u\n",htons(header->len),peer[id].ilen);
#endif
return;
}
pos += BGP_HEADER_LEN;
peer[id].rmsg++;
peer[id].rts = ts.tv_sec;
if ( header->type == 1 && peer[id].status == 1 )
{
struct bgp_open *bopen;
bopen = (struct bgp_open*) (ibuf + pos);
if ( bopen->version != 4 )
{
snprintf(logline, sizeof(logline), "%s wrong bgp version (%u)\n",inet_ntoa(addr),bopen->version);
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUB
printf("invalid BGP version %u\n",bopen->version);
#endif
peer[id].status = 0;
return;
}
#ifdef DEBUG
printf("BGP Version: %u\n",bopen->version);
#endif
if ( htons(bopen->as) != peer[id].as )
{
snprintf(logline, sizeof(logline), "%s wrong neighbor as (%u != %u)\n",inet_ntoa(addr),htons(bopen->as),peer[id].as);
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUG
printf("invalid BGP neighbor AS %u\n",htons(bopen->as));
#endif
peer[id].status = 0;
return;
}
peer[id].rhold = htons(bopen->holdtime);
if ( peer[id].rhold < peer[id].shold )
{
peer[id].shold = peer[id].rhold;
}
if ( htons(header->len) != BGP_HEADER_LEN + BGP_OPEN_LEN + bopen->param_len )
{
snprintf(logline, sizeof(logline), "%s parameter parsing error)\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
#ifdef DEBUG
printf("size error in bgp open params, len %u, header %u open %u param %u\n",htons(header->len),BGP_HEADER_LEN,BGP_OPEN_LEN,bopen->param_len);
#endif
peer[id].status = 0;
return;
}
pos += BGP_OPEN_LEN;
while(pos<htons(header->len))
{
struct bgp_param *param;
param = (struct bgp_param*) (ibuf+pos);
#ifdef DEBUG
printf("param code %u len %u\n",param->type,param->len);
{ int a; for(a=0; a<param->len; a++) { printf("%u ",(uint8_t)param->param[a]); } printf("\n"); }
#endif
pos += 2;
pos += param->len;
if ( param->type > 4 )
{
#ifdef DEBUG
printf("parameter rejected!\n");
#endif
peer[id].status = 0;
return;
}
}
snprintf(logline, sizeof(logline), "%s established\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 2;
p_dump_add_open(peer, id, ts.tv_sec);
}
else if ( header->type == 2 && peer[id].status == 2 )
{
/* BGP update */
uint16_t wlen;
uint16_t alen;
void *aspath = NULL;
uint16_t aspathlen = 0;
void *community = NULL;
uint16_t communitylen = 0;
wlen = *(uint16_t *) (ibuf + pos);
wlen = htons(wlen);
pos += 2;
#ifdef DEBUG
printf("Withdrawn Length %u\n",wlen);
#endif
while(wlen>0)
{
uint8_t plen;
uint32_t prefix;
plen = *(uint8_t *) (ibuf + pos);
pos++;
wlen--;
memcpy(&prefix, ibuf+pos, sizeof(prefix));
prefix = ntohl(prefix);
if ( plen > 24 )
{
pos+= 4;
wlen-=4;
}
else if ( plen > 16 )
{
pos+= 3;
wlen-=3;
/* prefix = (prefix >> 8) << 8; */
}
else if ( plen > 8 )
{
pos+= 2;
wlen-=2;
/* prefix = (prefix >> 16) << 16; */
}
else if ( plen > 0 )
{
pos++;
wlen--;
/* prefix = (prefix >> 24) << 24; */
}
else
{
/* prefix = 0; */
}
{
#ifdef DEBUG
addr.s_addr = prefix;
printf("withdrawn %s/%u\n",inet_ntoa(addr),plen);
#endif
p_dump_add_withdrawn(peer,id,ts.tv_sec,prefix,plen);
peer[id].ucount++;
}
}
alen = *(uint16_t *) (ibuf + pos);
alen = htons(alen);
#ifdef DEBUG
printf("announce size: %u\n",alen);
#endif
pos+=2;
while(alen>0)
{
uint8_t flags = *(uint8_t*) (ibuf+pos);
uint8_t code = *(uint8_t*) (ibuf+pos+1);
uint16_t codelen;
alen-=2;
pos+=2;
#ifdef DEBUG
if ( flags & 0x80 ) { printf("attr is optional\n"); } else { printf("attr is well-known\n"); }
if ( flags & 0x40 ) { printf("attr is transitive\n"); } else { printf("attr is non-transitive\n"); }
if ( flags & 0x20 ) { printf("attr is partial\n"); } else { printf("attr is complete\n"); }
if ( flags & 0x10 ) { printf("attr is 2 octet\n"); } else { printf("attr is 1 octets\n"); }
printf("code: %x\n",code);
#endif
if ( flags & 0x10 )
{
uint16_t clen = *(uint16_t*) (ibuf+pos);
clen = htons(clen);
pos+= 2;
alen-= 2;
codelen = clen;
}
else
{
uint8_t clen = *(uint8_t*) (ibuf+pos);
pos++;
alen--;
codelen = clen;
}
#ifdef DEBUG
printf("length: %u\n",codelen);
#endif
if ( code == 2 )
{
uint8_t aspath_type = *(uint8_t*) (ibuf+pos);
uint8_t aspath_len = *(uint8_t*) (ibuf+pos+1);
if ( codelen == 0 )
{
#ifdef DEBUG
printf("Empty ASPATH (iBGP)\n");
aspathlen = 0;
#endif
}
else if ( aspath_type == 1 )
{
#ifdef DEBUG
printf("AS_SET %u\n",aspath_len);
#endif
}
else if ( aspath_type == 2 )
{
#ifdef DEBUG
printf("AS_PATH %u\n",aspath_len);
{
int a;
for(a=0; a<aspath_len; a++)
{
uint16_t toto = *(uint16_t*)(ibuf+pos+2+(a*2));
printf("%5u ",ntohs(toto));
}
printf("\n");
}
#endif
aspath = (void *)(ibuf+pos+2);
aspathlen = aspath_len;
}
else
{
#ifdef DEBUG
printf("error in aspath code, type %u unknown (len %u)\n",aspath_type,aspath_len);
#endif
snprintf(logline, sizeof(logline), "%s error in aspath code\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
return;
}
}
else if ( code == 8 )
{
if ( (codelen % 4) != 0 )
{
snprintf(logline, sizeof(logline), "%s error in community length\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
return;
}
community = (void *) (ibuf+pos);
communitylen = codelen / 4;
}
pos+=codelen;
alen-=codelen;
}
while((htons(header->len) - pos))
{
uint8_t plen = *(uint8_t *) (ibuf+pos);
uint32_t prefix = *(uint32_t *) (ibuf+pos+1);
pos++;
prefix = ntohl(prefix);
if ( plen > 24 )
{
pos+= 4;
}
else if ( plen > 16 )
{
pos+= 3;
/* prefix &= 0xffffff00; */
}
else if ( plen > 8 )
{
pos+= 2;
/* prefix &= 0xffff0000; */
}
else if ( plen > 0 )
{
pos++;
/* prefix &= 0xff000000; */
}
else
{
prefix = 0;
}
{
#ifdef DEBUG
addr.s_addr = htonl(prefix);
printf("announce %s/%u\n",inet_ntoa(addr),plen);
#endif
p_dump_add_announce(peer,id,ts.tv_sec,prefix,plen,aspath,aspathlen,community,communitylen);
peer[id].ucount++;
}
}
}
else if ( header->type == 3 )
{
/* bgp error msg */
struct bgp_error *error;
error = (struct bgp_error*) (ibuf + pos);
#ifdef DEBUG
printf("error code : %i/%i\n",error->code,error->subcode);
#endif
pos += BGP_ERROR_LEN;
snprintf(logline, sizeof(logline), "%s notification received code %u/%u\n",inet_ntoa(addr),error->code,error->subcode);
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
}
else if ( header->type == 4 && peer[id].status == 2 )
{
/* keepalive packet */
peer[id].rts = ts.tv_sec;
p_dump_add_keepalive(peer, id, ts.tv_sec);
#ifdef DEBUG
printf("received keepalive\n");
#endif
}
else
{
#ifdef DEBUG
printf("invalid header type %u\n",header->type);
#endif
snprintf(logline, sizeof(logline), "%s invalid message type\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
return;
}
if ( htons(header->len) != pos )
{
#ifdef DEBUG
printf("something went wrong with the packet size\n");
#endif
snprintf(logline, sizeof(logline), "%s error in packet size\n",inet_ntoa(addr));
p_log_add((time_t)ts.tv_sec, logline);
peer[id].status = 0;
return;
}
if ( peer[id].ilen <= htons(header->len) )
{
peer[id].ilen = 0;
return;
}
#ifdef DEBUG
printf("still got datas!\n");
#endif
memmove(ibuf, ibuf+htons(header->len), peer[id].ilen-htons(header->len));
peer[id].ilen -= pos;
}
}
/* send() */
void p_main_peer_send(int id, char *obuf)
{
/* sending datas */
if ( peer[id].olen > 0 )
{
int slen = 0;
#ifdef DEBUG
printf("something to send\n");
#endif
slen = send(peer[id].sock, obuf, peer[id].olen, 0);
if ( slen == peer[id].olen )
{
#ifdef DEBUG
printf("send ok\n");
#endif
peer[id].olen = 0;
}
else if ( slen == 0 )
{
#ifdef DEBUG
printf("couldnt send anything\n");
#endif
peer[id].olen = -1;
}
else if ( slen == -1 )
{
#ifdef DEBUG
printf("failed to send()\n");
#endif
peer[id].olen = -1;
}
else if ( slen < peer[id].olen )
{
#ifdef DEBUG
printf("cound not send all\n");
#endif
memmove(obuf, obuf+slen, peer[id].olen-slen);
peer[id].olen -= slen;
}
else
{
#ifdef DEBUG
printf("impossible send() case!\n");
#endif
peer[id].olen = -1;
}
}
return;
}
/* peer exit, thread exit */
void p_main_peer_exit(void *data, int sock)
{
#ifdef DEBUG
printf("dead thready with socket %i\n",sock);
#endif
close(sock);
free(data);
pthread_exit(NULL);
}
/* syntax */
void p_main_syntax(char *prog)
{
printf("syntax: %s <configuration file>\n",prog);
}
/* kill -HUP for config reload */
void p_main_sighup(int sig)
{
if ( p_config_load((struct config_t*)&config,(struct peer_t*)peer, (time_t)ts.tv_sec) == -1 )
{
#ifdef DEBUG
printf("failed to reload config!\n");
#else
p_log_add((time_t)ts.tv_sec, "failed to reload configuration\n");
#endif
exit(1);
}
p_log_add((time_t)ts.tv_sec, "configuration reloaded\n");
signal(sig,p_main_sighup);
}
int mydaemon(int nochdir, int noclose)
{
int fd;
/* set new uid/gid */
setuid(config.uid);
seteuid(config.uid);
setgid(config.gid);
setegid(config.gid);
switch (fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1)
return (-1);
if (!nochdir)
(void)chdir("/");
if ( !noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1)
{
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)close(fd);
}
return(0);
}