#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <net/ethernet.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <dnet.h>
#include <pcap.h>
#include <syslog.h>
#include <stdarg.h>
#define BUF_SIZ 256*256
struct sockaddr_in bootp_server;
char errbuf[PCAP_ERRBUF_SIZE];
int open_interfaces(int argc,char **argv);
int create_bootp(char *server);
int create_socket_bootps(void);
int create_socket_answer(void);
int process_bootps(void);
int process_bootps_server(int i,u_char *msgbuf,struct sockaddr_in fromsock,socklen_t socklen);
int process_bootps_client(int len,u_char *data,struct ip *ip);
void pcap_callback(u_char *user, const struct pcap_pkthdr *h, const u_char *sp);
void add_request(char *xid,uint32_t ip);
struct request *find_request(char *xid);
char *print_mac(u_char *s);
char *print_xid(u_char *s);
char *print_ip(u_char *s);
char *print_ip_uint32(uint32_t s);
char *print_dhcptype(u_char *s,int len);
char *print_hostname(u_char *s,int len);
struct request {
char xid[4];
int broadcast;
struct request *next;
};
struct request *requests=NULL;
int DEBUG=0;
int bootpc_port=0;
int bootps_port=0;
struct interface {
int number;
char name[INTF_NAME_LEN];
ip_addr_t ip;
int mask;
eth_addr_t mac;
eth_t *fd;
int fds;
pcap_t *cap;
};
struct interface *interfaces,*interface;
char *wait=".oOo";
int main(int argc,char **argv) {
int waitcounter=0;
while (argc>1 && strcmp(argv[1],"-d")==0) {
DEBUG++;
argv++;
argc--;
}
if (DEBUG==0) {
pid_t p;
if (fork()) {
sleep(3);
exit(0);
}
if ((p=fork())!=0) {
FILE *f;
openlog("dchprelay",LOG_NDELAY|LOG_PID,LOG_DAEMON);
syslog(LOG_NOTICE,"dhcprelay started as process %d",p);
if ((f=fopen("/var/run/dhcprelay.pid","w"))!=NULL) {
fprintf(f,"%d\n",p);
fclose(f);
}
sleep(3);
exit(0);
}
}
sleep(1);
{
struct servent *servent;
if ((servent=getservbyname("bootps",0))==NULL)
errx(1,"getservbyname(%s)","bootps");
bootps_port=servent->s_port;
if ((servent=getservbyname("bootpc",0))==NULL)
errx(1,"getservbyname(%s)","bootpc");
bootpc_port=servent->s_port;
}
openlog("dchprelay",LOG_NDELAY|LOG_PID,LOG_DAEMON);
if (argc==1) {
printf(
"Usage: %s [-d] <interface[:ipaddress]> <dhcpserver>\n"
"Example: %s fxp0 10.192.0.1\n"
"Example: %s fxp0:192.168.1.1 10.192.0.1\n",
argv[0],argv[0],argv[0]
);
return 0;
}
open_interfaces(argc,argv);
create_bootp(argv[argc-1]);
while (1) {
u_char i;
if (DEBUG) fprintf(stderr,"%c",wait[waitcounter++%4]);
i=0;
while (interfaces[i].number) {
interface=&interfaces[i];
pcap_dispatch(interface->cap,1,pcap_callback,NULL);
{
fd_set read;
struct timeval timeout;
int max;
FD_ZERO(&read);
FD_SET(interface->fds,&read);
max=interface->fds;
timeout.tv_sec=0;
timeout.tv_usec=10;
if (select(max+1,&read,NULL,NULL,&timeout)<0) {
errx(1,"select");
}
if (FD_ISSET(interface->fds,&read)) {
process_bootps();
FD_CLR(interface->fds,&read);
}
}
i++;
}
}
closelog();
return 0;
}
void pcap_callback(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) {
struct ether_header *eh;
struct ip *ip;
struct udphdr *udp;
u_char *data;
int offset=0;
if (DEBUG>1)
printf("%s - %d/%d - ",interface->name,h->caplen,h->len);
if (h->caplen<ETHER_HDR_LEN) {
// silently ignore
syslog(LOG_WARNING,"eh:caplen(%d)<%d",h->caplen,ETHER_HDR_LEN);
return;
}
eh=(struct ether_header *)(sp+offset);
offset+=ETHER_HDR_LEN;
if (DEBUG>1) {
printf(" %s",print_mac(eh->ether_dhost));
printf(" %s",print_mac(eh->ether_shost));
printf(" %d",eh->ether_type);
}
// check for IPv4 packets
if (eh->ether_type!=8) {
if (DEBUG>1) printf("\n");
return;
}
if (h->caplen<offset+sizeof(struct ip)) {
// silently ignore
syslog(LOG_WARNING,"ip:caplen(%d)<%d",
h->caplen,offset+sizeof(struct ip)
);
return;
}
ip=(struct ip *)(sp+offset);
offset+=sizeof(struct ip);
if (DEBUG>1) {
printf(" - %s",print_ip_uint32((uint32_t)(ip->ip_src.s_addr)));
printf(" %s",print_ip_uint32((uint32_t)(ip->ip_dst.s_addr)));
}
if (ip->ip_p!=IPPROTO_UDP) {
if (DEBUG>1) printf("\n");
return;
}
if (h->caplen<offset+sizeof(struct udphdr)) {
// silently ignore
syslog(LOG_WARNING,"udp:caplen(%d)<%d",
h->caplen,offset+sizeof(struct udphdr)
);
return;
}
udp=(struct udphdr *)(sp+offset);
offset+=sizeof(struct udphdr);
data=(u_char *)(sp+offset);
if (DEBUG>2) {
printf(" - %d %d %d",data[0],data[1],data[2]);
printf("\n");
}
if (h->caplen<offset+240) {
// silently ignore
syslog(LOG_WARNING,"dhcp:caplen(%d)<%d",
h->caplen,offset+240
);
return;
}
if (DEBUG>1)
printf("\n");
process_bootps_client(h->caplen-offset,data,ip);
}
int open_interfaces(int argc,char **argv) {
int i;
intf_t *intf;
interfaces=(struct interface *)calloc(argc,sizeof(struct interface));
intf=intf_open();
for (i=1;i<argc-1;i++) {
char *ifname;
char *ipaddress=NULL;
char *p;
ifname=argv[i];
if ((p=strchr(argv[i],':'))!=NULL) {
*p=0;
p++;
ipaddress=p;
}
{
struct intf_entry ifentry;
ip_addr_t ip_src;
char msg[BUF_SIZ];
memset(&ifentry,0,sizeof(struct intf_entry));
strcpy(ifentry.intf_name,ifname);
intf_get(intf,&ifentry);
interfaces[i-1].number=i;
strcpy(interfaces[i-1].name,argv[i]);
if (ipaddress) {
struct hostent *hostent;
if ((hostent=gethostbyname(ipaddress))==NULL)
errx(1,"gethostbyname(%s)\n",ipaddress);
memcpy(&interfaces[i-1].ip,hostent->h_addr,hostent->h_length);
} else {
ip_src=ifentry.intf_addr.addr_ip;
memcpy(&interfaces[i-1].ip,&ip_src,sizeof(ip_addr_t));
}
interfaces[i-1].mask=ifentry.intf_addr.addr_bits;
memcpy(&interfaces[i-1].mac,&ifentry.intf_link_addr.addr_eth,sizeof(eth_addr_t));
interfaces[i-1].fd=eth_open(ifname);
sprintf(msg,"Opening %s %s/%d, %s",
ifname,
print_ip((u_char *)&interfaces[i-1].ip),
interfaces[i-1].mask,
print_mac((u_char *)&interfaces[i-1].mac));
syslog(LOG_NOTICE,"%s",msg);
fprintf(stderr,"%s\n",msg);
}
{
pcap_t *cap;
struct bpf_program fp;
if ((cap=pcap_open_live(ifname,1500,1,100,errbuf))==NULL)
errx(1,"pcap_open_live(%s): %s",ifname,errbuf);
if (pcap_compile(cap,&fp,"udp and (port bootpc or port bootps)",0,0)<0)
errx(1,"pcap_compile: %s",pcap_geterr(cap));
if (pcap_setfilter(cap,&fp)<0)
errx(1,"pcap_setfilter: %s",pcap_geterr(cap));
if (pcap_setnonblock(cap,0,errbuf)<0)
errx(1,"pcap_setnonblock(%s): %s",ifname,pcap_geterr(cap));
interfaces[i-1].cap=cap;
}
{
int x;
struct sockaddr_in name;
int fd;
if (DEBUG>1) printf("Creating bootps socket.\n");
if ((fd=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
errx(1,"socket");
x=1;
if (setsockopt(fd,SOL_SOCKET,SO_BROADCAST,(char *)&x,sizeof(x))<0)
errx(1,"setsockopt: SO_BROADCAST");
if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&x,sizeof(x))<0)
errx(1,"setsockopt: SO_REUSEADDR");
name.sin_family = AF_INET;
name.sin_port = bootps_port;
memcpy(&name.sin_addr.s_addr,&interfaces[i-1].ip,4);
if (bind(fd,(struct sockaddr *)&name,sizeof name)<0)
errx(1,"bind failed.");
interfaces[i-1].fds=fd;
}
}
intf_close(intf);
return 0;
}
int create_bootp(char *server) {
struct hostent *hostent;
if ((hostent=gethostbyname(server))==NULL)
errx(1,"gethostbyname(%s)",server);
bootp_server.sin_family=AF_INET;
bootp_server.sin_port=bootps_port;
memcpy(&bootp_server.sin_addr.s_addr,hostent->h_addr,hostent->h_length);
return 0;
}
int process_bootps() {
u_char msgbuf[BUF_SIZ];
struct sockaddr_in fromsock;
socklen_t fromlen=sizeof(fromsock);
int i;
if (DEBUG>1) printf("process_bootps\n");
i=recvfrom(interface->fds,msgbuf,BUF_SIZ,0,(struct sockaddr *)&fromsock,&fromlen);
// silently ignore requests, they are handled via pcap
if (msgbuf[0]!=2)
return 0;
if (DEBUG>1) printf("%d bytes from %s\n",i,print_ip((u_char *)&fromsock.sin_addr));
process_bootps_server(i,msgbuf,fromsock,fromlen);
if (DEBUG>1) printf("\n");
return 0;
}
int process_bootps_server(int msgbuf_len,u_char *msgbuf,struct sockaddr_in fromsock,socklen_t socklen) {
char packet[2*BUF_SIZ];
struct request *r;
int packet_offset=0;
struct in_addr *in;
struct ether_header *eh;
int eh_len;
struct ip *ip;
int ip_len;
struct udphdr *udp;
int udp_len;
char *hostname;
char syslogmsg[BUF_SIZ];
int result;
if (DEBUG>1) printf("process_bootps_server\n");
in=(struct in_addr *)(msgbuf+16);
r=find_request(msgbuf+4);
if (r==NULL) {
printf("Question not found (%s)\n",print_xid(msgbuf));
return 0;
}
hostname=print_hostname(msgbuf,msgbuf_len);
sprintf(syslogmsg,"%s from %s for %s%s%s%s",
print_dhcptype(msgbuf,msgbuf_len),
print_ip((u_char *)&fromsock.sin_addr),
print_mac(msgbuf+28),
hostname==NULL?"":" (",
hostname==NULL?"":hostname,
hostname==NULL?"":")"
);
sprintf(syslogmsg+strlen(syslogmsg)," to %s/%s",
interface->name,
print_ip((u_char *)in)
);
if (r->broadcast)
sprintf(syslogmsg+strlen(syslogmsg)," via 255.255.255.255");
syslog(LOG_INFO,"%s",syslogmsg);
if (DEBUG) printf("%s\n",syslogmsg);
{
eh=(struct ether_header *)(packet+packet_offset);
memcpy(eh->ether_dhost,msgbuf+28,ETHER_ADDR_LEN);
memcpy(eh->ether_shost,&(interfaces->mac),ETHER_ADDR_LEN);
eh->ether_type=htons(ETHERTYPE_IP);
eh_len=ETHER_HDR_LEN;
packet_offset+=eh_len;
}
{
ip_addr_t ip_src=interface->ip;
ip=(struct ip *)(packet+packet_offset);
ip->ip_v=4;
ip->ip_hl=20/4;
ip->ip_tos=IPTOS_LOWDELAY;
ip->ip_len=htons(sizeof(struct ip)+sizeof(struct udphdr)+msgbuf_len);
ip->ip_id=0;
ip->ip_off=0;
ip->ip_ttl=16;
ip->ip_p=IPPROTO_UDP;
ip->ip_sum=0;
memcpy(&ip->ip_src,&ip_src,4);
if (r->broadcast)
ip->ip_dst.s_addr=INADDR_BROADCAST;
else
memcpy(&ip->ip_dst,in,4);
ip_len=sizeof(struct ip);
packet_offset+=ip_len;
}
{
udp=(struct udphdr *)(packet+packet_offset);
udp->uh_sport=bootps_port;
udp->uh_dport=bootpc_port;
udp->uh_ulen=htons(sizeof(struct udphdr)+msgbuf_len);
udp->uh_sum=0;
udp_len=sizeof(struct udphdr);
packet_offset+=udp_len;
}
memcpy(packet+packet_offset,msgbuf,msgbuf_len);
packet_offset+=msgbuf_len;
ip_checksum(ip,ip_len+udp_len+msgbuf_len);
result=eth_send(interface->fd,packet,packet_offset);
if (result==-1)
warnx("Not everything was sent");
return 0;
}
int process_bootps_client(int len,u_char *data,struct ip *ip) {
char *hostname;
char syslogmsg[BUF_SIZ];
if (DEBUG>1) printf("process_bootps_client\n");
// ignore replies
if (data[0]!=1) return 0;
data[3]++; // hops
memcpy(data+24,&interface->ip,4); // GIADDR
hostname=print_hostname(data,len);
sprintf(syslogmsg,"%s from %s/%s for %s%s%s%s",
print_dhcptype(data,len),
interface->name,
print_ip_uint32((uint32_t)(ip->ip_src.s_addr)),
print_mac(data+28),
hostname==NULL?"":" (",
hostname==NULL?"":hostname,
hostname==NULL?"":")"
);
sprintf(syslogmsg+strlen(syslogmsg)," to %s",
print_ip_uint32((uint32_t)(ip->ip_dst.s_addr))
);
sprintf(syslogmsg+strlen(syslogmsg)," forwarded to %s",
print_ip((u_char *)&bootp_server.sin_addr)
);
syslog(LOG_INFO,"%s",syslogmsg);
if (DEBUG) printf("%s\n",syslogmsg);
sendto(interface->fds,data,len,0,(struct sockaddr *)&bootp_server,sizeof(bootp_server));
add_request(data+4,(uint32_t)(ip->ip_dst.s_addr));
return 0;
}
void add_request(char *xid,uint32_t ip) {
struct request *r;
r=find_request(xid);
if (r==NULL) {
r=(struct request *)calloc(1,sizeof(struct request));
memcpy(r->xid,xid,4);
r->next=requests;
requests=r;
}
if (ip==-1) r->broadcast=1;
}
struct request *find_request(char *xid) {
struct request *r;
for (r=requests;r!=NULL;r=r->next) {
if (memcmp(r->xid,xid,4)==0) return r;
}
return NULL;
}
char *print_xid(u_char *s) {
static char buffer[BUF_SIZ];
memset(buffer,0,BUF_SIZ);
sprintf(buffer,"%02x%02x%02x%02x",
s[0],s[1],s[2],s[3]);
return buffer;
}
char *print_mac(u_char *s) {
static char buffer[BUF_SIZ];
memset(buffer,0,BUF_SIZ);
sprintf(buffer,"%02x:%02x:%02x:%02x:%02x:%02x",
s[0],s[1],s[2],s[3],s[4],s[5]);
return buffer;
}
char *print_ip(u_char *s) {
static char buffer[BUF_SIZ];
memset(buffer,0,BUF_SIZ);
sprintf(buffer,"%d.%d.%d.%d",s[0],s[1],s[2],s[3]);
return buffer;
}
char *print_ip_uint32(uint32_t s) {
static char buffer[BUF_SIZ];
u_char b[4];
memset(buffer,0,BUF_SIZ);
memcpy(b,&s,4);
sprintf(buffer,"%u.%u.%u.%u",b[0],b[1],b[2],b[3]);
return buffer;
}
u_char *find_option(u_char *s,int maxlen,int type) {
int len=maxlen;
while (s[0]!=0 && len>=0) {
if (s[0]==type) { // DHCP Message Type
return s;
}
s+=2+s[1];
len-=2+s[1];
}
return NULL;
}
char *print_dhcptype(u_char *s,int len) {
static char buffer[BUF_SIZ];
static char *types[8]={
"DISCOVER",
"OFFER",
"REQUEST",
"DECLINE",
"ACK",
"NAK",
"RELEASE",
"INFORM"
};
memset(buffer,0,BUF_SIZ);
s+=240;
if ((s=find_option(s,len-240,53))==NULL) {
strcpy(buffer,"not-found");
return buffer;
}
if (s[2]>=1 && s[2]<=8)
strcpy(buffer,types[s[2]-1]);
else
sprintf(buffer,"UNKOWN-%d",s[2]);
return buffer;
}
char *print_hostname(u_char *s,int len) {
static char buffer[BUF_SIZ];
memset(buffer,0,BUF_SIZ);
s+=240;
if ((s=find_option(s,len-240,12))==NULL) return NULL;
strncpy(buffer,s+2,s[1]);
return buffer;
}
syntax highlighted by Code2HTML, v. 0.9.1