mirror of
https://github.com/bol-van/zapret.git
synced 2025-04-30 02:42:58 +03:00
1941 lines
52 KiB
C
1941 lines
52 KiB
C
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/param.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifndef IP_NODEFRAG
|
|
// for very old toolchains
|
|
#define IP_NODEFRAG 22
|
|
#endif
|
|
|
|
#include "darkmagic.h"
|
|
#include "helpers.h"
|
|
#include "params.h"
|
|
#include "nfqws.h"
|
|
|
|
#ifdef __CYGWIN__
|
|
#include <wlanapi.h>
|
|
#include <netlistmgr.h>
|
|
#endif
|
|
|
|
uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment)
|
|
{
|
|
return htonl(ntohl(netorder_value) + cpuorder_increment);
|
|
}
|
|
uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment)
|
|
{
|
|
return htons(ntohs(netorder_value) + cpuorder_increment);
|
|
}
|
|
|
|
uint8_t *tcp_find_option(struct tcphdr *tcp, uint8_t kind)
|
|
{
|
|
uint8_t *t = (uint8_t *)(tcp + 1);
|
|
uint8_t *end = (uint8_t *)tcp + (tcp->th_off << 2);
|
|
while (t < end)
|
|
{
|
|
switch (*t)
|
|
{
|
|
case 0: // end
|
|
return NULL;
|
|
case 1: // noop
|
|
t++;
|
|
break;
|
|
default: // kind,len,data
|
|
if ((t + 1) >= end || t[1] < 2 || (t + t[1]) > end)
|
|
return NULL;
|
|
if (*t == kind)
|
|
return t;
|
|
t += t[1];
|
|
break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
uint32_t *tcp_find_timestamps(struct tcphdr *tcp)
|
|
{
|
|
uint8_t *t = tcp_find_option(tcp, 8);
|
|
return (t && t[1] == 10) ? (uint32_t *)(t + 2) : NULL;
|
|
}
|
|
uint8_t tcp_find_scale_factor(const struct tcphdr *tcp)
|
|
{
|
|
uint8_t *scale = tcp_find_option((struct tcphdr *)tcp, 3); // tcp option 3 - scale factor
|
|
if (scale && scale[1] == 3)
|
|
return scale[2];
|
|
return SCALE_NONE;
|
|
}
|
|
bool tcp_has_fastopen(const struct tcphdr *tcp)
|
|
{
|
|
uint8_t *opt;
|
|
// new style RFC7413
|
|
opt = tcp_find_option((struct tcphdr *)tcp, 34);
|
|
if (opt)
|
|
return true;
|
|
// old style RFC6994
|
|
opt = tcp_find_option((struct tcphdr *)tcp, 254);
|
|
return opt && opt[1] >= 4 && opt[2] == 0xF9 && opt[3] == 0x89;
|
|
}
|
|
|
|
// n prefix (nsport, nwsize) means network byte order
|
|
static void fill_tcphdr(
|
|
struct tcphdr *tcp, uint32_t fooling, uint8_t tcp_flags,
|
|
uint32_t nseq, uint32_t nack_seq,
|
|
uint16_t nsport, uint16_t ndport,
|
|
uint16_t nwsize, uint8_t scale_factor,
|
|
uint32_t *timestamps,
|
|
uint32_t badseq_increment,
|
|
uint32_t badseq_ack_increment,
|
|
uint16_t data_len)
|
|
{
|
|
char *tcpopt = (char *)(tcp + 1);
|
|
uint8_t t = 0;
|
|
|
|
memset(tcp, 0, sizeof(*tcp));
|
|
tcp->th_sport = nsport;
|
|
tcp->th_dport = ndport;
|
|
if (fooling & FOOL_BADSEQ)
|
|
{
|
|
tcp->th_seq = net32_add(nseq, badseq_increment);
|
|
tcp->th_ack = net32_add(nack_seq, badseq_ack_increment);
|
|
}
|
|
else
|
|
{
|
|
tcp->th_seq = nseq;
|
|
tcp->th_ack = nack_seq;
|
|
}
|
|
tcp->th_off = 5;
|
|
if ((fooling & FOOL_DATANOACK) && !(tcp_flags & (TH_SYN | TH_RST)) && data_len)
|
|
tcp_flags &= ~TH_ACK;
|
|
*((uint8_t *)tcp + 13) = tcp_flags;
|
|
tcp->th_win = nwsize;
|
|
if (fooling & FOOL_MD5SIG)
|
|
{
|
|
tcpopt[0] = 19; // kind
|
|
tcpopt[1] = 18; // len
|
|
*(uint32_t *)(tcpopt + 2) = random();
|
|
*(uint32_t *)(tcpopt + 6) = random();
|
|
*(uint32_t *)(tcpopt + 10) = random();
|
|
*(uint32_t *)(tcpopt + 14) = random();
|
|
t = 18;
|
|
}
|
|
if (timestamps || (fooling & FOOL_TS))
|
|
{
|
|
tcpopt[t] = 8; // kind
|
|
tcpopt[t + 1] = 10; // len
|
|
// forge only TSecr if orig timestamp is present
|
|
*(uint32_t *)(tcpopt + t + 2) = timestamps ? timestamps[0] : -1;
|
|
*(uint32_t *)(tcpopt + t + 6) = (timestamps && !(fooling & FOOL_TS)) ? timestamps[1] : -1;
|
|
t += 10;
|
|
}
|
|
if (scale_factor != SCALE_NONE)
|
|
{
|
|
tcpopt[t++] = 3;
|
|
tcpopt[t++] = 3;
|
|
tcpopt[t++] = scale_factor;
|
|
}
|
|
while (t & 3)
|
|
tcpopt[t++] = 1; // noop
|
|
tcp->th_off += t >> 2;
|
|
tcp->th_sum = 0;
|
|
}
|
|
static uint16_t tcpopt_len(uint32_t fooling, const uint32_t *timestamps, uint8_t scale_factor)
|
|
{
|
|
uint16_t t = 0;
|
|
if (fooling & FOOL_MD5SIG)
|
|
t = 18;
|
|
if ((fooling & FOOL_TS) || timestamps)
|
|
t += 10;
|
|
if (scale_factor != SCALE_NONE)
|
|
t += 3;
|
|
return (t + 3) & ~3;
|
|
}
|
|
|
|
// n prefix (nsport, nwsize) means network byte order
|
|
static void fill_udphdr(struct udphdr *udp, uint16_t nsport, uint16_t ndport, uint16_t len_payload)
|
|
{
|
|
udp->uh_sport = nsport;
|
|
udp->uh_dport = ndport;
|
|
udp->uh_ulen = htons(len_payload + sizeof(struct udphdr));
|
|
udp->uh_sum = 0;
|
|
}
|
|
|
|
static void fill_iphdr(struct ip *ip, const struct in_addr *src, const struct in_addr *dst, uint16_t pktlen, uint8_t proto, uint8_t ttl)
|
|
{
|
|
ip->ip_tos = 0;
|
|
ip->ip_sum = 0;
|
|
ip->ip_off = 0;
|
|
ip->ip_v = 4;
|
|
ip->ip_hl = 5;
|
|
ip->ip_len = htons(pktlen);
|
|
ip->ip_id = 0;
|
|
ip->ip_ttl = ttl;
|
|
ip->ip_p = proto;
|
|
ip->ip_src = *src;
|
|
ip->ip_dst = *dst;
|
|
}
|
|
static void fill_ip6hdr(struct ip6_hdr *ip6, const struct in6_addr *src, const struct in6_addr *dst, uint16_t payloadlen, uint8_t proto, uint8_t ttl)
|
|
{
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payloadlen);
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = proto;
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl;
|
|
ip6->ip6_src = *src;
|
|
ip6->ip6_dst = *dst;
|
|
}
|
|
|
|
bool prepare_tcp_segment4(
|
|
const struct sockaddr_in *src, const struct sockaddr_in *dst,
|
|
uint8_t tcp_flags,
|
|
uint32_t nseq, uint32_t nack_seq,
|
|
uint16_t nwsize,
|
|
uint8_t scale_factor,
|
|
uint32_t *timestamps,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
uint32_t badseq_increment,
|
|
uint32_t badseq_ack_increment,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
uint16_t tcpoptlen = tcpopt_len(fooling, timestamps, scale_factor);
|
|
uint16_t ip_payload_len = sizeof(struct tcphdr) + tcpoptlen + len;
|
|
uint16_t pktlen = sizeof(struct ip) + ip_payload_len;
|
|
if (pktlen > *buflen)
|
|
return false;
|
|
|
|
struct ip *ip = (struct ip *)buf;
|
|
struct tcphdr *tcp = (struct tcphdr *)(ip + 1);
|
|
uint8_t *payload = (uint8_t *)(tcp + 1) + tcpoptlen;
|
|
|
|
fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_TCP, ttl);
|
|
fill_tcphdr(tcp, fooling, tcp_flags, nseq, nack_seq, src->sin_port, dst->sin_port, nwsize, scale_factor, timestamps, badseq_increment, badseq_ack_increment, len);
|
|
|
|
memcpy(payload, data, len);
|
|
tcp4_fix_checksum(tcp, ip_payload_len, &ip->ip_src, &ip->ip_dst);
|
|
if (fooling & FOOL_BADSUM)
|
|
tcp->th_sum ^= htons(0xBEAF);
|
|
|
|
*buflen = pktlen;
|
|
return true;
|
|
}
|
|
|
|
bool prepare_tcp_segment6(
|
|
const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,
|
|
uint8_t tcp_flags,
|
|
uint32_t nseq, uint32_t nack_seq,
|
|
uint16_t nwsize,
|
|
uint8_t scale_factor,
|
|
uint32_t *timestamps,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
uint32_t badseq_increment,
|
|
uint32_t badseq_ack_increment,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
uint16_t tcpoptlen = tcpopt_len(fooling, timestamps, scale_factor);
|
|
uint16_t transport_payload_len = sizeof(struct tcphdr) + tcpoptlen + len;
|
|
uint16_t ip_payload_len = transport_payload_len +
|
|
8 * !!((fooling & (FOOL_HOPBYHOP | FOOL_HOPBYHOP2)) == FOOL_HOPBYHOP) +
|
|
16 * !!(fooling & FOOL_HOPBYHOP2) +
|
|
8 * !!(fooling & FOOL_DESTOPT) +
|
|
8 * !!(fooling & FOOL_IPFRAG1);
|
|
uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len;
|
|
if (pktlen > *buflen)
|
|
return false;
|
|
|
|
struct ip6_hdr *ip6 = (struct ip6_hdr *)buf;
|
|
struct tcphdr *tcp = (struct tcphdr *)(ip6 + 1);
|
|
uint8_t proto = IPPROTO_TCP, *nexttype = NULL;
|
|
|
|
if (fooling & (FOOL_HOPBYHOP | FOOL_HOPBYHOP2))
|
|
{
|
|
struct ip6_hbh *hbh = (struct ip6_hbh *)tcp;
|
|
tcp = (struct tcphdr *)((uint8_t *)tcp + 8);
|
|
memset(hbh, 0, 8);
|
|
// extra HOPBYHOP header. standard violation
|
|
if (fooling & FOOL_HOPBYHOP2)
|
|
{
|
|
hbh = (struct ip6_hbh *)tcp;
|
|
tcp = (struct tcphdr *)((uint8_t *)tcp + 8);
|
|
memset(hbh, 0, 8);
|
|
}
|
|
hbh->ip6h_nxt = IPPROTO_TCP;
|
|
nexttype = &hbh->ip6h_nxt;
|
|
proto = IPPROTO_HOPOPTS;
|
|
}
|
|
if (fooling & FOOL_DESTOPT)
|
|
{
|
|
struct ip6_dest *dest = (struct ip6_dest *)tcp;
|
|
tcp = (struct tcphdr *)((uint8_t *)tcp + 8);
|
|
memset(dest, 0, 8);
|
|
dest->ip6d_nxt = IPPROTO_TCP;
|
|
if (nexttype)
|
|
*nexttype = IPPROTO_DSTOPTS;
|
|
else
|
|
proto = IPPROTO_DSTOPTS;
|
|
nexttype = &dest->ip6d_nxt;
|
|
}
|
|
if (fooling & FOOL_IPFRAG1)
|
|
{
|
|
struct ip6_frag *frag = (struct ip6_frag *)tcp;
|
|
tcp = (struct tcphdr *)((uint8_t *)tcp + sizeof(struct ip6_frag));
|
|
frag->ip6f_nxt = IPPROTO_TCP;
|
|
frag->ip6f_ident = htonl(1 + random() % 0xFFFFFFFF);
|
|
frag->ip6f_reserved = 0;
|
|
frag->ip6f_offlg = 0;
|
|
if (nexttype)
|
|
*nexttype = IPPROTO_FRAGMENT;
|
|
else
|
|
proto = IPPROTO_FRAGMENT;
|
|
}
|
|
|
|
uint8_t *payload = (uint8_t *)(tcp + 1) + tcpoptlen;
|
|
|
|
fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl);
|
|
fill_tcphdr(tcp, fooling, tcp_flags, nseq, nack_seq, src->sin6_port, dst->sin6_port, nwsize, scale_factor, timestamps, badseq_increment, badseq_ack_increment, len);
|
|
|
|
memcpy(payload, data, len);
|
|
tcp6_fix_checksum(tcp, transport_payload_len, &ip6->ip6_src, &ip6->ip6_dst);
|
|
if (fooling & FOOL_BADSUM)
|
|
tcp->th_sum ^= htons(0xBEAF);
|
|
|
|
*buflen = pktlen;
|
|
return true;
|
|
}
|
|
|
|
bool prepare_tcp_segment(
|
|
const struct sockaddr *src, const struct sockaddr *dst,
|
|
uint8_t tcp_flags,
|
|
uint32_t nseq, uint32_t nack_seq,
|
|
uint16_t nwsize,
|
|
uint8_t scale_factor,
|
|
uint32_t *timestamps,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
uint32_t badseq_increment,
|
|
uint32_t badseq_ack_increment,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
return (src->sa_family == AF_INET && dst->sa_family == AF_INET) ? prepare_tcp_segment4((struct sockaddr_in *)src, (struct sockaddr_in *)dst, tcp_flags, nseq, nack_seq, nwsize, scale_factor, timestamps, ttl, fooling, badseq_increment, badseq_ack_increment, data, len, buf, buflen) : (src->sa_family == AF_INET6 && dst->sa_family == AF_INET6) ? prepare_tcp_segment6((struct sockaddr_in6 *)src, (struct sockaddr_in6 *)dst, tcp_flags, nseq, nack_seq, nwsize, scale_factor, timestamps, ttl, fooling, badseq_increment, badseq_ack_increment, data, len, buf, buflen)
|
|
: false;
|
|
}
|
|
|
|
// padlen<0 means payload shrinking
|
|
bool prepare_udp_segment4(
|
|
const struct sockaddr_in *src, const struct sockaddr_in *dst,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
const uint8_t *padding, size_t padding_size,
|
|
int padlen,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
if ((len + padlen) <= 0)
|
|
padlen = -(int)len + 1; // do not allow payload to be less that 1 byte
|
|
if ((len + padlen) > 0xFFFF)
|
|
padlen = 0xFFFF - len; // do not allow payload size to exceed u16 range
|
|
if (padlen < 0)
|
|
{
|
|
len += padlen;
|
|
padlen = 0;
|
|
}
|
|
uint16_t datalen = (uint16_t)(len + padlen);
|
|
uint16_t ip_payload_len = sizeof(struct udphdr) + datalen;
|
|
uint16_t pktlen = sizeof(struct ip) + ip_payload_len;
|
|
if (pktlen > *buflen)
|
|
return false;
|
|
|
|
struct ip *ip = (struct ip *)buf;
|
|
struct udphdr *udp = (struct udphdr *)(ip + 1);
|
|
uint8_t *payload = (uint8_t *)(udp + 1);
|
|
|
|
fill_iphdr(ip, &src->sin_addr, &dst->sin_addr, pktlen, IPPROTO_UDP, ttl);
|
|
fill_udphdr(udp, src->sin_port, dst->sin_port, datalen);
|
|
|
|
memcpy(payload, data, len);
|
|
if (padding)
|
|
fill_pattern(payload + len, padlen, padding, padding_size);
|
|
else
|
|
memset(payload + len, 0, padlen);
|
|
udp4_fix_checksum(udp, ip_payload_len, &ip->ip_src, &ip->ip_dst);
|
|
if (fooling & FOOL_BADSUM)
|
|
udp->uh_sum ^= htons(0xBEAF);
|
|
|
|
*buflen = pktlen;
|
|
return true;
|
|
}
|
|
bool prepare_udp_segment6(
|
|
const struct sockaddr_in6 *src, const struct sockaddr_in6 *dst,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
const uint8_t *padding, size_t padding_size,
|
|
int padlen,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
if ((len + padlen) <= 0)
|
|
padlen = -(int)len + 1; // do not allow payload to be less that 1 byte
|
|
if ((len + padlen) > 0xFFFF)
|
|
padlen = 0xFFFF - len; // do not allow payload size to exceed u16 range
|
|
if (padlen < 0)
|
|
{
|
|
len += padlen;
|
|
padlen = 0;
|
|
}
|
|
uint16_t datalen = (uint16_t)(len + padlen);
|
|
uint16_t transport_payload_len = sizeof(struct udphdr) + datalen;
|
|
uint16_t ip_payload_len = transport_payload_len +
|
|
8 * !!((fooling & (FOOL_HOPBYHOP | FOOL_HOPBYHOP2)) == FOOL_HOPBYHOP) +
|
|
16 * !!(fooling & FOOL_HOPBYHOP2) +
|
|
8 * !!(fooling & FOOL_DESTOPT) +
|
|
8 * !!(fooling & FOOL_IPFRAG1);
|
|
uint16_t pktlen = sizeof(struct ip6_hdr) + ip_payload_len;
|
|
if (pktlen > *buflen)
|
|
return false;
|
|
|
|
struct ip6_hdr *ip6 = (struct ip6_hdr *)buf;
|
|
struct udphdr *udp = (struct udphdr *)(ip6 + 1);
|
|
uint8_t proto = IPPROTO_UDP, *nexttype = NULL;
|
|
|
|
if (fooling & (FOOL_HOPBYHOP | FOOL_HOPBYHOP2))
|
|
{
|
|
struct ip6_hbh *hbh = (struct ip6_hbh *)udp;
|
|
udp = (struct udphdr *)((uint8_t *)udp + 8);
|
|
memset(hbh, 0, 8);
|
|
// extra HOPBYHOP header. standard violation
|
|
if (fooling & FOOL_HOPBYHOP2)
|
|
{
|
|
hbh = (struct ip6_hbh *)udp;
|
|
udp = (struct udphdr *)((uint8_t *)udp + 8);
|
|
memset(hbh, 0, 8);
|
|
}
|
|
hbh->ip6h_nxt = IPPROTO_UDP;
|
|
nexttype = &hbh->ip6h_nxt;
|
|
proto = IPPROTO_HOPOPTS;
|
|
}
|
|
if (fooling & FOOL_DESTOPT)
|
|
{
|
|
struct ip6_dest *dest = (struct ip6_dest *)udp;
|
|
udp = (struct udphdr *)((uint8_t *)udp + 8);
|
|
memset(dest, 0, 8);
|
|
dest->ip6d_nxt = IPPROTO_UDP;
|
|
if (nexttype)
|
|
*nexttype = IPPROTO_DSTOPTS;
|
|
else
|
|
proto = IPPROTO_DSTOPTS;
|
|
nexttype = &dest->ip6d_nxt;
|
|
}
|
|
if (fooling & FOOL_IPFRAG1)
|
|
{
|
|
struct ip6_frag *frag = (struct ip6_frag *)udp;
|
|
udp = (struct udphdr *)((uint8_t *)udp + sizeof(struct ip6_frag));
|
|
frag->ip6f_nxt = IPPROTO_UDP;
|
|
frag->ip6f_ident = htonl(1 + random() % 0xFFFFFFFF);
|
|
frag->ip6f_reserved = 0;
|
|
frag->ip6f_offlg = 0;
|
|
if (nexttype)
|
|
*nexttype = IPPROTO_FRAGMENT;
|
|
else
|
|
proto = IPPROTO_FRAGMENT;
|
|
}
|
|
|
|
uint8_t *payload = (uint8_t *)(udp + 1);
|
|
|
|
fill_ip6hdr(ip6, &src->sin6_addr, &dst->sin6_addr, ip_payload_len, proto, ttl);
|
|
fill_udphdr(udp, src->sin6_port, dst->sin6_port, datalen);
|
|
|
|
memcpy(payload, data, len);
|
|
if (padding)
|
|
fill_pattern(payload + len, padlen, padding, padding_size);
|
|
else
|
|
memset(payload + len, 0, padlen);
|
|
udp6_fix_checksum(udp, transport_payload_len, &ip6->ip6_src, &ip6->ip6_dst);
|
|
if (fooling & FOOL_BADSUM)
|
|
udp->uh_sum ^= htons(0xBEAF);
|
|
|
|
*buflen = pktlen;
|
|
return true;
|
|
}
|
|
bool prepare_udp_segment(
|
|
const struct sockaddr *src, const struct sockaddr *dst,
|
|
uint8_t ttl,
|
|
uint32_t fooling,
|
|
const uint8_t *padding, size_t padding_size,
|
|
int padlen,
|
|
const void *data, uint16_t len,
|
|
uint8_t *buf, size_t *buflen)
|
|
{
|
|
return (src->sa_family == AF_INET && dst->sa_family == AF_INET) ? prepare_udp_segment4((struct sockaddr_in *)src, (struct sockaddr_in *)dst, ttl, fooling, padding, padding_size, padlen, data, len, buf, buflen) : (src->sa_family == AF_INET6 && dst->sa_family == AF_INET6) ? prepare_udp_segment6((struct sockaddr_in6 *)src, (struct sockaddr_in6 *)dst, ttl, fooling, padding, padding_size, padlen, data, len, buf, buflen)
|
|
: false;
|
|
}
|
|
|
|
bool ip6_insert_simple_hdr(uint8_t type, uint8_t *data_pkt, size_t len_pkt, uint8_t *buf, size_t *buflen)
|
|
{
|
|
if ((len_pkt + 8) <= *buflen && len_pkt >= sizeof(struct ip6_hdr))
|
|
{
|
|
struct ip6_hdr *ip6 = (struct ip6_hdr *)buf;
|
|
struct ip6_ext *hdr = (struct ip6_ext *)(ip6 + 1);
|
|
*ip6 = *(struct ip6_hdr *)data_pkt;
|
|
memset(hdr, 0, 8);
|
|
memcpy((uint8_t *)hdr + 8, data_pkt + sizeof(struct ip6_hdr), len_pkt - sizeof(struct ip6_hdr));
|
|
hdr->ip6e_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = type;
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = net16_add(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen, 8);
|
|
*buflen = len_pkt + 8;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// split IPv4 packet into 2 fragments at data payload position frag_pos
|
|
bool ip_frag4(
|
|
const uint8_t *pkt, size_t pkt_size,
|
|
size_t frag_pos, uint32_t ident,
|
|
uint8_t *pkt1, size_t *pkt1_size,
|
|
uint8_t *pkt2, size_t *pkt2_size)
|
|
{
|
|
uint16_t hdrlen, payload_len;
|
|
// frag_pos must be 8-byte aligned
|
|
if (frag_pos & 7 || pkt_size < sizeof(struct ip))
|
|
return false;
|
|
payload_len = htons(((struct ip *)pkt)->ip_len);
|
|
hdrlen = ((struct ip *)pkt)->ip_hl << 2;
|
|
if (payload_len > pkt_size || hdrlen > pkt_size || hdrlen > payload_len)
|
|
return false;
|
|
payload_len -= hdrlen;
|
|
if (frag_pos >= payload_len || *pkt1_size < (hdrlen + frag_pos) || *pkt2_size < (hdrlen + payload_len - frag_pos))
|
|
return false;
|
|
|
|
memcpy(pkt1, pkt, hdrlen + frag_pos);
|
|
((struct ip *)pkt1)->ip_off = htons(IP_MF);
|
|
((struct ip *)pkt1)->ip_len = htons(hdrlen + frag_pos);
|
|
if (ident != (uint32_t)-1)
|
|
((struct ip *)pkt1)->ip_id = (uint16_t)ident;
|
|
*pkt1_size = hdrlen + frag_pos;
|
|
ip4_fix_checksum((struct ip *)pkt1);
|
|
|
|
memcpy(pkt2, pkt, hdrlen);
|
|
memcpy(pkt2 + hdrlen, pkt + hdrlen + frag_pos, payload_len - frag_pos);
|
|
((struct ip *)pkt2)->ip_off = htons((uint16_t)frag_pos >> 3 & IP_OFFMASK);
|
|
((struct ip *)pkt2)->ip_len = htons(hdrlen + payload_len - frag_pos);
|
|
if (ident != (uint32_t)-1)
|
|
((struct ip *)pkt2)->ip_id = (uint16_t)ident;
|
|
*pkt2_size = hdrlen + payload_len - frag_pos;
|
|
ip4_fix_checksum((struct ip *)pkt2);
|
|
|
|
return true;
|
|
}
|
|
bool ip_frag6(
|
|
const uint8_t *pkt, size_t pkt_size,
|
|
size_t frag_pos, uint32_t ident,
|
|
uint8_t *pkt1, size_t *pkt1_size,
|
|
uint8_t *pkt2, size_t *pkt2_size)
|
|
{
|
|
size_t payload_len, unfragmentable;
|
|
uint8_t *last_header_type;
|
|
uint8_t proto;
|
|
struct ip6_frag *frag;
|
|
const uint8_t *payload;
|
|
|
|
if (frag_pos & 7 || pkt_size < sizeof(struct ip6_hdr))
|
|
return false;
|
|
payload_len = sizeof(struct ip6_hdr) + htons(((struct ip6_hdr *)pkt)->ip6_ctlun.ip6_un1.ip6_un1_plen);
|
|
if (pkt_size < payload_len)
|
|
return false;
|
|
|
|
payload = pkt;
|
|
proto_skip_ipv6((uint8_t **)&payload, &payload_len, &proto, &last_header_type);
|
|
unfragmentable = payload - pkt;
|
|
|
|
// printf("pkt_size=%zu FRAG_POS=%zu payload_len=%zu unfragmentable=%zu dh=%zu\n",pkt_size,frag_pos,payload_len,unfragmentable,last_header_type - pkt);
|
|
|
|
if (frag_pos >= payload_len ||
|
|
*pkt1_size < (unfragmentable + sizeof(struct ip6_frag) + frag_pos) ||
|
|
*pkt2_size < (unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
memcpy(pkt1, pkt, unfragmentable);
|
|
((struct ip6_hdr *)pkt1)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + frag_pos);
|
|
pkt1[last_header_type - pkt] = IPPROTO_FRAGMENT;
|
|
frag = (struct ip6_frag *)(pkt1 + unfragmentable);
|
|
frag->ip6f_nxt = proto;
|
|
frag->ip6f_reserved = 0;
|
|
frag->ip6f_offlg = IP6F_MORE_FRAG;
|
|
frag->ip6f_ident = ident;
|
|
memcpy(frag + 1, pkt + unfragmentable, frag_pos);
|
|
*pkt1_size = unfragmentable + sizeof(struct ip6_frag) + frag_pos;
|
|
|
|
memcpy(pkt2, pkt, sizeof(struct ip6_hdr));
|
|
((struct ip6_hdr *)pkt2)->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(unfragmentable - sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + payload_len - frag_pos);
|
|
pkt2[last_header_type - pkt] = IPPROTO_FRAGMENT;
|
|
frag = (struct ip6_frag *)(pkt2 + unfragmentable);
|
|
frag->ip6f_nxt = proto;
|
|
frag->ip6f_reserved = 0;
|
|
frag->ip6f_offlg = htons(frag_pos);
|
|
frag->ip6f_ident = ident;
|
|
memcpy(frag + 1, pkt + unfragmentable + frag_pos, payload_len - frag_pos);
|
|
*pkt2_size = unfragmentable + sizeof(struct ip6_frag) + payload_len - frag_pos;
|
|
|
|
return true;
|
|
}
|
|
bool ip_frag(
|
|
const uint8_t *pkt, size_t pkt_size,
|
|
size_t frag_pos, uint32_t ident,
|
|
uint8_t *pkt1, size_t *pkt1_size,
|
|
uint8_t *pkt2, size_t *pkt2_size)
|
|
{
|
|
if (proto_check_ipv4(pkt, pkt_size))
|
|
return ip_frag4(pkt, pkt_size, frag_pos, ident, pkt1, pkt1_size, pkt2, pkt2_size);
|
|
else if (proto_check_ipv6(pkt, pkt_size))
|
|
return ip_frag6(pkt, pkt_size, frag_pos, ident, pkt1, pkt1_size, pkt2, pkt2_size);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void rewrite_ttl(struct ip *ip, struct ip6_hdr *ip6, uint8_t ttl)
|
|
{
|
|
if (ip)
|
|
ip->ip_ttl = ttl;
|
|
if (ip6)
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = ttl;
|
|
}
|
|
|
|
void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport)
|
|
{
|
|
if (sport)
|
|
*sport = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport
|
|
: 0);
|
|
if (dport)
|
|
*dport = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport
|
|
: 0);
|
|
if (proto)
|
|
*proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP
|
|
: -1;
|
|
}
|
|
|
|
void extract_endpoints(const struct ip *ip, const struct ip6_hdr *ip6hdr, const struct tcphdr *tcphdr, const struct udphdr *udphdr, struct sockaddr_storage *src, struct sockaddr_storage *dst)
|
|
{
|
|
if (ip)
|
|
{
|
|
struct sockaddr_in *si;
|
|
|
|
if (dst)
|
|
{
|
|
si = (struct sockaddr_in *)dst;
|
|
si->sin_family = AF_INET;
|
|
si->sin_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport
|
|
: 0;
|
|
si->sin_addr = ip->ip_dst;
|
|
}
|
|
|
|
if (src)
|
|
{
|
|
si = (struct sockaddr_in *)src;
|
|
si->sin_family = AF_INET;
|
|
si->sin_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport
|
|
: 0;
|
|
si->sin_addr = ip->ip_src;
|
|
}
|
|
}
|
|
else if (ip6hdr)
|
|
{
|
|
struct sockaddr_in6 *si;
|
|
|
|
if (dst)
|
|
{
|
|
si = (struct sockaddr_in6 *)dst;
|
|
si->sin6_family = AF_INET6;
|
|
si->sin6_port = tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport
|
|
: 0;
|
|
si->sin6_addr = ip6hdr->ip6_dst;
|
|
si->sin6_flowinfo = 0;
|
|
si->sin6_scope_id = 0;
|
|
}
|
|
|
|
if (src)
|
|
{
|
|
si = (struct sockaddr_in6 *)src;
|
|
si->sin6_family = AF_INET6;
|
|
si->sin6_port = tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport
|
|
: 0;
|
|
si->sin6_addr = ip6hdr->ip6_src;
|
|
si->sin6_flowinfo = 0;
|
|
si->sin6_scope_id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *proto_name(uint8_t proto)
|
|
{
|
|
switch (proto)
|
|
{
|
|
case IPPROTO_TCP:
|
|
return "tcp";
|
|
case IPPROTO_UDP:
|
|
return "udp";
|
|
case IPPROTO_ICMP:
|
|
return "icmp";
|
|
case IPPROTO_ICMPV6:
|
|
return "icmp6";
|
|
case IPPROTO_IGMP:
|
|
return "igmp";
|
|
case IPPROTO_ESP:
|
|
return "esp";
|
|
case IPPROTO_AH:
|
|
return "ah";
|
|
case IPPROTO_IPV6:
|
|
return "6in4";
|
|
case IPPROTO_IPIP:
|
|
return "4in4";
|
|
#ifdef IPPROTO_GRE
|
|
case IPPROTO_GRE:
|
|
return "gre";
|
|
#endif
|
|
#ifdef IPPROTO_SCTP
|
|
case IPPROTO_SCTP:
|
|
return "sctp";
|
|
#endif
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
static void str_proto_name(char *s, size_t s_len, uint8_t proto)
|
|
{
|
|
const char *name = proto_name(proto);
|
|
if (name)
|
|
snprintf(s, s_len, "%s", name);
|
|
else
|
|
snprintf(s, s_len, "%u", proto);
|
|
}
|
|
uint16_t family_from_proto(uint8_t l3proto)
|
|
{
|
|
switch (l3proto)
|
|
{
|
|
case IPPROTO_IP:
|
|
return AF_INET;
|
|
case IPPROTO_IPV6:
|
|
return AF_INET6;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void str_srcdst_ip(char *s, size_t s_len, const void *saddr, const void *daddr)
|
|
{
|
|
char s_ip[16], d_ip[16];
|
|
*s_ip = *d_ip = 0;
|
|
inet_ntop(AF_INET, saddr, s_ip, sizeof(s_ip));
|
|
inet_ntop(AF_INET, daddr, d_ip, sizeof(d_ip));
|
|
snprintf(s, s_len, "%s => %s", s_ip, d_ip);
|
|
}
|
|
void str_ip(char *s, size_t s_len, const struct ip *ip)
|
|
{
|
|
char ss[35], s_proto[16];
|
|
str_srcdst_ip(ss, sizeof(ss), &ip->ip_src, &ip->ip_dst);
|
|
str_proto_name(s_proto, sizeof(s_proto), ip->ip_p);
|
|
snprintf(s, s_len, "%s proto=%s ttl=%u", ss, s_proto, ip->ip_ttl);
|
|
}
|
|
void print_ip(const struct ip *ip)
|
|
{
|
|
char s[66];
|
|
str_ip(s, sizeof(s), ip);
|
|
printf("%s", s);
|
|
}
|
|
void str_srcdst_ip6(char *s, size_t s_len, const void *saddr, const void *daddr)
|
|
{
|
|
char s_ip[40], d_ip[40];
|
|
*s_ip = *d_ip = 0;
|
|
inet_ntop(AF_INET6, saddr, s_ip, sizeof(s_ip));
|
|
inet_ntop(AF_INET6, daddr, d_ip, sizeof(d_ip));
|
|
snprintf(s, s_len, "%s => %s", s_ip, d_ip);
|
|
}
|
|
void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto)
|
|
{
|
|
char ss[83], s_proto[16];
|
|
str_srcdst_ip6(ss, sizeof(ss), &ip6hdr->ip6_src, &ip6hdr->ip6_dst);
|
|
str_proto_name(s_proto, sizeof(s_proto), proto);
|
|
snprintf(s, s_len, "%s proto=%s ttl=%u", ss, s_proto, ip6hdr->ip6_hlim);
|
|
}
|
|
void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto)
|
|
{
|
|
char s[128];
|
|
str_ip6hdr(s, sizeof(s), ip6hdr, proto);
|
|
printf("%s", s);
|
|
}
|
|
void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr)
|
|
{
|
|
char flags[7], *f = flags;
|
|
if (tcphdr->th_flags & TH_SYN)
|
|
*f++ = 'S';
|
|
if (tcphdr->th_flags & TH_ACK)
|
|
*f++ = 'A';
|
|
if (tcphdr->th_flags & TH_RST)
|
|
*f++ = 'R';
|
|
if (tcphdr->th_flags & TH_FIN)
|
|
*f++ = 'F';
|
|
if (tcphdr->th_flags & TH_PUSH)
|
|
*f++ = 'P';
|
|
if (tcphdr->th_flags & TH_URG)
|
|
*f++ = 'U';
|
|
*f = 0;
|
|
snprintf(s, s_len, "sport=%u dport=%u flags=%s seq=%u ack_seq=%u", htons(tcphdr->th_sport), htons(tcphdr->th_dport), flags, htonl(tcphdr->th_seq), htonl(tcphdr->th_ack));
|
|
}
|
|
void print_tcphdr(const struct tcphdr *tcphdr)
|
|
{
|
|
char s[80];
|
|
str_tcphdr(s, sizeof(s), tcphdr);
|
|
printf("%s", s);
|
|
}
|
|
void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr)
|
|
{
|
|
snprintf(s, s_len, "sport=%u dport=%u", htons(udphdr->uh_sport), htons(udphdr->uh_dport));
|
|
}
|
|
void print_udphdr(const struct udphdr *udphdr)
|
|
{
|
|
char s[30];
|
|
str_udphdr(s, sizeof(s), udphdr);
|
|
printf("%s", s);
|
|
}
|
|
|
|
bool proto_check_ipv4(const uint8_t *data, size_t len)
|
|
{
|
|
return len >= 20 && (data[0] & 0xF0) == 0x40 &&
|
|
len >= ((data[0] & 0x0F) << 2);
|
|
}
|
|
// move to transport protocol
|
|
void proto_skip_ipv4(uint8_t **data, size_t *len)
|
|
{
|
|
size_t l;
|
|
|
|
l = (**data & 0x0F) << 2;
|
|
*data += l;
|
|
*len -= l;
|
|
}
|
|
bool proto_check_tcp(const uint8_t *data, size_t len)
|
|
{
|
|
return len >= 20 && len >= ((data[12] & 0xF0) >> 2);
|
|
}
|
|
void proto_skip_tcp(uint8_t **data, size_t *len)
|
|
{
|
|
size_t l;
|
|
l = ((*data)[12] & 0xF0) >> 2;
|
|
*data += l;
|
|
*len -= l;
|
|
}
|
|
bool proto_check_udp(const uint8_t *data, size_t len)
|
|
{
|
|
return len >= 8 && len >= (data[4] << 8 | data[5]);
|
|
}
|
|
void proto_skip_udp(uint8_t **data, size_t *len)
|
|
{
|
|
*data += 8;
|
|
*len -= 8;
|
|
}
|
|
|
|
bool proto_check_ipv6(const uint8_t *data, size_t 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(uint8_t **data, size_t *len, uint8_t *proto_type, uint8_t **last_header_type)
|
|
{
|
|
size_t hdrlen;
|
|
uint8_t HeaderType;
|
|
|
|
if (proto_type)
|
|
*proto_type = 0; // put error in advance
|
|
|
|
HeaderType = (*data)[6]; // NextHeader field
|
|
if (last_header_type)
|
|
*last_header_type = (*data) + 6;
|
|
*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 43: // routing
|
|
case 51: // authentication
|
|
case 60: // Destination Options
|
|
case 135: // mobility
|
|
case 139: // Host Identity Protocol Version v2
|
|
case 140: // Shim6
|
|
if (*len < 2)
|
|
return; // error
|
|
hdrlen = 8 + ((*data)[1] << 3);
|
|
break;
|
|
case 44: // fragment. length fixed to 8, hdrlen field defined as reserved
|
|
hdrlen = 8;
|
|
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
|
|
if (proto_type)
|
|
*proto_type = HeaderType;
|
|
return;
|
|
}
|
|
if (*len < hdrlen)
|
|
return; // error
|
|
HeaderType = **data;
|
|
if (last_header_type)
|
|
*last_header_type = *data;
|
|
// advance to the next header location
|
|
*len -= hdrlen;
|
|
*data += hdrlen;
|
|
}
|
|
// we have garbage
|
|
}
|
|
|
|
void proto_dissect_l3l4(
|
|
uint8_t *data, size_t len,
|
|
struct ip **ip, struct ip6_hdr **ip6,
|
|
uint8_t *proto,
|
|
struct tcphdr **tcp,
|
|
struct udphdr **udp,
|
|
size_t *transport_len,
|
|
uint8_t **data_payload, size_t *len_payload)
|
|
{
|
|
*ip = NULL;
|
|
*ip6 = NULL;
|
|
*proto = 0;
|
|
*tcp = NULL;
|
|
*transport_len = 0;
|
|
*udp = NULL;
|
|
*data_payload = NULL;
|
|
*len_payload = 0;
|
|
|
|
if (proto_check_ipv4(data, len))
|
|
{
|
|
*ip = (struct ip *)data;
|
|
*proto = (*ip)->ip_p;
|
|
proto_skip_ipv4(&data, &len);
|
|
}
|
|
else if (proto_check_ipv6(data, len))
|
|
{
|
|
*ip6 = (struct ip6_hdr *)data;
|
|
proto_skip_ipv6(&data, &len, proto, NULL);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (*proto == IPPROTO_TCP && proto_check_tcp(data, len))
|
|
{
|
|
*tcp = (struct tcphdr *)data;
|
|
*transport_len = len;
|
|
|
|
proto_skip_tcp(&data, &len);
|
|
|
|
*data_payload = data;
|
|
*len_payload = len;
|
|
}
|
|
else if (*proto == IPPROTO_UDP && proto_check_udp(data, len))
|
|
{
|
|
*udp = (struct udphdr *)data;
|
|
*transport_len = len;
|
|
|
|
proto_skip_udp(&data, &len);
|
|
|
|
*data_payload = data;
|
|
*len_payload = len;
|
|
}
|
|
}
|
|
|
|
bool tcp_synack_segment(const struct tcphdr *tcphdr)
|
|
{
|
|
/* check for set bits in TCP hdr */
|
|
return ((tcphdr->th_flags & (TH_URG | TH_ACK | TH_PUSH | TH_RST | TH_SYN | TH_FIN)) == (TH_ACK | TH_SYN));
|
|
}
|
|
bool tcp_syn_segment(const struct tcphdr *tcphdr)
|
|
{
|
|
/* check for set bits in TCP hdr */
|
|
return ((tcphdr->th_flags & (TH_URG | TH_ACK | TH_PUSH | TH_RST | TH_SYN | TH_FIN)) == TH_SYN);
|
|
}
|
|
bool tcp_ack_segment(const struct tcphdr *tcphdr)
|
|
{
|
|
/* check for set bits in TCP hdr */
|
|
return ((tcphdr->th_flags & (TH_URG | TH_ACK | TH_PUSH | TH_RST | TH_SYN | TH_FIN)) == TH_ACK);
|
|
}
|
|
|
|
void tcp_rewrite_wscale(struct tcphdr *tcp, uint8_t scale_factor)
|
|
{
|
|
uint8_t *scale, scale_factor_old;
|
|
|
|
if (scale_factor != SCALE_NONE)
|
|
{
|
|
scale = tcp_find_option(tcp, 3); // tcp option 3 - scale factor
|
|
if (scale && scale[1] == 3) // length should be 3
|
|
{
|
|
scale_factor_old = scale[2];
|
|
// do not allow increasing scale factor
|
|
if (scale_factor >= scale_factor_old)
|
|
DLOG("Scale factor %u unchanged\n", scale_factor_old);
|
|
else
|
|
{
|
|
scale[2] = scale_factor;
|
|
DLOG("Scale factor change %u => %u\n", scale_factor_old, scale_factor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// scale_factor=SCALE_NONE - do not change
|
|
void tcp_rewrite_winsize(struct tcphdr *tcp, uint16_t winsize, uint8_t scale_factor)
|
|
{
|
|
uint16_t winsize_old;
|
|
|
|
winsize_old = htons(tcp->th_win); // << scale_factor;
|
|
tcp->th_win = htons(winsize);
|
|
DLOG("Window size change %u => %u\n", winsize_old, winsize);
|
|
|
|
tcp_rewrite_wscale(tcp, scale_factor);
|
|
}
|
|
|
|
#ifdef __CYGWIN__
|
|
|
|
static HANDLE w_filter = NULL;
|
|
static OVERLAPPED ovl = {.hEvent = NULL};
|
|
static const struct str_list_head *wlan_filter_ssid = NULL, *nlm_filter_net = NULL;
|
|
static DWORD logical_net_filter_tick = 0;
|
|
uint32_t w_win32_error = 0;
|
|
INetworkListManager *pNetworkListManager = NULL;
|
|
|
|
static void guid2str(const GUID *guid, char *str)
|
|
{
|
|
snprintf(str, 37, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
|
|
}
|
|
static bool str2guid(const char *str, GUID *guid)
|
|
{
|
|
unsigned int u[11], k;
|
|
|
|
if (36 != strlen(str) || 11 != sscanf(str, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", u + 0, u + 1, u + 2, u + 3, u + 4, u + 5, u + 6, u + 7, u + 8, u + 9, u + 10))
|
|
return false;
|
|
guid->Data1 = u[0];
|
|
if ((u[1] & 0xFFFF0000) || (u[2] & 0xFFFF0000))
|
|
return false;
|
|
guid->Data2 = (USHORT)u[1];
|
|
guid->Data3 = (USHORT)u[2];
|
|
for (k = 0; k < 8; k++)
|
|
{
|
|
if (u[k + 3] & 0xFFFFFF00)
|
|
return false;
|
|
guid->Data4[k] = (UCHAR)u[k + 3];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const char *sNetworkCards = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards";
|
|
// get adapter name from guid string
|
|
static bool AdapterID2Name(const GUID *guid, char *name, DWORD name_len)
|
|
{
|
|
char sguid[39], sidx[32], val[256];
|
|
HKEY hkNetworkCards, hkCard;
|
|
DWORD dwIndex, dwLen;
|
|
bool bRet = false;
|
|
WCHAR namew[128];
|
|
DWORD namew_len;
|
|
|
|
if (name_len < 2)
|
|
return false;
|
|
|
|
if ((w_win32_error = RegOpenKeyExA(HKEY_LOCAL_MACHINE, sNetworkCards, 0, KEY_ENUMERATE_SUB_KEYS, &hkNetworkCards)) == ERROR_SUCCESS)
|
|
{
|
|
guid2str(guid, sguid + 1);
|
|
sguid[0] = '{';
|
|
sguid[37] = '}';
|
|
sguid[38] = '\0';
|
|
|
|
for (dwIndex = 0;; dwIndex++)
|
|
{
|
|
dwLen = sizeof(sidx) - 1;
|
|
w_win32_error = RegEnumKeyExA(hkNetworkCards, dwIndex, sidx, &dwLen, NULL, NULL, NULL, NULL);
|
|
if (w_win32_error == ERROR_SUCCESS)
|
|
{
|
|
sidx[dwLen] = '\0';
|
|
|
|
if ((w_win32_error = RegOpenKeyExA(hkNetworkCards, sidx, 0, KEY_QUERY_VALUE, &hkCard)) == ERROR_SUCCESS)
|
|
{
|
|
dwLen = sizeof(val) - 1;
|
|
if ((w_win32_error = RegQueryValueExA(hkCard, "ServiceName", NULL, NULL, val, &dwLen)) == ERROR_SUCCESS)
|
|
{
|
|
val[dwLen] = '\0';
|
|
if (!strcmp(val, sguid))
|
|
{
|
|
namew_len = sizeof(namew) - sizeof(WCHAR);
|
|
if ((w_win32_error = RegQueryValueExW(hkCard, L"Description", NULL, NULL, (LPBYTE)namew, &namew_len)) == ERROR_SUCCESS)
|
|
{
|
|
namew[namew_len / sizeof(WCHAR)] = L'\0';
|
|
if (WideCharToMultiByte(CP_UTF8, 0, namew, -1, name, name_len, NULL, NULL))
|
|
bRet = true;
|
|
}
|
|
}
|
|
}
|
|
RegCloseKey(hkCard);
|
|
}
|
|
if (bRet)
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
RegCloseKey(hkNetworkCards);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool win_dark_init(const struct str_list_head *ssid_filter, const struct str_list_head *nlm_filter)
|
|
{
|
|
win_dark_deinit();
|
|
if (LIST_EMPTY(ssid_filter))
|
|
ssid_filter = NULL;
|
|
if (LIST_EMPTY(nlm_filter))
|
|
nlm_filter = NULL;
|
|
if (nlm_filter)
|
|
{
|
|
if (SUCCEEDED(w_win32_error = CoInitialize(NULL)))
|
|
{
|
|
if (FAILED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID *)&pNetworkListManager)))
|
|
{
|
|
CoUninitialize();
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
nlm_filter_net = nlm_filter;
|
|
wlan_filter_ssid = ssid_filter;
|
|
return true;
|
|
}
|
|
bool win_dark_deinit(void)
|
|
{
|
|
if (pNetworkListManager)
|
|
{
|
|
pNetworkListManager->lpVtbl->Release(pNetworkListManager);
|
|
pNetworkListManager = NULL;
|
|
}
|
|
if (nlm_filter_net)
|
|
CoUninitialize();
|
|
wlan_filter_ssid = nlm_filter_net = NULL;
|
|
}
|
|
|
|
bool nlm_list(bool bAll)
|
|
{
|
|
bool bRet = true;
|
|
|
|
if (SUCCEEDED(w_win32_error = CoInitialize(NULL)))
|
|
{
|
|
INetworkListManager *pNetworkListManager;
|
|
if (SUCCEEDED(w_win32_error = CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, (LPVOID *)&pNetworkListManager)))
|
|
{
|
|
IEnumNetworks *pEnumNetworks;
|
|
if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_ALL, &pEnumNetworks)))
|
|
{
|
|
INetwork *pNet;
|
|
INetworkConnection *pConn;
|
|
IEnumNetworkConnections *pEnumConnections;
|
|
VARIANT_BOOL bIsConnected, bIsConnectedInet;
|
|
NLM_NETWORK_CATEGORY category;
|
|
GUID idNet, idAdapter;
|
|
BSTR bstrName;
|
|
char Name[128], Name2[128];
|
|
int connected;
|
|
for (connected = 1; connected >= !bAll; connected--)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (FAILED(w_win32_error = pEnumNetworks->lpVtbl->Next(pEnumNetworks, 1, &pNet, NULL)))
|
|
{
|
|
bRet = false;
|
|
break;
|
|
}
|
|
if (w_win32_error != S_OK)
|
|
break;
|
|
if (SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnected(pNet, &bIsConnected)) &&
|
|
SUCCEEDED(w_win32_error = pNet->lpVtbl->get_IsConnectedToInternet(pNet, &bIsConnectedInet)) &&
|
|
SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) &&
|
|
SUCCEEDED(w_win32_error = pNet->lpVtbl->GetCategory(pNet, &category)) &&
|
|
SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName)))
|
|
{
|
|
if (!!bIsConnected == connected)
|
|
{
|
|
if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL))
|
|
{
|
|
printf("Name : %s", Name);
|
|
if (bIsConnected)
|
|
printf(" (connected)");
|
|
if (bIsConnectedInet)
|
|
printf(" (inet)");
|
|
printf(" (%s)\n",
|
|
category == NLM_NETWORK_CATEGORY_PUBLIC ? "public" : category == NLM_NETWORK_CATEGORY_PRIVATE ? "private"
|
|
: category == NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED ? "domain"
|
|
: "unknown");
|
|
guid2str(&idNet, Name);
|
|
printf("NetID : %s\n", Name);
|
|
if (connected && SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkConnections(pNet, &pEnumConnections)))
|
|
{
|
|
while ((w_win32_error = pEnumConnections->lpVtbl->Next(pEnumConnections, 1, &pConn, NULL)) == S_OK)
|
|
{
|
|
if (SUCCEEDED(w_win32_error = pConn->lpVtbl->GetAdapterId(pConn, &idAdapter)))
|
|
{
|
|
guid2str(&idAdapter, Name);
|
|
if (AdapterID2Name(&idAdapter, Name2, sizeof(Name2)))
|
|
printf("Adapter : %s (%s)\n", Name2, Name);
|
|
else
|
|
printf("Adapter : %s\n", Name);
|
|
}
|
|
pConn->lpVtbl->Release(pConn);
|
|
}
|
|
pEnumConnections->lpVtbl->Release(pEnumConnections);
|
|
}
|
|
printf("\n");
|
|
}
|
|
else
|
|
{
|
|
w_win32_error = HRESULT_FROM_WIN32(GetLastError());
|
|
bRet = false;
|
|
}
|
|
}
|
|
SysFreeString(bstrName);
|
|
}
|
|
else
|
|
bRet = false;
|
|
pNet->lpVtbl->Release(pNet);
|
|
if (!bRet)
|
|
break;
|
|
}
|
|
if (!bRet)
|
|
break;
|
|
pEnumNetworks->lpVtbl->Reset(pEnumNetworks);
|
|
}
|
|
pEnumNetworks->lpVtbl->Release(pEnumNetworks);
|
|
}
|
|
else
|
|
bRet = false;
|
|
pNetworkListManager->lpVtbl->Release(pNetworkListManager);
|
|
}
|
|
else
|
|
bRet = false;
|
|
}
|
|
else
|
|
bRet = false;
|
|
|
|
CoUninitialize();
|
|
return bRet;
|
|
}
|
|
|
|
static bool nlm_filter_match(const struct str_list_head *nlm_list)
|
|
{
|
|
// no filter given. always matches.
|
|
if (!nlm_list || LIST_EMPTY(nlm_list))
|
|
{
|
|
w_win32_error = 0;
|
|
return true;
|
|
}
|
|
|
|
bool bRet = true, bMatch = false;
|
|
IEnumNetworks *pEnum;
|
|
|
|
if (SUCCEEDED(w_win32_error = pNetworkListManager->lpVtbl->GetNetworks(pNetworkListManager, NLM_ENUM_NETWORK_CONNECTED, &pEnum)))
|
|
{
|
|
INetwork *pNet;
|
|
GUID idNet, g;
|
|
BSTR bstrName;
|
|
char Name[128];
|
|
struct str_list *nlm;
|
|
for (;;)
|
|
{
|
|
if (FAILED(w_win32_error = pEnum->lpVtbl->Next(pEnum, 1, &pNet, NULL)))
|
|
{
|
|
bRet = false;
|
|
break;
|
|
}
|
|
if (w_win32_error != S_OK)
|
|
break;
|
|
if (SUCCEEDED(w_win32_error = pNet->lpVtbl->GetNetworkId(pNet, &idNet)) &&
|
|
SUCCEEDED(w_win32_error = pNet->lpVtbl->GetName(pNet, &bstrName)))
|
|
{
|
|
if (WideCharToMultiByte(CP_UTF8, 0, bstrName, -1, Name, sizeof(Name), NULL, NULL))
|
|
{
|
|
LIST_FOREACH(nlm, nlm_list, next)
|
|
{
|
|
bMatch = !strcmp(Name, nlm->str) || str2guid(nlm->str, &g) && !memcmp(&idNet, &g, sizeof(GUID));
|
|
if (bMatch)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
w_win32_error = HRESULT_FROM_WIN32(GetLastError());
|
|
bRet = false;
|
|
}
|
|
SysFreeString(bstrName);
|
|
}
|
|
else
|
|
bRet = false;
|
|
pNet->lpVtbl->Release(pNet);
|
|
if (!bRet || bMatch)
|
|
break;
|
|
}
|
|
pEnum->lpVtbl->Release(pEnum);
|
|
}
|
|
else
|
|
bRet = false;
|
|
return bRet && bMatch;
|
|
}
|
|
|
|
static bool wlan_filter_match(const struct str_list_head *ssid_list)
|
|
{
|
|
DWORD dwCurVersion;
|
|
HANDLE hClient = NULL;
|
|
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
|
|
PWLAN_INTERFACE_INFO pIfInfo;
|
|
PWLAN_CONNECTION_ATTRIBUTES pConnectInfo;
|
|
DWORD connectInfoSize, k;
|
|
bool bRes;
|
|
struct str_list *ssid;
|
|
size_t len;
|
|
|
|
// no filter given. always matches.
|
|
if (!ssid_list || LIST_EMPTY(ssid_list))
|
|
{
|
|
w_win32_error = 0;
|
|
return true;
|
|
}
|
|
|
|
w_win32_error = WlanOpenHandle(2, NULL, &dwCurVersion, &hClient);
|
|
if (w_win32_error != ERROR_SUCCESS)
|
|
goto fail;
|
|
w_win32_error = WlanEnumInterfaces(hClient, NULL, &pIfList);
|
|
if (w_win32_error != ERROR_SUCCESS)
|
|
goto fail;
|
|
for (k = 0; k < pIfList->dwNumberOfItems; k++)
|
|
{
|
|
pIfInfo = pIfList->InterfaceInfo + k;
|
|
if (pIfInfo->isState == wlan_interface_state_connected)
|
|
{
|
|
w_win32_error = WlanQueryInterface(hClient,
|
|
&pIfInfo->InterfaceGuid,
|
|
wlan_intf_opcode_current_connection,
|
|
NULL,
|
|
&connectInfoSize,
|
|
(PVOID *)&pConnectInfo,
|
|
NULL);
|
|
if (w_win32_error != ERROR_SUCCESS)
|
|
goto fail;
|
|
|
|
// printf("%s\n", pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID);
|
|
|
|
LIST_FOREACH(ssid, ssid_list, next)
|
|
{
|
|
len = strlen(ssid->str);
|
|
if (len == pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength && !memcmp(ssid->str, pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID, len))
|
|
{
|
|
WlanFreeMemory(pConnectInfo);
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
WlanFreeMemory(pConnectInfo);
|
|
}
|
|
}
|
|
w_win32_error = 0;
|
|
fail:
|
|
bRes = false;
|
|
ex:
|
|
if (pIfList)
|
|
WlanFreeMemory(pIfList);
|
|
if (hClient)
|
|
WlanCloseHandle(hClient, 0);
|
|
return bRes;
|
|
found:
|
|
w_win32_error = 0;
|
|
bRes = true;
|
|
goto ex;
|
|
}
|
|
|
|
bool logical_net_filter_match(void)
|
|
{
|
|
return wlan_filter_match(wlan_filter_ssid) && nlm_filter_match(nlm_filter_net);
|
|
}
|
|
|
|
static bool logical_net_filter_match_rate_limited(void)
|
|
{
|
|
DWORD dwTick = GetTickCount() / 1000;
|
|
if (logical_net_filter_tick == dwTick)
|
|
return true;
|
|
logical_net_filter_tick = dwTick;
|
|
return logical_net_filter_match();
|
|
}
|
|
|
|
static HANDLE windivert_init_filter(const char *filter, UINT64 flags)
|
|
{
|
|
LPSTR errormessage = NULL;
|
|
HANDLE h, hMutex;
|
|
const char *mutex_name = "Global\\winws_windivert_mutex";
|
|
|
|
// windivert driver start in windivert.dll has race conditions
|
|
hMutex = CreateMutexA(NULL, TRUE, mutex_name);
|
|
if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS)
|
|
WaitForSingleObject(hMutex, INFINITE);
|
|
h = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags);
|
|
w_win32_error = GetLastError();
|
|
|
|
if (hMutex)
|
|
{
|
|
ReleaseMutex(hMutex);
|
|
CloseHandle(hMutex);
|
|
SetLastError(w_win32_error);
|
|
}
|
|
|
|
if (h != INVALID_HANDLE_VALUE)
|
|
return h;
|
|
|
|
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, w_win32_error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPSTR)&errormessage, 0, NULL);
|
|
DLOG_ERR("windivert: error opening filter: %s", errormessage);
|
|
LocalFree(errormessage);
|
|
if (w_win32_error == ERROR_INVALID_IMAGE_HASH)
|
|
DLOG_ERR("windivert: try to disable secure boot and install OS patches\n");
|
|
|
|
return NULL;
|
|
}
|
|
void rawsend_cleanup(void)
|
|
{
|
|
if (w_filter)
|
|
{
|
|
CancelIoEx(w_filter, &ovl);
|
|
WinDivertClose(w_filter);
|
|
w_filter = NULL;
|
|
}
|
|
if (ovl.hEvent)
|
|
{
|
|
CloseHandle(ovl.hEvent);
|
|
ovl.hEvent = NULL;
|
|
}
|
|
}
|
|
bool windivert_init(const char *filter)
|
|
{
|
|
rawsend_cleanup();
|
|
w_filter = windivert_init_filter(filter, 0);
|
|
if (w_filter)
|
|
{
|
|
ovl.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
if (!ovl.hEvent)
|
|
{
|
|
w_win32_error = GetLastError();
|
|
rawsend_cleanup();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa)
|
|
{
|
|
UINT recv_len;
|
|
DWORD err;
|
|
DWORD rd;
|
|
char c;
|
|
|
|
if (bQuit)
|
|
{
|
|
errno = EINTR;
|
|
return false;
|
|
}
|
|
if (!logical_net_filter_match_rate_limited())
|
|
{
|
|
errno = ENODEV;
|
|
return false;
|
|
}
|
|
usleep(0);
|
|
if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, NULL, &ovl))
|
|
{
|
|
*len = recv_len;
|
|
return true;
|
|
}
|
|
for (;;)
|
|
{
|
|
w_win32_error = GetLastError();
|
|
switch (w_win32_error)
|
|
{
|
|
case ERROR_IO_PENDING:
|
|
// make signals working
|
|
while (WaitForSingleObject(ovl.hEvent, 50) == WAIT_TIMEOUT)
|
|
{
|
|
if (bQuit)
|
|
{
|
|
errno = EINTR;
|
|
return false;
|
|
}
|
|
if (!logical_net_filter_match_rate_limited())
|
|
{
|
|
errno = ENODEV;
|
|
return false;
|
|
}
|
|
usleep(0);
|
|
}
|
|
if (!GetOverlappedResult(hFilter, &ovl, &rd, TRUE))
|
|
continue;
|
|
*len = rd;
|
|
return true;
|
|
case ERROR_INSUFFICIENT_BUFFER:
|
|
errno = ENOBUFS;
|
|
break;
|
|
case ERROR_NO_DATA:
|
|
errno = ESHUTDOWN;
|
|
break;
|
|
default:
|
|
errno = EIO;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa)
|
|
{
|
|
return windivert_recv_filter(w_filter, packet, len, wa);
|
|
}
|
|
|
|
static bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa)
|
|
{
|
|
bool b = WinDivertSend(hFilter, packet, (UINT)len, NULL, wa);
|
|
w_win32_error = GetLastError();
|
|
return b;
|
|
}
|
|
bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa)
|
|
{
|
|
return windivert_send_filter(w_filter, packet, len, wa);
|
|
}
|
|
|
|
bool rawsend(const struct sockaddr *dst, uint32_t fwmark, const char *ifout, const void *data, size_t len)
|
|
{
|
|
WINDIVERT_ADDRESS wa;
|
|
|
|
memset(&wa, 0, sizeof(wa));
|
|
// pseudo interface id IfIdx.SubIfIdx
|
|
if (sscanf(ifout, "%u.%u", &wa.Network.IfIdx, &wa.Network.SubIfIdx) != 2)
|
|
{
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
wa.Outbound = 1;
|
|
wa.IPChecksum = 1;
|
|
wa.TCPChecksum = 1;
|
|
wa.UDPChecksum = 1;
|
|
wa.IPv6 = (dst->sa_family == AF_INET6);
|
|
|
|
return windivert_send(data, len, &wa);
|
|
}
|
|
|
|
#else // *nix
|
|
|
|
static int rawsend_sock4 = -1, rawsend_sock6 = -1;
|
|
static bool b_bind_fix4 = false, b_bind_fix6 = false;
|
|
static void rawsend_clean_sock(int *sock)
|
|
{
|
|
if (sock && *sock != -1)
|
|
{
|
|
close(*sock);
|
|
*sock = -1;
|
|
}
|
|
}
|
|
void rawsend_cleanup(void)
|
|
{
|
|
rawsend_clean_sock(&rawsend_sock4);
|
|
rawsend_clean_sock(&rawsend_sock6);
|
|
}
|
|
static int *rawsend_family_sock(sa_family_t family)
|
|
{
|
|
switch (family)
|
|
{
|
|
case AF_INET:
|
|
return &rawsend_sock4;
|
|
case AF_INET6:
|
|
return &rawsend_sock6;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef BSD
|
|
int socket_divert(sa_family_t family)
|
|
{
|
|
int fd;
|
|
|
|
#ifdef __FreeBSD__
|
|
// freebsd14+ way
|
|
// don't want to use ifdefs with os version to make binaries compatible with all versions
|
|
fd = socket(PF_DIVERT, SOCK_RAW, 0);
|
|
if (fd == -1 && (errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT || errno == EPFNOSUPPORT))
|
|
#endif
|
|
// freebsd13- or openbsd way
|
|
fd = socket(family, SOCK_RAW, IPPROTO_DIVERT);
|
|
return fd;
|
|
}
|
|
static int rawsend_socket_divert(sa_family_t family)
|
|
{
|
|
// HACK HACK HACK HACK HACK HACK HACK HACK
|
|
// FreeBSD doesnt allow IP_HDRINCL for IPV6
|
|
// OpenBSD doesnt allow rawsending tcp frames
|
|
// we either have to go to the link layer (its hard, possible problems arise, compat testing, ...) or use some HACKING
|
|
// from my point of view disabling direct ability to send ip frames is not security. its SHIT
|
|
|
|
int fd = socket_divert(family);
|
|
if (fd != -1 && !set_socket_buffers(fd, 4096, RAW_SNDBUF))
|
|
{
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
static int rawsend_sendto_divert(sa_family_t family, int sock, const void *buf, size_t len)
|
|
{
|
|
struct sockaddr_storage sa;
|
|
socklen_t slen;
|
|
|
|
#ifdef __FreeBSD__
|
|
// since FreeBSD 14 it requires hardcoded IPv4 values, although can also send IPv6 frames
|
|
family = AF_INET;
|
|
slen = sizeof(struct sockaddr_in);
|
|
#else
|
|
// OpenBSD requires correct family and size
|
|
switch (family)
|
|
{
|
|
case AF_INET:
|
|
slen = sizeof(struct sockaddr_in);
|
|
break;
|
|
case AF_INET6:
|
|
slen = sizeof(struct sockaddr_in6);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
#endif
|
|
memset(&sa, 0, slen);
|
|
sa.ss_family = family;
|
|
return sendto(sock, buf, len, 0, (struct sockaddr *)&sa, slen);
|
|
}
|
|
#endif
|
|
|
|
static int rawsend_socket_raw(int domain, int proto)
|
|
{
|
|
int fd = socket(domain, SOCK_RAW, proto);
|
|
if (fd != -1)
|
|
{
|
|
#ifdef __linux__
|
|
int s = RAW_SNDBUF / 2;
|
|
int r = 2048;
|
|
#else
|
|
int s = RAW_SNDBUF;
|
|
int r = 4096;
|
|
#endif
|
|
if (!set_socket_buffers(fd, r, s))
|
|
{
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
static bool set_socket_fwmark(int sock, uint32_t fwmark)
|
|
{
|
|
#ifdef BSD
|
|
#ifdef SO_USER_COOKIE
|
|
if (setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &fwmark, sizeof(fwmark)) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(SO_USER_COOKIE)");
|
|
return false;
|
|
}
|
|
#endif
|
|
#elif defined(__linux__)
|
|
if (setsockopt(sock, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark)) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(SO_MARK)");
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static int rawsend_socket(sa_family_t family)
|
|
{
|
|
int *sock = rawsend_family_sock(family);
|
|
if (!sock)
|
|
return -1;
|
|
|
|
if (*sock == -1)
|
|
{
|
|
int yes = 1, pri = 6;
|
|
// printf("rawsend_socket: family %d",family);
|
|
|
|
#ifdef __FreeBSD__
|
|
// IPPROTO_RAW with IPv6 in FreeBSD always returns EACCES on sendto.
|
|
// must use IPPROTO_TCP for IPv6. IPPROTO_RAW works for IPv4
|
|
// divert sockets are always v4 but accept both v4 and v6
|
|
*sock = rawsend_socket_divert(AF_INET);
|
|
#elif defined(__OpenBSD__) || defined(__APPLE__)
|
|
// OpenBSD does not allow sending TCP frames through raw sockets
|
|
// I don't know about macOS. They have dropped ipfw in recent versions and their PF does not support divert-packet
|
|
*sock = rawsend_socket_divert(family);
|
|
#else
|
|
*sock = rawsend_socket_raw(family, IPPROTO_RAW);
|
|
#endif
|
|
if (*sock == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: socket()");
|
|
return -1;
|
|
}
|
|
#ifdef __linux__
|
|
if (setsockopt(*sock, SOL_SOCKET, SO_PRIORITY, &pri, sizeof(pri)) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(SO_PRIORITY)");
|
|
goto exiterr;
|
|
}
|
|
if (family == AF_INET && setsockopt(*sock, IPPROTO_IP, IP_NODEFRAG, &yes, sizeof(yes)) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(IP_NODEFRAG)");
|
|
goto exiterr;
|
|
}
|
|
if (family == AF_INET && setsockopt(*sock, IPPROTO_IP, IP_FREEBIND, &yes, sizeof(yes)) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(IP_FREEBIND)");
|
|
goto exiterr;
|
|
}
|
|
if (family == AF_INET6 && setsockopt(*sock, SOL_IPV6, IPV6_FREEBIND, &yes, sizeof(yes)) == -1)
|
|
{
|
|
// DLOG_PERROR("rawsend: setsockopt(IPV6_FREEBIND)");
|
|
// don't error because it's supported only from kernel 4.15
|
|
}
|
|
#endif
|
|
}
|
|
return *sock;
|
|
exiterr:
|
|
rawsend_clean_sock(sock);
|
|
return -1;
|
|
}
|
|
bool rawsend_preinit(bool bind_fix4, bool bind_fix6)
|
|
{
|
|
b_bind_fix4 = bind_fix4;
|
|
b_bind_fix6 = bind_fix6;
|
|
// allow IPv6 disabled systems
|
|
return rawsend_socket(AF_INET) != -1 && (rawsend_socket(AF_INET6) != -1 || errno == EAFNOSUPPORT);
|
|
}
|
|
bool rawsend(const struct sockaddr *dst, uint32_t fwmark, const char *ifout, const void *data, size_t len)
|
|
{
|
|
ssize_t bytes;
|
|
int sock = rawsend_socket(dst->sa_family);
|
|
if (sock == -1)
|
|
return false;
|
|
if (!set_socket_fwmark(sock, fwmark))
|
|
return false;
|
|
|
|
int salen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
|
|
struct sockaddr_storage dst2;
|
|
memcpy(&dst2, dst, salen);
|
|
if (dst->sa_family == AF_INET6)
|
|
((struct sockaddr_in6 *)&dst2)->sin6_port = 0; // or will be EINVAL in Linux
|
|
|
|
#if defined(BSD)
|
|
bytes = rawsend_sendto_divert(dst->sa_family, sock, data, len);
|
|
if (bytes == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: sendto_divert");
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
#else
|
|
|
|
#ifdef __linux__
|
|
struct sockaddr_storage sa_src;
|
|
switch (dst->sa_family)
|
|
{
|
|
case AF_INET:
|
|
if (!b_bind_fix4)
|
|
goto nofix;
|
|
extract_endpoints(data, NULL, NULL, NULL, &sa_src, NULL);
|
|
break;
|
|
case AF_INET6:
|
|
if (!b_bind_fix6)
|
|
goto nofix;
|
|
extract_endpoints(NULL, data, NULL, NULL, &sa_src, NULL);
|
|
break;
|
|
default:
|
|
return false; // should not happen
|
|
}
|
|
// printf("family %u dev %s bind : ", dst->sa_family, ifout); print_sockaddr((struct sockaddr *)&sa_src); printf("\n");
|
|
if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifout, ifout ? strlen(ifout) + 1 : 0) == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: setsockopt(SO_BINDTODEVICE)");
|
|
return false;
|
|
}
|
|
if (bind(sock, (const struct sockaddr *)&sa_src, dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)))
|
|
{
|
|
DLOG_PERROR("rawsend: bind (ignoring)");
|
|
// do not fail. this can happen regardless of IP_FREEBIND
|
|
// rebind to any address
|
|
memset(&sa_src, 0, sizeof(sa_src));
|
|
sa_src.ss_family = dst->sa_family;
|
|
if (bind(sock, (const struct sockaddr *)&sa_src, dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)))
|
|
{
|
|
DLOG_PERROR("rawsend: bind to any");
|
|
return false;
|
|
}
|
|
}
|
|
nofix:
|
|
#endif
|
|
|
|
// normal raw socket sendto
|
|
bytes = sendto(sock, data, len, 0, (struct sockaddr *)&dst2, salen);
|
|
if (bytes == -1)
|
|
{
|
|
DLOG_PERROR("rawsend: sendto");
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
#endif // not CYGWIN
|
|
|
|
bool rawsend_rp(const struct rawpacket *rp)
|
|
{
|
|
return rawsend((struct sockaddr *)&rp->dst, rp->fwmark, rp->ifout, rp->packet, rp->len);
|
|
}
|
|
bool rawsend_queue(struct rawpacket_tailhead *q)
|
|
{
|
|
struct rawpacket *rp;
|
|
bool b;
|
|
for (b = true; (rp = rawpacket_dequeue(q)); rawpacket_free(rp))
|
|
b &= rawsend_rp(rp);
|
|
return b;
|
|
}
|
|
|
|
// return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling
|
|
// ttl = TTL of incoming packet
|
|
uint8_t autottl_guess(uint8_t ttl, const autottl *attl)
|
|
{
|
|
uint8_t orig, path, fake;
|
|
|
|
// 18.65.168.125 ( cloudfront ) 255
|
|
// 157.254.246.178 128
|
|
// 1.1.1.1 64
|
|
// guess original ttl. consider path lengths less than 32 hops
|
|
if (ttl > 223)
|
|
orig = 255;
|
|
else if (ttl < 128 && ttl > 96)
|
|
orig = 128;
|
|
else if (ttl < 64 && ttl > 32)
|
|
orig = 64;
|
|
else
|
|
return 0;
|
|
|
|
path = orig - ttl;
|
|
|
|
fake = path > attl->delta ? path - attl->delta : attl->min;
|
|
if (fake < attl->min)
|
|
fake = attl->min;
|
|
else if (fake > attl->max)
|
|
fake = attl->max;
|
|
|
|
if (fake >= path)
|
|
return 0;
|
|
|
|
return fake;
|
|
}
|
|
|
|
void do_nat(bool bOutbound, struct ip *ip, struct ip6_hdr *ip6, struct tcphdr *tcphdr, struct udphdr *udphdr, const struct sockaddr_in *target4, const struct sockaddr_in6 *target6)
|
|
{
|
|
uint16_t nport;
|
|
|
|
if (ip && target4)
|
|
{
|
|
nport = target4->sin_port;
|
|
if (bOutbound)
|
|
ip->ip_dst = target4->sin_addr;
|
|
else
|
|
ip->ip_src = target4->sin_addr;
|
|
ip4_fix_checksum(ip);
|
|
}
|
|
else if (ip6 && target6)
|
|
{
|
|
nport = target6->sin6_port;
|
|
if (bOutbound)
|
|
ip6->ip6_dst = target6->sin6_addr;
|
|
else
|
|
ip6->ip6_src = target6->sin6_addr;
|
|
}
|
|
else
|
|
return;
|
|
if (nport)
|
|
{
|
|
if (tcphdr)
|
|
{
|
|
if (bOutbound)
|
|
tcphdr->th_dport = nport;
|
|
else
|
|
tcphdr->th_sport = nport;
|
|
}
|
|
if (udphdr)
|
|
{
|
|
if (bOutbound)
|
|
udphdr->uh_dport = nport;
|
|
else
|
|
udphdr->uh_sport = nport;
|
|
}
|
|
}
|
|
}
|
|
|
|
void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr)
|
|
{
|
|
if (!(verdict & VERDICT_NOCSUM))
|
|
{
|
|
// always fix csum for windivert. original can be partial or bad
|
|
#ifndef __CYGWIN__
|
|
#ifdef __FreeBSD__
|
|
// FreeBSD tend to pass IPv6 frames with wrong checksum
|
|
if ((verdict & VERDICT_MASK) == VERDICT_MODIFY || ip6hdr)
|
|
#else
|
|
// if original packet was tampered earlier it needs checksum fixed
|
|
if ((verdict & VERDICT_MASK) == VERDICT_MODIFY)
|
|
#endif
|
|
#endif
|
|
tcp_fix_checksum(tcphdr, transport_len, ip, ip6hdr);
|
|
}
|
|
}
|
|
void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, struct ip *ip, struct ip6_hdr *ip6hdr)
|
|
{
|
|
if (!(verdict & VERDICT_NOCSUM))
|
|
{
|
|
// always fix csum for windivert. original can be partial or bad
|
|
#ifndef __CYGWIN__
|
|
#ifdef __FreeBSD__
|
|
// FreeBSD tend to pass IPv6 frames with wrong checksum
|
|
if ((verdict & VERDICT_MASK) == VERDICT_MODIFY || ip6hdr)
|
|
#else
|
|
// if original packet was tampered earlier it needs checksum fixed
|
|
if ((verdict & VERDICT_MASK) == VERDICT_MODIFY)
|
|
#endif
|
|
#endif
|
|
udp_fix_checksum(udphdr, transport_len, ip, ip6hdr);
|
|
}
|
|
}
|