mirror of
https://github.com/bol-van/zapret.git
synced 2024-12-24 23:26:42 +00:00
497 lines
11 KiB
C
497 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
#include <linux/tcp.h>
|
|
//#include <netinet/in.h>
|
|
#include <linux/types.h>
|
|
#include <linux/netfilter.h> /* for NF_ACCEPT */
|
|
#include <libnetfilter_queue/libnetfilter_queue.h>
|
|
#include <getopt.h>
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
|
|
bool proto_check_ipv4(unsigned char *data,int len)
|
|
{
|
|
return len>=20 && (data[0] & 0xF0)==0x40 &&
|
|
len>=((data[0] & 0x0F)<<2);
|
|
}
|
|
// move to transport protocol
|
|
void proto_skip_ipv4(unsigned char **data,int *len)
|
|
{
|
|
int l;
|
|
|
|
l = (**data & 0x0F)<<2;
|
|
*data += l;
|
|
*len -= l;
|
|
}
|
|
bool proto_check_tcp(unsigned char *data,int len)
|
|
{
|
|
return len>=20 && len>=((data[12] & 0xF0)>>2);
|
|
}
|
|
void proto_skip_tcp(unsigned char **data,int *len)
|
|
{
|
|
int l;
|
|
l = ((*data)[12] & 0xF0)>>2;
|
|
*data += l;
|
|
*len -= l;
|
|
}
|
|
|
|
bool proto_check_ipv6(unsigned char *data,int len)
|
|
{
|
|
return len>=40 && (data[0] & 0xF0)==0x60 &&
|
|
(len-40)>=htons(*(uint16_t*)(data+4)); // payload length
|
|
}
|
|
// move to transport protocol
|
|
// proto_type = 0 => error
|
|
void proto_skip_ipv6(unsigned char **data,int *len,uint8_t *proto_type)
|
|
{
|
|
int hdrlen;
|
|
uint8_t HeaderType;
|
|
|
|
*proto_type = 0; // put error in advance
|
|
|
|
HeaderType = (*data)[6]; // NextHeader field
|
|
*data += 40; *len -= 40; // skip ipv6 base header
|
|
while(*len>0) // need at least one byte for NextHeader field
|
|
{
|
|
switch(HeaderType)
|
|
{
|
|
case 0: // Hop-by-Hop Options
|
|
case 60: // Destination Options
|
|
case 43: // routing
|
|
if (*len<2) return; // error
|
|
hdrlen = 8+((*data)[1]<<3);
|
|
break;
|
|
case 44: // fragment
|
|
hdrlen = 4;
|
|
break;
|
|
case 59: // no next header
|
|
return; // error
|
|
default:
|
|
// we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit
|
|
*proto_type = HeaderType;
|
|
return;
|
|
}
|
|
if (*len<hdrlen) return; // error
|
|
HeaderType = **data;
|
|
// advance to the next header location
|
|
*len -= hdrlen;
|
|
*data += hdrlen;
|
|
}
|
|
// we have garbage
|
|
}
|
|
|
|
unsigned char *find_bin(unsigned char *data,int len,const void *blk,int blk_len)
|
|
{
|
|
while (len>=blk_len)
|
|
{
|
|
if (!memcmp(data,blk,blk_len))
|
|
return data;
|
|
data++;
|
|
len--;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool tcp_synack_segment( const struct tcphdr *tcphdr )
|
|
{
|
|
/* check for set bits in TCP hdr */
|
|
return tcphdr->urg == 0 &&
|
|
tcphdr->ack == 1 &&
|
|
tcphdr->psh == 0 &&
|
|
tcphdr->rst == 0 &&
|
|
tcphdr->syn == 1 &&
|
|
tcphdr->fin == 0;
|
|
}
|
|
|
|
uint16_t tcp_checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr)
|
|
{
|
|
const uint16_t *buf=buff;
|
|
uint16_t *ip_src=(uint16_t *)&src_addr, *ip_dst=(uint16_t *)&dest_addr;
|
|
uint32_t sum;
|
|
int length=len;
|
|
|
|
// Calculate the sum
|
|
sum = 0;
|
|
while (len > 1)
|
|
{
|
|
sum += *buf++;
|
|
if (sum & 0x80000000)
|
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
|
len -= 2;
|
|
}
|
|
if ( len & 1 )
|
|
// Add the padding if the packet lenght is odd
|
|
sum += *((uint8_t *)buf);
|
|
|
|
// Add the pseudo-header
|
|
sum += *(ip_src++);
|
|
sum += *ip_src;
|
|
sum += *(ip_dst++);
|
|
sum += *ip_dst;
|
|
sum += htons(IPPROTO_TCP);
|
|
sum += htons(length);
|
|
|
|
// Add the carries
|
|
while (sum >> 16)
|
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
|
|
|
// Return the one's complement of sum
|
|
return (uint16_t)(~sum);
|
|
}
|
|
void tcp_fix_checksum(struct tcphdr *tcp,int len, in_addr_t src_addr, in_addr_t dest_addr)
|
|
{
|
|
tcp->check = 0;
|
|
tcp->check = tcp_checksum(tcp,len,src_addr,dest_addr);
|
|
}
|
|
uint16_t tcp6_checksum(const void *buff, int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr)
|
|
{
|
|
const uint16_t *buf=buff;
|
|
const uint16_t *ip_src=(uint16_t *)src_addr, *ip_dst=(uint16_t *)dest_addr;
|
|
uint32_t sum;
|
|
int length=len;
|
|
|
|
// Calculate the sum
|
|
sum = 0;
|
|
while (len > 1)
|
|
{
|
|
sum += *buf++;
|
|
if (sum & 0x80000000)
|
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
|
len -= 2;
|
|
}
|
|
if ( len & 1 )
|
|
// Add the padding if the packet lenght is odd
|
|
sum += *((uint8_t *)buf);
|
|
|
|
// Add the pseudo-header
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *(ip_src++);
|
|
sum += *ip_src;
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *(ip_dst++);
|
|
sum += *ip_dst;
|
|
sum += htons(IPPROTO_TCP);
|
|
sum += htons(length);
|
|
|
|
// Add the carries
|
|
while (sum >> 16)
|
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
|
|
|
// Return the one's complement of sum
|
|
return (uint16_t)(~sum);
|
|
}
|
|
void tcp6_fix_checksum(struct tcphdr *tcp,int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr)
|
|
{
|
|
tcp->check = 0;
|
|
tcp->check = tcp6_checksum(tcp,len,src_addr,dest_addr);
|
|
}
|
|
|
|
void tcp_rewrite_winsize(struct tcphdr *tcp,uint16_t winsize)
|
|
{
|
|
unsigned int winsize_old;
|
|
/*
|
|
unsigned char scale_factor=1;
|
|
int optlen = (tcp->doff << 2);
|
|
unsigned char *opt = (unsigned char*)(tcp+1);
|
|
|
|
optlen = optlen>sizeof(struct tcphdr) ? optlen-sizeof(struct tcphdr) : 0;
|
|
printf("optslen=%d\n",optlen);
|
|
while (optlen)
|
|
{
|
|
switch(*opt)
|
|
{
|
|
case 0: break; // end of option list;
|
|
case 1: opt++; optlen--; break; // noop
|
|
default:
|
|
if (optlen<2 || optlen<opt[1]) break;
|
|
if (*opt==3 && opt[1]>=3)
|
|
{
|
|
scale_factor=opt[2];
|
|
printf("Found scale factor %u\n",opt[2]);
|
|
//opt[2]=0;
|
|
}
|
|
optlen-=opt[1];
|
|
opt+=opt[1];
|
|
}
|
|
}
|
|
*/
|
|
winsize_old = htons(tcp->window); // << scale_factor;
|
|
tcp->window = htons(winsize);
|
|
printf("Window size change %u => %u\n",winsize_old,winsize);
|
|
}
|
|
|
|
struct cbdata_s
|
|
{
|
|
int wsize;
|
|
int qnum;
|
|
bool hostcase;
|
|
};
|
|
|
|
// ret: false - not modified, true - modified
|
|
bool processPacketData(unsigned char *data,int len,const struct cbdata_s *cbdata)
|
|
{
|
|
struct iphdr *iphdr = NULL;
|
|
struct ip6_hdr *ip6hdr = NULL;
|
|
struct tcphdr *tcphdr = NULL;
|
|
unsigned char *p;
|
|
int len_tcp;
|
|
bool bRet = false;
|
|
uint8_t proto;
|
|
|
|
if (proto_check_ipv4(data,len))
|
|
{
|
|
iphdr = (struct iphdr *) data;
|
|
proto = iphdr->protocol;
|
|
proto_skip_ipv4(&data,&len);
|
|
}
|
|
else if (proto_check_ipv6(data,len))
|
|
{
|
|
ip6hdr = (struct ip6_hdr *) data;
|
|
proto_skip_ipv6(&data,&len,&proto);
|
|
}
|
|
else
|
|
{
|
|
// not ipv6 and not ipv4
|
|
return false;
|
|
}
|
|
|
|
if (proto==IPPROTO_TCP && proto_check_tcp(data,len))
|
|
{
|
|
tcphdr = (struct tcphdr *) data;
|
|
len_tcp = len;
|
|
proto_skip_tcp(&data,&len);
|
|
//printf("got TCP packet. payload_len=%d\n",len);
|
|
if (cbdata->wsize && tcp_synack_segment(tcphdr))
|
|
{
|
|
tcp_rewrite_winsize(tcphdr,(uint16_t)cbdata->wsize);
|
|
bRet = true;
|
|
}
|
|
if (cbdata->hostcase && (p = find_bin(data,len,"\r\nHost: ",8)))
|
|
{
|
|
printf("modifying Host: => host:\n");
|
|
p[2]='h'; // "Host:" => "host:"
|
|
bRet = true;
|
|
}
|
|
if (bRet)
|
|
{
|
|
if (iphdr)
|
|
tcp_fix_checksum(tcphdr,len_tcp,iphdr->saddr,iphdr->daddr);
|
|
else
|
|
tcp6_fix_checksum(tcphdr,len_tcp,&ip6hdr->ip6_src,&ip6hdr->ip6_dst);
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
|
|
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
|
|
struct nfq_data *nfa, void *cookie)
|
|
{
|
|
int id,len;
|
|
struct nfqnl_msg_packet_hdr *ph;
|
|
unsigned char *data;
|
|
const struct cbdata_s *cbdata = (struct cbdata_s*)cookie;
|
|
|
|
ph = nfq_get_msg_packet_hdr(nfa);
|
|
id = ph ? ntohl(ph->packet_id) : 0;
|
|
|
|
len = nfq_get_payload(nfa, &data);
|
|
printf("packet: id=%d len=%d\n",id,len);
|
|
if (len >= 0)
|
|
{
|
|
if (processPacketData(data, len, cbdata))
|
|
return nfq_set_verdict(qh, id, NF_ACCEPT, len, data);
|
|
}
|
|
|
|
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
|
|
}
|
|
|
|
bool droproot(uid_t uid, gid_t gid)
|
|
{
|
|
if (uid)
|
|
{
|
|
if (setgid(gid))
|
|
{
|
|
perror("setgid: ");
|
|
return false;
|
|
}
|
|
if (setuid(uid))
|
|
{
|
|
perror("setuid: ");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void exithelp()
|
|
{
|
|
printf(" --qnum=<nfqueue_number>\n --wsize=<window_size>\t; set window size. 0 = do not modify\n --hostcase\t\t; change Host: => host:\n --daemon\t\t; daemonize\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct nfq_handle *h;
|
|
struct nfq_q_handle *qh;
|
|
int fd;
|
|
int rv;
|
|
char buf[4096] __attribute__ ((aligned));
|
|
struct cbdata_s cbdata;
|
|
int option_index=0;
|
|
int v;
|
|
bool daemon=false;
|
|
uid_t uid=0;
|
|
gid_t gid;
|
|
|
|
memset(&cbdata,0,sizeof(cbdata));
|
|
const struct option long_options[] = {
|
|
{"qnum",required_argument,0,0}, // optidx=0
|
|
{"daemon",no_argument,0,0}, // optidx=1
|
|
{"wsize",required_argument,0,0}, // optidx=2
|
|
{"hostcase",no_argument,0,0}, // optidx=3
|
|
{"user",required_argument,0,0}, // optidx=4
|
|
{NULL,0,NULL,0}
|
|
};
|
|
if (argc<2) exithelp();
|
|
while ((v=getopt_long_only(argc,argv,"",long_options,&option_index))!=-1)
|
|
{
|
|
if (v) exithelp();
|
|
switch(option_index)
|
|
{
|
|
case 0: /* qnum */
|
|
cbdata.qnum=atoi(optarg);
|
|
if (cbdata.qnum<0 || cbdata.qnum>65535)
|
|
{
|
|
fprintf(stdout,"bad qnum\n");
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 1: /* daemon */
|
|
daemon = true;
|
|
break;
|
|
case 2: /* wsize */
|
|
cbdata.wsize=atoi(optarg);
|
|
if (cbdata.wsize<0 || cbdata.wsize>65535)
|
|
{
|
|
fprintf(stdout,"bad qnum\n");
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 3: /* hostcase */
|
|
cbdata.hostcase = true;
|
|
break;
|
|
case 4: /* user */
|
|
{
|
|
struct passwd *pwd = getpwnam(optarg);
|
|
if (!pwd)
|
|
{
|
|
fprintf(stderr,"non-existent username supplied\n");
|
|
exit(1);
|
|
}
|
|
uid = pwd->pw_uid;
|
|
gid = pwd->pw_gid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (daemon)
|
|
{
|
|
int pid;
|
|
|
|
pid = fork();
|
|
if (pid == -1)
|
|
return -1;
|
|
else if (pid != 0)
|
|
return 0;
|
|
if (setsid() == -1)
|
|
return -1;
|
|
if (chdir ("/") == -1)
|
|
return -1;
|
|
close(STDIN_FILENO);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
/* redirect fd's 0,1,2 to /dev/null */
|
|
open ("/dev/null", O_RDWR);
|
|
/* stdin */
|
|
dup(0);
|
|
/* stdout */
|
|
dup(0);
|
|
/* stderror */
|
|
}
|
|
|
|
printf("opening library handle\n");
|
|
h = nfq_open();
|
|
if (!h) {
|
|
fprintf(stderr, "error during nfq_open()\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("unbinding existing nf_queue handler for AF_INET (if any)\n");
|
|
if (nfq_unbind_pf(h, AF_INET) < 0) {
|
|
fprintf(stderr, "error during nfq_unbind_pf()\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n");
|
|
if (nfq_bind_pf(h, AF_INET) < 0) {
|
|
fprintf(stderr, "error during nfq_bind_pf()\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("binding this socket to queue '%u'\n", cbdata.qnum);
|
|
qh = nfq_create_queue(h, cbdata.qnum, &cb, &cbdata);
|
|
if (!qh) {
|
|
fprintf(stderr, "error during nfq_create_queue()\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("setting copy_packet mode\n");
|
|
if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
|
|
fprintf(stderr, "can't set packet_copy mode\n");
|
|
exit(1);
|
|
}
|
|
|
|
fd = nfq_fd(h);
|
|
|
|
if (droproot(uid,gid))
|
|
{
|
|
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
|
|
{
|
|
int r=nfq_handle_packet(h, buf, rv);
|
|
if (r) fprintf(stderr,"nfq_handle_packet error %d\n",r);
|
|
}
|
|
}
|
|
|
|
printf("unbinding from queue 0\n");
|
|
nfq_destroy_queue(qh);
|
|
|
|
#ifdef INSANE
|
|
/* normally, applications SHOULD NOT issue this command, since
|
|
* it detaches other programs/sockets from AF_INET, too ! */
|
|
printf("unbinding from AF_INET\n");
|
|
nfq_unbind_pf(h, AF_INET);
|
|
#endif
|
|
|
|
printf("closing library handle\n");
|
|
nfq_close(h);
|
|
|
|
exit(0);
|
|
}
|