Browse Source

;) Nailed it! SO FAR SO GOOD

pull/2430/head
PantherJohn 5 years ago
parent
commit
8096db706d
15 changed files with 839 additions and 422 deletions
  1. 40
      acl/delegation.acl
  2. 617
      src/acl.c
  3. 31
      src/acl.h
  4. 15
      src/cache.c
  5. 7
      src/cache.h
  6. 18
      src/jconf.c
  7. 4
      src/manager.c
  8. 45
      src/netutils.c
  9. 10
      src/netutils.h
  10. 58
      src/relay.c
  11. 20
      src/server.c
  12. 9
      src/shadowsocks.h
  13. 31
      src/udprelay.c
  14. 255
      src/utils.c
  15. 101
      src/utils.h

40
acl/delegation.acl

@ -1,12 +1,42 @@
# All IPs listed here will be blocked while the ss-server try to outbound. # All IPs listed here will be blocked while the ss-server try to outbound.
# Only IP is allowed, *NOT* domain name. # Only IP is allowed, *NOT* domain name.
# #
#[bypass_all]
@mode bypass
[bypass_list] [bypass_list]
127.0.0.1
(^|\.)bing\.com$
@import local.acl # relative path
@import $$
#!/bin/bash
arin() {
declare authority
declare country="$1" version="$2"
case ${country} in
"US"|"CA"|"PR")
authority='http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest' ;;
*) authority='http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' ;;
esac
wget -qO- "${authority}" | grep "${version}" | grep "${country}" | awk -F'|' '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }'
}
# see: https://github.com/firehol/iprange/wiki#iprange
#arin CN | iprange # optimized list of ip addresses
$$ # shell script
#@import [barfoo]
#@import [foo] from /etc/foo.acl /etc/barz.acl
(^|\.)cn$
[proxy_list: Seattle, WA] [proxy_list: Seattle, WA]
(^|\.)edu$
(^|\.)google\.com$
(^|\.)(edu|mil|gov|us)$
[proxy_list: Netflix]
#@match regex
(^|\.)(netflix|nflxext)\.com$
(^|\.)(nflxso|nflximg|nflxvideo)\.net$
[proxy_list: La Jolla, CA]
.*
#[dname_map]
#(^|\.)cdninstagram\.com 127.0.0.1
#(^|\.)instagram\.com facebook.com

617
src/acl.c

@ -25,6 +25,8 @@
#endif #endif
#include <ctype.h> #include <ctype.h>
#include <unistd.h>
#include <libgen.h>
#include "rule.h" #include "rule.h"
#include "netutils.h" #include "netutils.h"
@ -35,168 +37,44 @@
static acl_t acl; static acl_t acl;
void free_rules(struct cork_dllist *rules);
void update_addrlist(addrlist *list, int atyp, const void *host);
typedef cork_array(char *) labels_t;
typedef struct {
char *ltag; // list tag
labels_t *tags; // delegation label
} label_t;
void
init_addrlist(addrlist *addrlist) {
ipset_init(&addrlist->ipv4);
ipset_init(&addrlist->ipv6);
cork_dllist_init(&addrlist->domain);
}
typedef struct ctrlist {
label_t label;
cork_array(addrlist *) *list;
} ctrlist_t;
void void
free_addrlist(addrlist *addrlist) {
ipset_done(&addrlist->ipv4);
ipset_done(&addrlist->ipv6);
free_rules(&addrlist->domain);
init_addrlist(addrlist *addrlist)
{
ipset_init(&addrlist->ip);
cork_dllist_init(&addrlist->domain);
} }
void void
free_rules(struct cork_dllist *rules)
merge_addrlist(addrlist *dst, addrlist *src)
{ {
rule_t *rule;
struct cork_dllist_item *curr, *next;
cork_dllist_foreach(rules, curr, next,
rule_t, rule, entries) {
remove_rule(rule);
}
}
struct ipset_iterator
*sit = ipset_iterate(&src->ip, true),
*sit_ntwks = ipset_iterate_networks(&src->ip, true);
int
init_acl(jconf_t *conf)
{
const char *path = conf->acl;
if (path == NULL) {
return -1;
while (!sit->finished) {
ipset_ip_add(&dst->ip, &sit->addr);
ipset_iterator_advance(sit);
} }
FILE *f = fopen(path, "r");
if (f == NULL) {
LOGE("Invalid acl path.");
return -1;
while (!sit_ntwks->finished) {
ipset_ip_add_network(&dst->ip, &sit->addr, sit->cidr_prefix);
ipset_iterator_advance(sit_ntwks);
} }
acl.mode = ACL_BLACKLIST;
init_addrlist(&acl.blocklist);
init_addrlist(&acl.blacklist);
init_addrlist(&acl.whitelist);
addrlist *list = &acl.blacklist;
char buf[MAX_HOSTNAME_LEN];
while (!feof(f))
if (fgets(buf, 256, f)) {
// Discards the whole line if longer than 255 characters
int long_line = 0; // 1: Long 2: Error
while ((strlen(buf) == 255) && (buf[254] != '\n')) {
long_line = 1;
LOGE("Discarding long ACL content: %s", buf);
if (fgets(buf, 256, f) == NULL) {
long_line = 2;
break;
}
}
if (long_line) {
if (long_line == 1) {
LOGE("Discarding long ACL content: %s", buf);
}
continue;
}
// Trim the newline
int len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}
char *comment = strchr(buf, '#');
if (comment) {
*comment = '\0';
}
char *line = trim_whitespace(buf);
if (strlen(line) == 0) {
continue;
}
if (strcmp(line, "[outbound_block_list]") == 0) {
list = &acl.blocklist;
continue;
} else if (strcmp(line, "[black_list]") == 0
|| strcmp(line, "[bypass_list]") == 0) {
list = &acl.blacklist;
continue;
} else if (strcmp(line, "[white_list]") == 0
|| strcmp(line, "[proxy_list]") == 0) {
list = &acl.whitelist;
continue;
} else if (strcmp(line, "[reject_all]") == 0
|| strcmp(line, "[bypass_all]") == 0) {
acl.mode = ACL_WHITELIST;
continue;
} else if (strcmp(line, "[accept_all]") == 0
|| strcmp(line, "[proxy_all]") == 0) {
acl.mode = ACL_BLACKLIST;
continue;
} else {
const char *delim = "[]", *sep = ":";
char *keyword = strtok(strdup(line), delim);
if (strcmp(line, keyword) == 0) {
update_addrlist(list, ACL_ATYP_ANY, line);
continue;
}
int j = 0;
do {
char *cmd = trim_whitespace(strtok(keyword, sep));
if (cmd != NULL) {
if (strcmp(cmd, "proxy_list") == 0) {
int remote_num = 0;
int *remote_idxs = NULL;
char *tag = NULL;
while ((tag = strtok(NULL, sep))) {
for (int i = 0; i < conf->remote_num; i++) {
char *tag_ = trim_whitespace(tag);
char *rtag = trim_whitespace(conf->remotes[i]->tag);
if (rtag != NULL && strcmp(tag_, rtag) == 0) {
remote_idxs = ss_realloc(remote_idxs,
(remote_num + 1) * sizeof(*remote_idxs));
remote_idxs[remote_num++] = i;
}
}
}
acl.deleglist = ss_realloc(acl.deleglist,
(j + 1) * sizeof(acl.deleglist));
delglist *deleglist = acl.deleglist[j++]
= ss_calloc(1, sizeof(*acl.deleglist));
deleglist->remote_num = remote_num;
deleglist->remote_idxs = remote_idxs;
init_addrlist(list = &deleglist->_);
}
}
} while ((keyword = strtok(NULL, delim)));
acl.deleglist[j] = NULL;
}
}
fclose(f);
return 0;
}
void
free_acl(void)
{
free_addrlist(&acl.blocklist);
free_addrlist(&acl.blacklist);
free_addrlist(&acl.whitelist);
cork_dllist_merge(&dst->domain, &src->domain);
ipset_iterator_free(sit);
ipset_iterator_free(sit_ntwks);
} }
void void
@ -212,23 +90,16 @@ update_addrlist(addrlist *list,
parse_addr_cidr((const char *)host, hostaddr, &cidr); parse_addr_cidr((const char *)host, hostaddr, &cidr);
int err = cork_ip_init(&addr, hostaddr); int err = cork_ip_init(&addr, hostaddr);
if (!err) { if (!err) {
switch (addr.version) {
case 4:
if (cidr >= 0) {
ipset_ipv4_add_network(&list->ipv4, &addr.ip.v4, cidr);
} else {
ipset_ipv4_add(&list->ipv4, &addr.ip.v4);
} break;
case 6:
if (cidr >= 0) {
ipset_ipv6_add_network(&list->ipv6, &addr.ip.v6, cidr);
} else {
ipset_ipv6_add(&list->ipv6, &addr.ip.v6);
} break;
if (cidr < 0) {
ipset_ip_add(&list->ip, &addr);
} else {
ipset_ip_add_network(&list->ip, &addr, cidr);
} }
} else { } else {
rule_t *rule = new_rule(); rule_t *rule = new_rule();
if (accept_rule_arg(rule, (const char *)host) != -1 && init_rule(rule)) {
if (accept_rule_arg(rule, (const char *)host) != -1
&& init_rule(rule))
{
add_rule(&list->domain, rule); add_rule(&list->domain, rule);
} }
} }
@ -236,35 +107,30 @@ update_addrlist(addrlist *list,
case ACL_ATYP_IP: { case ACL_ATYP_IP: {
switch(((struct sockaddr_storage *)host)->ss_family) { switch(((struct sockaddr_storage *)host)->ss_family) {
case AF_INET: { case AF_INET: {
struct cork_ipv4 addr;
cork_ipv4_copy(&addr,
&((struct sockaddr_in *)host)->sin_addr);
ipset_ipv4_add(&list->ipv4, &addr);
ipset_ipv4_add(&list->ip,
(struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr);
} break; } break;
case AF_INET6: { case AF_INET6: {
struct cork_ipv6 addr;
cork_ipv6_copy(&addr,
&((struct sockaddr_in6 *)host)->sin6_addr);
ipset_ipv6_add(&list->ipv6, &addr);
ipset_ipv6_add(&list->ip,
(struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr);
} break; } break;
} }
} break; } break;
case ACL_ATYP_IPV4: { case ACL_ATYP_IPV4: {
struct cork_ipv4 addr;
cork_ipv4_copy(&addr, (struct in_addr *)host);
ipset_ipv4_add(&list->ipv4, &addr);
ipset_ipv4_add(&list->ip,
(struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr);
} break; } break;
case ACL_ATYP_IPV6: { case ACL_ATYP_IPV6: {
struct cork_ipv6 addr;
cork_ipv6_copy(&addr, (struct in6_addr *)host);
ipset_ipv6_add(&list->ipv6, &addr);
ipset_ipv6_add(&list->ip,
(struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr);
} break; } break;
case ACL_ATYP_DOMAIN: { case ACL_ATYP_DOMAIN: {
const char *dname = (const char *)host;
rule_t *rule = new_rule(); rule_t *rule = new_rule();
accept_rule_arg(rule, dname);
init_rule(rule);
add_rule(&list->domain, rule);
if (accept_rule_arg(rule, (const char *)host) != -1
&& init_rule(rule))
{
add_rule(&list->domain, rule);
}
} break; } break;
} }
} }
@ -278,29 +144,21 @@ search_addrlist(addrlist *list,
switch (atyp) { switch (atyp) {
case ACL_ATYP_IP: { case ACL_ATYP_IP: {
switch(((struct sockaddr_storage *)host)->ss_family) { switch(((struct sockaddr_storage *)host)->ss_family) {
case AF_INET: {
struct cork_ipv4 addr;
cork_ipv4_copy(&addr,
&((struct sockaddr_in *)host)->sin_addr);
return ipset_contains_ipv4(&list->ipv4, &addr);
}
case AF_INET6: {
struct cork_ipv6 addr;
cork_ipv6_copy(&addr,
&((struct sockaddr_in6 *)host)->sin6_addr);
return ipset_contains_ipv6(&list->ipv6, &addr);
}
case AF_INET:
return ipset_contains_ipv4(&list->ip,
(struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr);
case AF_INET6:
return ipset_contains_ipv6(&list->ip,
(struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr);
} }
} break; } break;
case ACL_ATYP_IPV4: { case ACL_ATYP_IPV4: {
struct cork_ipv4 addr;
cork_ipv4_copy(&addr, (struct in_addr *)host);
return ipset_contains_ipv4(&list->ipv4, &addr);
return ipset_contains_ipv4(&list->ip,
(struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr);
} }
case ACL_ATYP_IPV6: { case ACL_ATYP_IPV6: {
struct cork_ipv6 addr;
cork_ipv6_copy(&addr, (struct in6_addr *)host);
return ipset_contains_ipv6(&list->ipv6, &addr);
return ipset_contains_ipv6(&list->ip,
(struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr);
} }
case ACL_ATYP_DOMAIN: { case ACL_ATYP_DOMAIN: {
dname_t *dname = (dname_t *)host; dname_t *dname = (dname_t *)host;
@ -311,6 +169,361 @@ search_addrlist(addrlist *list,
return false; return false;
} }
void
free_addrlist(addrlist *addrlist)
{
rule_t *rule = NULL;
struct cork_dllist_item *curr, *next;
cork_dllist_foreach(&addrlist->domain, curr, next,
rule_t, rule, entries) {
remove_rule(rule);
}
ipset_done(&addrlist->ip);
}
ctrlist_t *
fetch_ctrlist(struct cache *ctrlists, label_t *label)
{
ctrlist_t *ctrlist = NULL;
size_t ltaglen = strlen(label->ltag);
if (cache_lookup(ctrlists,
label->ltag, ltaglen + 1, &ctrlist) != 0) {
ctrlist = ss_calloc(1, sizeof(ctrlist_t));
ctrlist->label = *label;
ctrlist->list = ss_malloc(sizeof(*ctrlist->list));
cork_array_init(ctrlist->list);
addrlist *addrlist = ss_malloc(sizeof(*addrlist));
init_addrlist(addrlist);
cork_array_append(ctrlist->list, addrlist);
cache_insert(ctrlists, label->ltag, ltaglen + 1, ctrlist);
}
return ctrlist;
}
void
ctrlist_free_cb(void *key, void *element)
{
ctrlist_t *ctrlist = element;
if (ctrlist->label.ltag)
ss_free(ctrlist->label.ltag);
if (ctrlist->list) {
for (int i = 0; i < ctrlist->list->size; i++)
free_addrlist(ctrlist->list->items[i]);
cork_array_done(ctrlist->list);
ss_free(ctrlist->list);
}
if (ctrlist->label.tags)
cork_array_done(ctrlist->label.tags);
}
int
merge_ctrlists(struct cache *dst, struct cache *src)
{
if (!(dst && src))
return -1;
struct cache_entry *entry, *tmp;
cache_foreach(src, entry, tmp) {
ctrlist_t *dstlist = NULL, *srclist = entry->data;
size_t ltaglen = strlen(srclist->label.ltag);
if (cache_lookup(dst, srclist->label.ltag, ltaglen + 1, &dstlist) == 0) {
// TODO merge OR replace?
if (!srclist->list)
continue;
if (dstlist->list)
cork_array_merge(dstlist->list, srclist->list);
if (!srclist->label.tags)
continue;
if (!dstlist->label.tags) {
dstlist->label.tags = ss_malloc(sizeof(*dstlist->label.tags));
cork_array_init(dstlist->label.tags);
}
cork_array_merge(dstlist->label.tags, srclist->label.tags);
} else {
cache_insert(dst, srclist->label.ltag, ltaglen + 1, srclist);
}
}
return 0;
}
void
parse_aclconf(FILE *f, aclconf_t *aclconf, ctrlist_t *ctrlist_n)
{
char buf[MAX_HOSTNAME_LEN];
char dlscript[] = "$$", dlvarnme[] = "[]",
dltagsep[] = ":", dfaultag[] = "default",
dlempty[] = "", whitespace[] = " \t\n";
struct cache *ctrlists = new_cache(-1, ctrlist_free_cb);
if (ctrlists == NULL)
return;
ctrlist_t *ctrlist = fetch_ctrlist(ctrlists,
&(label_t) { .ltag = strdup(dfaultag) });
while (fgets(buf, sizeof(buf), f) != NULL) {
// Discards the whole line if longer than 255 characters
while (strlen(buf) >= sizeof(buf) &&
buf[sizeof(buf) - 2] != '\n') {
LOGE("Discarding long ACL content: %s", buf);
if (fgets(buf, sizeof(buf), f) == NULL)
break;
continue;
}
// Trim the newline
int len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}
char *comment = strchr(buf, '#');
if (comment) {
*comment = '\0';
}
char *line = trim_whitespace(buf);
if (strlen(line) == 0) {
continue;
}
char *keywd_n = strdup(line);
char *keyword = strtok(keywd_n, whitespace);
if (strcmp(keyword, "@mode") == 0) {
char *mode = strtok(NULL, whitespace);
if (mode != NULL) {
if (strcmp(mode, "reject") == 0 ||
strcmp(mode, "bypass") == 0 ||
strcmp(mode, "reject_all") == 0 ||
strcmp(mode, "bypass_all") == 0)
{
aclconf->mode = ACL_BLACKLIST;
} else
if (strcmp(mode, "accept") == 0 ||
strcmp(mode, "proxy") == 0 ||
strcmp(mode, "accept_all") == 0 ||
strcmp(mode, "proxy_all") == 0)
{
aclconf->mode = ACL_WHITELIST;
}
}
} else if (strcmp(keyword, "@interval") == 0) {
aclconf->interval = strtotime(strtok(NULL, whitespace));
} else if (strcmp(keyword, "@algorithm") == 0) {
continue;
} else if (strcmp(keyword, "@import") == 0) {
char *import = NULL;
while ((import = strtok(NULL, whitespace))) {
// scripting identifier
if (strcmp(import, dlscript) == 0) {
char *script = strtok(NULL, dlempty);
size_t script_len = 0;
if (script != NULL) {
script = strdup(script);
}
do {
script_len = script ? strlen(script) : 0;
script = ss_realloc(script, script_len + BUF_SIZE + 1);
char *n;
if ((n = strstr(script, dlscript))) {
*n = 0;
break;
}
} while (fgets(script + script_len, BUF_SIZE, f));
FILE *f = popen(script, "r");
parse_aclconf(f, aclconf, ctrlist);
pclose(f);
ss_free(script);
break;
} else {
char *varname = strtok(strdup(import), dlvarnme);
if (strcmp(varname, import)) {
// variable name
do {
char *ltag = trim_whitespace(varname);
ctrlist_t *ctrlistv = NULL;
// only valid inside the list
if (ltag != NULL && strcmp(ctrlist->label.ltag, dfaultag) != 0 &&
cache_lookup(ctrlists, ltag, strlen(ltag) + 1, &ctrlistv) == 0)
{
cork_array_merge(ctrlist->list, ctrlistv->list);
}
} while ((varname = strtok(NULL, dlvarnme)));
} else {
// file path
FILE *f = fopen(import, "r");
setcwd(current_dir(import));
parse_aclconf(f, aclconf, ctrlist);
fclose(f);
}
}
}
} else {
char *varname = strtok(line, dlvarnme);
/**
* acl entry: addrlist element
* -------------------------------
* format: valid CIDR notation (IP addresses)
* or perl-compatible regex (domain name)
* example: 127.0.0.1, 192.168.0.1/32, (^|\.)(edu|mil|gov|us)$
*/
if (strcmp(line, varname) == 0) {
update_addrlist(*ctrlist->list->items, ACL_ATYP_ANY, line);
continue;
}
/**
* acl definition: addrlist tag
* -------------------------------
* format: [{ addrlist_type }: { tag }:{ tag }:...]
* example: [proxy_list: La Jolla, CA]
*
* NOTE: delegation tags are not inheritable
*/
do {
char *ltag = trim_whitespace(strtok(varname, dltagsep));
if (ltag != NULL) {
char *tag = NULL;
labels_t *tags = ss_malloc(sizeof(*tags));
cork_array_init(tags);
while ((tag = strtok(NULL, dltagsep))) {
cork_array_append(tags, trim_whitespace(tag));
}
ctrlist = fetch_ctrlist(ctrlists, &(label_t) { strdup(ltag), tags });
}
} while ((varname = strtok(NULL, dlvarnme)));
}
ss_free(keywd_n);
}
ctrlist_t *ctrlistv = NULL;
if (ctrlist_n != NULL &&
strcmp(ctrlist_n->label.ltag, dfaultag) != 0 &&
(cache_lookup(ctrlists, ctrlist_n->label.ltag,
strlen(ctrlist_n->label.ltag) + 1, &ctrlistv) == 0 ||
cache_lookup(ctrlists, dfaultag, sizeof(dfaultag), &ctrlistv) == 0))
{
// inside the list being declared
cork_array_merge(ctrlist_n->list, ctrlistv->list);
} else {
// global scope
merge_ctrlists(aclconf->lists, ctrlists);
}
cache_delete(ctrlists, true);
}
int
parse_acl(acl_t *acl, aclconf_t *aclconf)
{
const char *path = aclconf->path;
if (path == NULL) {
return -1;
}
char *prevwd = getcwd(NULL, 0);
FILE *f = fopen(path, "r");
if (f == NULL) {
LOGE("[acl] invalid acl path %s", path);
return -1;
}
if (!aclconf->lists)
return -1;
setcwd(current_dir(path));
parse_aclconf(f, aclconf, NULL);
fclose(f);
if (prevwd != NULL)
setcwd(prevwd);
ss_free(prevwd);
if (aclconf->interval > 0) {
ev_timer_set(&acl->watcher, aclconf->interval, aclconf->interval);
}
acl->mode = aclconf->mode;
int j = 0;
struct cache_entry *entry, *tmp;
cache_foreach(aclconf->lists, entry, tmp) {
ctrlist_t *list = entry->data;
addrlist *rlist = &acl->blacklist;
char *ltag = list->label.ltag;
if (strcmp(ltag, "outbound_block_list") == 0) {
rlist = &acl->blocklist;
} else if (strcmp(ltag, "black_list") == 0 ||
strcmp(ltag, "bypass_list") == 0) {
rlist = &acl->blacklist;
} else if (strcmp(ltag, "white_list") == 0) {
rlist = &acl->whitelist;
} else if (strcmp(ltag, "proxy_list") == 0) {
rlist = &acl->whitelist;
if (list->label.tags != NULL) {
acl->deleglist = ss_realloc(acl->deleglist,
(j + 2) * sizeof(acl->deleglist));
delglist *deleglist = acl->deleglist[j++]
= ss_calloc(1, sizeof(*deleglist));
cork_array_init(&deleglist->idxs);
for (int n = 0; n < list->label.tags->size; n++) {
for (int i = 0; i < aclconf->conf->remote_num; i++) {
char *rtag = trim_whitespace(aclconf->conf->remotes[i]->tag);
if (rtag && strcmp(list->label.tags->items[n], rtag) == 0)
cork_array_append(&deleglist->idxs, i);
}
}
init_addrlist(rlist = &deleglist->_);
acl->deleglist[j] = NULL;
}
} else {
if (strcmp(ltag, "default") != 0)
LOGE("[acl] invalid list type %s", ltag);
continue;
}
// merge address lists
for (int i = 0; i < list->list->size; i++)
merge_addrlist(rlist, list->list->items[i]);
}
cache_clear(aclconf->lists, 0);
return 0;
}
int
init_acl(jconf_t *conf)
{
acl.conf = (aclconf_t) {
.mode = ACL_BLACKLIST,
.lists = new_cache(-1, NULL),
.path = conf->acl,
.conf = conf
};
init_addrlist(&acl.blocklist);
init_addrlist(&acl.blacklist);
init_addrlist(&acl.whitelist);
if (parse_acl(&acl, &acl.conf) != 0)
return -1;
return 0;
}
/** /**
* search_acl * search_acl
* ---------------------- * ----------------------
@ -337,8 +550,8 @@ search_acl(int atyp, const void *host, int type)
delglist **deleglist = acl.deleglist; delglist **deleglist = acl.deleglist;
do { do {
if (search_addrlist(&(*deleglist)->_, atyp, host) if (search_addrlist(&(*deleglist)->_, atyp, host)
&& (*deleglist)->remote_idxs != NULL)
return (*deleglist)->remote_idxs[rand() % (*deleglist)->remote_num];
&& (*deleglist)->idxs.items != NULL)
return (*deleglist)->idxs.items[rand() % (*deleglist)->idxs.size];
} while (*(++deleglist)); } while (*(++deleglist));
} else { } else {
bool ret = false; bool ret = false;

31
src/acl.h

@ -29,6 +29,12 @@
#include <ipset/ipset.h> #include <ipset/ipset.h>
#endif #endif
#ifdef HAVE_LIBEV_EV_H
#include <libev/ev.h>
#else
#include <ev.h>
#endif
#include "jconf.h" #include "jconf.h"
enum { enum {
@ -49,28 +55,39 @@ enum {
} acl_types; } acl_types;
typedef struct { typedef struct {
struct ip_set ipv4, ipv6;
struct ip_set ip;
struct cork_dllist domain; struct cork_dllist domain;
} addrlist; } addrlist;
typedef struct { typedef struct {
int remote_num; // number of remote servers
int *remote_idxs; // a list of indexes of remote servers
addrlist _; // the delegation list for load-balancing
addrlist _; // delegation list for load-balancing
cork_array(int) idxs; // a list of indexes of remote servers
} delglist; } delglist;
typedef struct aclconf {
int mode, algo;
time_t interval;
struct cache *lists;
const char *path;
jconf_t *conf;
} aclconf_t;
typedef struct acl { typedef struct acl {
int mode; int mode;
aclconf_t conf;
addrlist blocklist; addrlist blocklist;
addrlist blacklist, whitelist; addrlist blacklist, whitelist;
delglist **deleglist; delglist **deleglist;
//addrlist **deleglist;
ev_timer watcher;
} acl_t; } acl_t;
int init_acl(jconf_t *conf); int init_acl(jconf_t *conf);
void free_acl(void);
int search_acl(int atyp, const void *host, int type);
void init_addrlist(addrlist *addrlist);
void free_addrlist(addrlist *addrlist);
void merge_addrlist(addrlist *dst, addrlist *src);
void update_addrlist(addrlist *list, int atyp, const void *host); void update_addrlist(addrlist *list, int atyp, const void *host);
bool search_addrlist(addrlist *list, int atyp, const void *host); bool search_addrlist(addrlist *list, int atyp, const void *host);
int search_acl(int atyp, const void *host, int type);
#endif // _ACL_H #endif // _ACL_H

15
src/cache.c

@ -35,6 +35,15 @@
#include "cache.h" #include "cache.h"
#include "utils.h" #include "utils.h"
struct cache *
new_cache(const size_t capacity,
void (*free_cb)(void *key, void *element))
{
struct cache *ret = NULL;
cache_create(&ret, capacity, free_cb);
return ret;
}
/** Creates a new cache object /** Creates a new cache object
* *
* @param dst * @param dst
@ -88,7 +97,7 @@ cache_delete(struct cache *cache, int keep_data)
if (keep_data) { if (keep_data) {
HASH_CLEAR(hh, cache->entries); HASH_CLEAR(hh, cache->entries);
} else { } else {
HASH_ITER(hh, cache->entries, entry, tmp){
HASH_ITER(hh, cache->entries, entry, tmp) {
HASH_DEL(cache->entries, entry); HASH_DEL(cache->entries, entry);
if (entry->data != NULL) { if (entry->data != NULL) {
if (cache->free_cb) { if (cache->free_cb) {
@ -127,7 +136,7 @@ cache_clear(struct cache *cache, ev_tstamp age)
ev_tstamp now = ev_time(); ev_tstamp now = ev_time();
HASH_ITER(hh, cache->entries, entry, tmp){
HASH_ITER(hh, cache->entries, entry, tmp) {
if (now - entry->ts > age) { if (now - entry->ts > age) {
HASH_DEL(cache->entries, entry); HASH_DEL(cache->entries, entry);
if (entry->data != NULL) { if (entry->data != NULL) {
@ -203,7 +212,7 @@ cache_remove(struct cache *cache, void *key, size_t key_len)
* you have to call this function with a **ptr, * you have to call this function with a **ptr,
* otherwise this will blow up in your face. * otherwise this will blow up in your face.
* *
* @return EINVAL if cache is NULL, 0 otherwise
* @return EINVAL if cache is NULL or the key doesn't exist, 0 otherwise
*/ */
int int
cache_lookup(struct cache *cache, void *key, size_t key_len, void *result) cache_lookup(struct cache *cache, void *key, size_t key_len, void *result)

7
src/cache.h

@ -55,6 +55,13 @@ struct cache {
void (*free_cb)(void *key, void *element); /**<Callback function to free cache entries */ void (*free_cb)(void *key, void *element); /**<Callback function to free cache entries */
}; };
#define cache_foreach(cache, entry, tmp) \
if (HASH_COUNT((cache)->entries) > 0) \
HASH_ITER(hh, (cache)->entries, (entry), (tmp))
struct cache *
new_cache(const size_t capacity,
void (*free_cb)(void *key, void *element));
int cache_create(struct cache **dst, const size_t capacity, int cache_create(struct cache **dst, const size_t capacity,
void (*free_cb)(void *key, void *element)); void (*free_cb)(void *key, void *element));
int cache_delete(struct cache *cache, int keep_data); int cache_delete(struct cache *cache, int keep_data);

18
src/jconf.c

@ -477,18 +477,16 @@ parse_argopts(jconf_t *conf, int argc, char **argv)
{ {
char optstr[3] = { [0] = option->name, char optstr[3] = { [0] = option->name,
[1] = option->has_arg == required_argument ? ':' : 0 }; [1] = option->has_arg == required_argument ? ':' : 0 };
short_options = ss_realloc(short_options, strlen(short_options) + strlen(optstr));
short_options = ss_realloc(short_options, strlen(short_options) + strlen(optstr) + 1);
strcat(short_options, optstr); strcat(short_options, optstr);
} }
} }
opterr = 0;
int c, curind;
int c;
int conf_parsed = 0; int conf_parsed = 0;
again: again:
curind = optind;
opterr = 0;
while ((c = getopt_long(argc, argv, while ((c = getopt_long(argc, argv,
short_options, long_options, NULL)) != -1) short_options, long_options, NULL)) != -1)
{ {
@ -547,17 +545,15 @@ again:
} break; } break;
case jconf_type_config: break; case jconf_type_config: break;
default: default:
case jconf_type_unknown: {
case jconf_type_unknown:
// The option character is not recognized. // The option character is not recognized.
LOGE("Unrecognized option: %s",
optopt ? (char []) { optopt } : argv[curind]);
opterr = 1;
} break;
LOGE("Unrecognized option: %s", argv[optind - 1]);
return -1;
} }
} }
break; break;
} }
} }
} }
return opterr;
return 0;
} }

4
src/manager.c

@ -61,10 +61,6 @@
#include "netutils.h" #include "netutils.h"
#include "manager.h" #include "manager.h"
#ifndef BUF_SIZE
#define BUF_SIZE 65535
#endif
int verbose = 0; int verbose = 0;
char *executable = "ss-server"; char *executable = "ss-server";
char *working_dir = NULL; char *working_dir = NULL;

45
src/netutils.c

@ -102,15 +102,33 @@ set_fastopen_passive(int socket)
return s; return s;
} }
socklen_t
get_sockaddr_len(struct sockaddr *addr)
int
tproxy_socket(int socket, int family)
{ {
if (addr->sa_family == AF_INET) {
return sizeof(struct sockaddr_in);
} else if (addr->sa_family == AF_INET6) {
return sizeof(struct sockaddr_in6);
#if defined(IP_TRANSPARENT) && \
defined(IP_RECVORIGDSTADDR) && \
defined(IPV6_RECVORIGDSTADDR)
int opt = 1, err = -1;
if (setsockopt(socket, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) {
close(socket);
return err;
} }
return 0;
switch (family) {
case AF_INET:
err = setsockopt(socket, SOL_IP,
IP_RECVORIGDSTADDR, &opt, sizeof(opt));
break;
case AF_INET6:
err = setsockopt(socket, SOL_IPV6,
IPV6_RECVORIGDSTADDR, &opt, sizeof(opt));
break;
}
return err;
#else
FATAL("transparent proxy not supported in this build");
#endif
} }
int int
@ -219,6 +237,13 @@ create_and_bind(struct sockaddr_storage *storage,
&& listen_ctx->mptcp) { && listen_ctx->mptcp) {
set_mptcp(fd); set_mptcp(fd);
} }
#elif MODULE_REDIR
if (protocol == IPPROTO_UDP) {
if (tproxy_socket(fd, storage->ss_family)) {
ERROR("tproxy_socket");
FATAL("failed to enable transparent proxy");
}
}
#endif #endif
} }
@ -489,7 +514,7 @@ sockaddr_readable(char *format, struct sockaddr_storage *addr)
if (addr->ss_family == AF_INET6 if (addr->ss_family == AF_INET6
&& i + 1 < len && format[i + 1] == ':') && i + 1 < len && format[i + 1] == ':')
{ {
substr = malloc(strlen(host) + 2);
substr = malloc(strlen(host) + 2 + 1);
sprintf(substr, "[%s]", host); sprintf(substr, "[%s]", host);
} else { } else {
substr = host; substr = host;
@ -509,7 +534,7 @@ sockaddr_readable(char *format, struct sockaddr_storage *addr)
} }
size_t substrlen = strlen(substr); size_t substrlen = strlen(substr);
ret = realloc(ret, strlen(ret) + substrlen);
ret = realloc(ret, strlen(ret) + substrlen + 1);
strcat(ret, substr); strcat(ret, substr);
} }
return ret; return ret;
@ -616,7 +641,7 @@ validate_hostname(const char *hostname, const int hostname_len)
char * char *
hostname_readable(char *dname, uint16_t port) hostname_readable(char *dname, uint16_t port)
{ {
static char ret[] = { 0 };
char *ret = ss_calloc(strlen(dname) + MAX_PORT_STR_LEN, sizeof(*ret));
sprintf(ret, "%s:%d", dname, ntohs(port)); sprintf(ret, "%s:%d", dname, ntohs(port));
return ret; return ret;
} }

10
src/netutils.h

@ -105,6 +105,10 @@
#define MAX_HOSTNAME_LEN 256 // FQCN <= 255 characters #define MAX_HOSTNAME_LEN 256 // FQCN <= 255 characters
#define MAX_PORT_STR_LEN 6 // PORT < 65536 #define MAX_PORT_STR_LEN 6 // PORT < 65536
#ifndef BUF_SIZE
#define BUF_SIZE 65535
#endif
#define SOCKET_BUF_SIZE (16 * 1024 - 1) // 16383 Byte, equals to the max chunk size #define SOCKET_BUF_SIZE (16 * 1024 - 1) // 16383 Byte, equals to the max chunk size
#define STREAM_BUF_SIZE SOCKET_BUF_SIZE #define STREAM_BUF_SIZE SOCKET_BUF_SIZE
@ -194,13 +198,17 @@ static const char mptcp_enabled_values[] = { MPTCP_ENABLED, 0 };
#define get_sockaddr(node, service, storage, resolv, ipv6first) \ #define get_sockaddr(node, service, storage, resolv, ipv6first) \
get_sockaddr_r(node, service, 0, storage, resolv, ipv6first) get_sockaddr_r(node, service, 0, storage, resolv, ipv6first)
socklen_t get_sockaddr_len(struct sockaddr *addr);
#define get_sockaddr_len(addr) \
(addr)->sa_family == AF_INET ? sizeof(struct sockaddr_in) : \
(addr)->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : 0
int get_sockaddr_r(const char *, const char *, int get_sockaddr_r(const char *, const char *,
uint16_t, struct sockaddr_storage *, int, int); uint16_t, struct sockaddr_storage *, int, int);
int get_sockaddr_str(struct sockaddr_storage *storage, int get_sockaddr_str(struct sockaddr_storage *storage,
char *host, char *port); char *host, char *port);
char *sockaddr_readable(char *format, struct sockaddr_storage *addr); char *sockaddr_readable(char *format, struct sockaddr_storage *addr);
int tproxy_socket(int socket, int family);
int getdestaddr(int fd, struct sockaddr_storage *destaddr); int getdestaddr(int fd, struct sockaddr_storage *destaddr);
int getdestaddr_dgram(struct msghdr *msg, struct sockaddr_storage *destaddr); int getdestaddr_dgram(struct msghdr *msg, struct sockaddr_storage *destaddr);

58
src/relay.c

@ -74,11 +74,9 @@ static int server_conn = 0;
static char *manager_addr = NULL; static char *manager_addr = NULL;
static struct cork_dllist listeners; static struct cork_dllist listeners;
extern uint64_t tx, rx; extern uint64_t tx, rx;
#ifndef __MINGW32__ #ifndef __MINGW32__
ev_timer stat_watcher; ev_timer stat_watcher;
#endif #endif
#endif #endif
static struct cork_dllist connections; static struct cork_dllist connections;
@ -216,18 +214,17 @@ bailed: {
create_ssocks_header(server->abuf, destaddr); create_ssocks_header(server->abuf, destaddr);
return init_remote(EV_A_ remote, listen_ctx->remotes[remote_idx]); return init_remote(EV_A_ remote, listen_ctx->remotes[remote_idx]);
} else { } else {
struct sockaddr_storage *addr
= elvis(destaddr->addr, ss_calloc(1, sizeof(*addr)));
if (destaddr->dname &&
if (destaddr->dname && !destaddr->addr &&
(destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr))) &&
get_sockaddr_r(destaddr->dname, NULL, get_sockaddr_r(destaddr->dname, NULL,
destaddr->port, addr, 1, ipv6first) == -1)
destaddr->port, destaddr->addr, 1, ipv6first) == -1)
{ {
remote->direct = 0; remote->direct = 0;
LOGE("failed to resolve %s", destaddr->dname); LOGE("failed to resolve %s", destaddr->dname);
goto bailed; goto bailed;
} }
return init_remote(EV_A_ remote, &(remote_cnf_t) { .addr = addr, .iface = listen_ctx->iface });
return init_remote(EV_A_ remote, &(remote_cnf_t) { .addr = destaddr->addr, .iface = listen_ctx->iface });
} }
return 0; return 0;
} }
@ -372,7 +369,7 @@ new_server(int fd, listen_ctx_t *listener)
ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb, ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
request_timeout, listener->timeout);
request_timeout, 0);
cork_dllist_add(&connections, &server->entries); cork_dllist_add(&connections, &server->entries);
@ -657,13 +654,6 @@ start_relay(jconf_t *conf,
return -1; return -1;
} }
#ifndef HAVE_LAUNCHD
if (conf->local_port == NULL) {
conf->local_port = "0";
LOGE("warning: random local port will be assigned");
}
#endif
if (conf->log) { if (conf->log) {
USE_LOGFILE(conf->log); USE_LOGFILE(conf->log);
LOGI("enabled %slogging %s", conf->verbose ? "verbose " : "", conf->log); LOGI("enabled %slogging %s", conf->verbose ? "verbose " : "", conf->log);
@ -685,9 +675,6 @@ start_relay(jconf_t *conf,
LOGI("prioritized IPv6 addresses in domain resolution"); LOGI("prioritized IPv6 addresses in domain resolution");
} }
if (!conf->remote_dns) {
LOGI("disabled remote domain resolution");
}
#ifndef MODULE_TUNNEL #ifndef MODULE_TUNNEL
if (conf->acl != NULL) { if (conf->acl != NULL) {
@ -741,6 +728,18 @@ start_relay(jconf_t *conf,
// Setup proxy context // Setup proxy context
struct ev_loop *loop = EV_DEFAULT; struct ev_loop *loop = EV_DEFAULT;
#ifdef MODULE_LOCAL #ifdef MODULE_LOCAL
#ifndef HAVE_LAUNCHD
if (conf->local_port == NULL) {
conf->local_port = "0";
LOGE("warning: random local port will be assigned");
}
#endif
if (!conf->remote_dns) {
LOGI("disabled remote domain resolution");
}
listen_ctx_t listen_ctx = { listen_ctx_t listen_ctx = {
.mtu = conf->mtu, .mtu = conf->mtu,
.mptcp = conf->mptcp, .mptcp = conf->mptcp,
@ -1056,18 +1055,28 @@ start_relay(jconf_t *conf,
if (conf->mode != UDP_ONLY) { if (conf->mode != UDP_ONLY) {
ev_io_stop(EV_A_ & listen_ctx.io); ev_io_stop(EV_A_ & listen_ctx.io);
free_connections(loop); free_connections(loop);
}
if (listen_ctx.remotes != NULL) {
for (int i = 0; i < listen_ctx.remote_num; i++) { for (int i = 0; i < listen_ctx.remote_num; i++) {
remote_cnf_t *remote_cnf = listen_ctx.remotes[i]; remote_cnf_t *remote_cnf = listen_ctx.remotes[i];
if (remote_cnf != NULL) { if (remote_cnf != NULL) {
ss_free(listen_ctx.remotes[i]);
if (remote_cnf->iface)
ss_free(remote_cnf->iface);
// TODO: hey yo! free() the whole struct, pls
if (remote_cnf->crypto)
ss_free(remote_cnf->crypto);
if (remote_cnf->addr)
ss_free(remote_cnf->addr);
ss_free(remote_cnf);
} }
} }
ss_free(listen_ctx.remotes); ss_free(listen_ctx.remotes);
} }
#elif MODULE_REMOTE #elif MODULE_REMOTE
#ifndef __MINGW32__ #ifndef __MINGW32__
if (manager_addr != NULL) {
if (conf->manager_addr) {
ev_timer_stop(EV_A_ & stat_watcher); ev_timer_stop(EV_A_ & stat_watcher);
} }
#endif #endif
@ -1078,13 +1087,16 @@ start_relay(jconf_t *conf,
} }
#endif #endif
if (plugin_enabled)
stop_plugin();
if (conf->mode != TCP_ONLY) { if (conf->mode != TCP_ONLY) {
free_udprelay(loop); free_udprelay(loop);
} }
if (plugin_enabled)
stop_plugin();
if (conf->log)
CLOSE_LOGFILE();
#ifdef __MINGW32__ #ifdef __MINGW32__
winsock_cleanup(); winsock_cleanup();
#endif #endif

20
src/server.c

@ -318,9 +318,6 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
case STAGE_STREAM: { case STAGE_STREAM: {
remote = server->remote; remote = server->remote;
buf = remote->buf; buf = remote->buf;
// Only timer the watcher if a valid connection is established
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
} break; } break;
} }
@ -387,7 +384,12 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
} }
if (destaddr.dname != NULL) { if (destaddr.dname != NULL) {
if (acl && search_acl(ACL_ATYP_DOMAIN, destaddr.dname, ACL_BLOCKLIST)) {
if (destaddr.dname[destaddr.dname_len - 1] != 0) {
destaddr.dname = ss_realloc(destaddr.dname, destaddr.dname_len + 1);
destaddr.dname[destaddr.dname_len] = 0;
}
if (acl && search_acl(ACL_ATYP_DOMAIN, &(dname_t){ destaddr.dname_len, destaddr.dname }, ACL_BLOCKLIST)) {
if (verbose) if (verbose)
LOGI("blocking access to %s", destaddr.dname); LOGI("blocking access to %s", destaddr.dname);
close_and_free_server(EV_A_ server); close_and_free_server(EV_A_ server);
@ -399,7 +401,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_stop(EV_A_ & server_recv_ctx->io);
query_t *query = ss_calloc(1, sizeof(query_t));
query_t *query = ss_malloc(sizeof(query_t));
query->server = server; query->server = server;
query->hostname = destaddr.dname; query->hostname = destaddr.dname;
@ -594,8 +596,6 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
return; return;
} }
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
ssize_t r = recv(remote->fd, server->buf->data, SOCKET_BUF_SIZE, 0); ssize_t r = recv(remote->fd, server->buf->data, SOCKET_BUF_SIZE, 0);
if (r == 0) { if (r == 0) {
@ -714,9 +714,9 @@ remote_send_cb(EV_P_ ev_io *w, int revents)
memset(&addr, 0, len); memset(&addr, 0, len);
int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len); int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
if (r == 0) { if (r == 0) {
if (verbose) {
LOGI("remote connected");
}
// connection connected, stop the request timeout timer
ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
remote_send_ctx->connected = 1; remote_send_ctx->connected = 1;
if (remote->buf->len == 0) { if (remote->buf->len == 0) {

9
src/shadowsocks.h

@ -100,7 +100,6 @@ new_ssocks_addr()
{ {
ssocks_addr_t *destaddr ssocks_addr_t *destaddr
= ss_calloc(1, sizeof(*destaddr)); = ss_calloc(1, sizeof(*destaddr));
destaddr->dname = ss_calloc(1, MAX_HOSTNAME_LEN * sizeof(*destaddr->dname));
destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr)); destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr));
return destaddr; return destaddr;
} }
@ -175,14 +174,14 @@ parse_ssocks_header(buffer_t *buf, ssocks_addr_t *destaddr, int offset)
} break; } break;
case SSOCKS_ATYP_DOMAIN: { case SSOCKS_ATYP_DOMAIN: {
size_t dname_len = *(uint8_t *)(buf->data + offset); size_t dname_len = *(uint8_t *)(buf->data + offset);
char *dname = ss_calloc(dname_len, sizeof(char));
if (buf->len < dname_len + 1 + offset) { if (buf->len < dname_len + 1 + offset) {
return -1; return -1;
} else {
memcpy(dname, buf->data + offset + 1, dname_len);
offset += dname_len + 1;
} }
char *dname = ss_malloc(dname_len);
memcpy(dname, buf->data + offset + 1, dname_len);
offset += dname_len + 1;
destaddr->port = *(uint16_t *)(buf->data + offset); destaddr->port = *(uint16_t *)(buf->data + offset);
struct cork_ip ip; struct cork_ip ip;

31
src/udprelay.c

@ -164,21 +164,21 @@ resolv_cb(struct sockaddr *addr, void *data)
#elif defined MODULE_REDIR #elif defined MODULE_REDIR
static int static int
create_tproxy(struct sockaddr_storage *addr, struct sockaddr_storage *destaddr)
create_tproxy(struct sockaddr_storage *destaddr)
{ {
int sourcefd = socket(addr->ss_family, SOCK_DGRAM, 0);
int sourcefd = socket(destaddr->ss_family, SOCK_DGRAM, 0);
if (sourcefd < 0) { if (sourcefd < 0) {
return -1; return -1;
} }
int opt = 1; int opt = 1;
if (setsockopt(sourcefd,
addr->ss_family == AF_INET6 ? SOL_IPV6 : SOL_IP,
if (setsockopt(sourcefd, destaddr->ss_family == AF_INET6 ? SOL_IPV6 : SOL_IP,
IP_TRANSPARENT, &opt, sizeof(opt))) IP_TRANSPARENT, &opt, sizeof(opt)))
{ {
close(sourcefd); close(sourcefd);
return -1; return -1;
} }
if (setsockopt(sourcefd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { if (setsockopt(sourcefd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
close(sourcefd); close(sourcefd);
return -1; return -1;
@ -187,12 +187,12 @@ create_tproxy(struct sockaddr_storage *addr, struct sockaddr_storage *destaddr)
// Set QoS flag // Set QoS flag
int tos = 46; int tos = 46;
setsockopt(sourcefd, setsockopt(sourcefd,
addr->ss_family == AF_INET6 ? IPPROTO_IP: IPPROTO_IPV6,
destaddr->ss_family == AF_INET6 ? IPPROTO_IP: IPPROTO_IPV6,
IP_TOS, &tos, sizeof(tos)); IP_TOS, &tos, sizeof(tos));
#endif #endif
if (bind(sourcefd, (struct sockaddr *)destaddr, if (bind(sourcefd, (struct sockaddr *)destaddr,
get_sockaddr_len((struct sockaddr *)&destaddr)) != 0)
get_sockaddr_len((struct sockaddr *)destaddr)) != 0)
{ {
ERROR("[udp] remote_recv_bind"); ERROR("[udp] remote_recv_bind");
close(sourcefd); close(sourcefd);
@ -422,7 +422,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
#endif #endif
#ifdef MODULE_REDIR #ifdef MODULE_REDIR
int sourcefd = create_tproxy(remote->src_addr, remote->destaddr);
int sourcefd = create_tproxy(remote->destaddr);
if (sourcefd < 0) { if (sourcefd < 0) {
ERROR("[udp] remote_recv_socket"); ERROR("[udp] remote_recv_socket");
goto CLEAN_UP; goto CLEAN_UP;
@ -502,8 +502,8 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
}; };
struct msghdr msg = { struct msghdr msg = {
.msg_name = destaddr->addr,
.msg_namelen = sizeof(*destaddr->addr),
.msg_name = src_addr,
.msg_namelen = sizeof(*src_addr),
.msg_control = control_buffer, .msg_control = control_buffer,
.msg_controllen = sizeof(control_buffer), .msg_controllen = sizeof(control_buffer),
.msg_iov = iov, .msg_iov = iov,
@ -516,13 +516,14 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
goto CLEAN_UP; goto CLEAN_UP;
} }
destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr));
if (getdestaddr_dgram(&msg, destaddr->addr)) { if (getdestaddr_dgram(&msg, destaddr->addr)) {
LOGE("[udp] unable to determine destination address"); LOGE("[udp] unable to determine destination address");
goto CLEAN_UP; goto CLEAN_UP;
} }
#else #else
socklen_t src_addr_len = sizeof(&src_addr);
socklen_t src_addr_len = sizeof(*src_addr);
ssize_t r = recvfrom(server->fd, buf->data, buf_size, ssize_t r = recvfrom(server->fd, buf->data, buf_size,
0, (struct sockaddr *)src_addr, &src_addr_len); 0, (struct sockaddr *)src_addr, &src_addr_len);
@ -575,6 +576,11 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
} }
if (destaddr->dname != NULL) { if (destaddr->dname != NULL) {
if (destaddr->dname[destaddr->dname_len - 1] != 0) {
destaddr->dname = ss_realloc(destaddr->dname, destaddr->dname_len + 1);
destaddr->dname[destaddr->dname_len] = 0;
}
if (acl && search_acl(ACL_ATYP_DOMAIN, if (acl && search_acl(ACL_ATYP_DOMAIN,
&(dname_t) { destaddr->dname_len, destaddr->dname }, ACL_BLOCKLIST)) &(dname_t) { destaddr->dname_len, destaddr->dname }, ACL_BLOCKLIST))
{ {
@ -738,8 +744,9 @@ bailed: {
remote->crypto = crypto; remote->crypto = crypto;
remote_addr = remote_cnf->addr; remote_addr = remote_cnf->addr;
} else { } else {
remote_addr = elvis(destaddr->addr, ss_calloc(1, sizeof(*remote_addr)));
if (destaddr->dname &&
remote_addr = destaddr->addr;
if (destaddr->dname && !remote_addr &&
(remote_addr = ss_calloc(1, sizeof(*remote_addr))) &&
get_sockaddr_r(destaddr->dname, NULL, get_sockaddr_r(destaddr->dname, NULL,
destaddr->port, remote_addr, 1, ipv6first) == -1) destaddr->port, remote_addr, 1, ipv6first) == -1)
{ {

255
src/utils.c

@ -49,7 +49,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#endif #endif
#define INT_DIGITS 19 /* enough for 64 bit integer */
#define INT_DIGITS 19 /* enough for 64 bit integer */
int use_tty = 0; int use_tty = 0;
int use_syslog = 0; int use_syslog = 0;
@ -60,25 +60,29 @@ ss_itoa(int i)
{ {
/* Room for INT_DIGITS digits, - and '\0' */ /* Room for INT_DIGITS digits, - and '\0' */
static char buf[INT_DIGITS + 2]; static char buf[INT_DIGITS + 2];
char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */
if (i >= 0) {
do {
char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */
if (i >= 0)
{
do
{
*--p = '0' + (i % 10); *--p = '0' + (i % 10);
i /= 10;
i /= 10;
} while (i != 0); } while (i != 0);
return p; return p;
} else { /* i < 0 */
do {
}
else
{ /* i < 0 */
do
{
*--p = '0' - (i % 10); *--p = '0' - (i % 10);
i /= 10;
i /= 10;
} while (i != 0); } while (i != 0);
*--p = '-'; *--p = '-';
} }
return p; return p;
} }
int
ss_isnumeric(const char *s)
int ss_isnumeric(const char *s)
{ {
if (!s || !*s) if (!s || !*s)
return 0; return 0;
@ -90,15 +94,16 @@ ss_isnumeric(const char *s)
/* /*
* setuid() and setgid() for a specified user. * setuid() and setgid() for a specified user.
*/ */
int
run_as(const char *user)
int run_as(const char *user)
{ {
#ifndef __MINGW32__ #ifndef __MINGW32__
if (user[0]) {
if (user[0])
{
/* Convert user to a long integer if it is a non-negative number. /* Convert user to a long integer if it is a non-negative number.
* -1 means it is a user name. */ * -1 means it is a user name. */
long uid = -1; long uid = -1;
if (ss_isnumeric(user)) {
if (ss_isnumeric(user))
{
errno = 0; errno = 0;
char *endptr; char *endptr;
uid = strtol(user, &endptr, 10); uid = strtol(user, &endptr, 10);
@ -112,18 +117,21 @@ run_as(const char *user)
size_t buflen; size_t buflen;
int err; int err;
for (buflen = 128;; buflen *= 2) {
char buf[buflen]; /* variable length array */
for (buflen = 128;; buflen *= 2)
{
char buf[buflen]; /* variable length array */
/* Note that we use getpwnam_r() instead of getpwnam(), /* Note that we use getpwnam_r() instead of getpwnam(),
* which returns its result in a statically allocated buffer and * which returns its result in a statically allocated buffer and
* cannot be considered thread safe. */ * cannot be considered thread safe. */
err = uid >= 0 ? getpwuid_r((uid_t)uid, &pwdbuf, buf, buflen, &pwd) err = uid >= 0 ? getpwuid_r((uid_t)uid, &pwdbuf, buf, buflen, &pwd)
: getpwnam_r(user, &pwdbuf, buf, buflen, &pwd);
: getpwnam_r(user, &pwdbuf, buf, buflen, &pwd);
if (err == 0 && pwd) {
if (err == 0 && pwd)
{
/* setgid first, because we may not be allowed to do it anymore after setuid */ /* setgid first, because we may not be allowed to do it anymore after setuid */
if (setgid(pwd->pw_gid) != 0) {
if (setgid(pwd->pw_gid) != 0)
{
LOGE( LOGE(
"Could not change group id to that of run_as user '%s': %s", "Could not change group id to that of run_as user '%s': %s",
pwd->pw_name, strerror(errno)); pwd->pw_name, strerror(errno));
@ -131,28 +139,37 @@ run_as(const char *user)
} }
#ifndef __CYGWIN__ #ifndef __CYGWIN__
if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
if (initgroups(pwd->pw_name, pwd->pw_gid) == -1)
{
LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name);
return 0; return 0;
} }
#endif #endif
if (setuid(pwd->pw_uid) != 0) {
if (setuid(pwd->pw_uid) != 0)
{
LOGE( LOGE(
"Could not change user id to that of run_as user '%s': %s", "Could not change user id to that of run_as user '%s': %s",
pwd->pw_name, strerror(errno)); pwd->pw_name, strerror(errno));
return 0; return 0;
} }
break; break;
} else if (err != ERANGE) {
if (err) {
}
else if (err != ERANGE)
{
if (err)
{
LOGE("run_as user '%s' could not be found: %s", user, LOGE("run_as user '%s' could not be found: %s", user,
strerror(err)); strerror(err));
} else {
}
else
{
LOGE("run_as user '%s' could not be found.", user); LOGE("run_as user '%s' could not be found.", user);
} }
return 0; return 0;
} else if (buflen >= 16 * 1024) {
}
else if (buflen >= 16 * 1024)
{
/* If getpwnam_r() seems defective, call it quits rather than /* If getpwnam_r() seems defective, call it quits rather than
* keep on allocating ever larger buffers until we crash. */ * keep on allocating ever larger buffers until we crash. */
LOGE( LOGE(
@ -166,21 +183,25 @@ run_as(const char *user)
/* No getpwnam_r() :-( We'll use getpwnam() and hope for the best. */ /* No getpwnam_r() :-( We'll use getpwnam() and hope for the best. */
struct passwd *pwd; struct passwd *pwd;
if (!(pwd = uid >= 0 ? getpwuid((uid_t)uid) : getpwnam(user))) {
if (!(pwd = uid >= 0 ? getpwuid((uid_t)uid) : getpwnam(user)))
{
LOGE("run_as user %s could not be found.", user); LOGE("run_as user %s could not be found.", user);
return 0; return 0;
} }
/* setgid first, because we may not allowed to do it anymore after setuid */ /* setgid first, because we may not allowed to do it anymore after setuid */
if (setgid(pwd->pw_gid) != 0) {
if (setgid(pwd->pw_gid) != 0)
{
LOGE("Could not change group id to that of run_as user '%s': %s", LOGE("Could not change group id to that of run_as user '%s': %s",
pwd->pw_name, strerror(errno)); pwd->pw_name, strerror(errno));
return 0; return 0;
} }
if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
if (initgroups(pwd->pw_name, pwd->pw_gid) == -1)
{
LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name);
return 0; return 0;
} }
if (setuid(pwd->pw_uid) != 0) {
if (setuid(pwd->pw_uid) != 0)
{
LOGE("Could not change user id to that of run_as user '%s': %s", LOGE("Could not change user id to that of run_as user '%s': %s",
pwd->pw_name, strerror(errno)); pwd->pw_name, strerror(errno));
return 0; return 0;
@ -200,7 +221,8 @@ ss_strndup(const char *s, size_t n)
size_t len = strlen(s); size_t len = strlen(s);
char *ret; char *ret;
if (len <= n) {
if (len <= n)
{
return strdup(s); return strdup(s);
} }
@ -210,59 +232,7 @@ ss_strndup(const char *s, size_t n)
return ret; return ret;
} }
void *
ss_malloc(size_t size)
{
void *tmp = malloc(size);
if (tmp == NULL)
exit(EXIT_FAILURE);
return tmp;
}
void *
ss_aligned_malloc(size_t size)
{
int err;
void *tmp = NULL;
#ifdef HAVE_POSIX_MEMALIGN
/* ensure 16 byte alignment */
err = posix_memalign(&tmp, 16, size);
#elif __MINGW32__
tmp = _aligned_malloc(size, 16);
err = tmp == NULL;
#else
err = -1;
#endif
if (err) {
return ss_malloc(size);
} else {
return tmp;
}
}
void *
ss_realloc(void *ptr, size_t new_size)
{
void *new = realloc(ptr, new_size);
if (new == NULL) {
free(ptr);
ptr = NULL;
exit(EXIT_FAILURE);
}
return new;
}
void *
ss_calloc(size_t num, size_t size)
{
void *tmp = calloc(num, size);
if (tmp == NULL)
exit(EXIT_FAILURE);
return tmp;
}
int
ss_is_ipv6addr(const char *addr)
int ss_is_ipv6addr(const char *addr)
{ {
return strcmp(addr, ":") > 0; return strcmp(addr, ":") > 0;
} }
@ -279,7 +249,7 @@ trim_whitespace(char *str)
while (isspace((unsigned char)*str)) while (isspace((unsigned char)*str))
str++; str++;
if (*str == 0) // All spaces?
if (*str == 0) // All spaces?
return str; return str;
// Trim trailing space // Trim trailing space
@ -293,8 +263,7 @@ trim_whitespace(char *str)
return str; return str;
} }
void
usage()
void usage()
{ {
printf("\n"); printf("\n");
printf("shadowsocks-libev %s\n\n", VERSION); printf("shadowsocks-libev %s\n\n", VERSION);
@ -432,8 +401,7 @@ usage()
printf("\n"); printf("\n");
} }
void
daemonize(const char *path)
void daemonize(const char *path)
{ {
#ifndef __MINGW32__ #ifndef __MINGW32__
/* Our process ID and Session ID */ /* Our process ID and Session ID */
@ -441,15 +409,18 @@ daemonize(const char *path)
/* Fork off the parent process */ /* Fork off the parent process */
pid = fork(); pid = fork();
if (pid < 0) {
if (pid < 0)
{
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* If we got a good PID, then /* If we got a good PID, then
* we can exit the parent process. */ * we can exit the parent process. */
if (pid > 0) {
if (pid > 0)
{
FILE *file = fopen(path, "w"); FILE *file = fopen(path, "w");
if (file == NULL) {
if (file == NULL)
{
FATAL("Invalid pid file\n"); FATAL("Invalid pid file\n");
} }
@ -465,23 +436,28 @@ daemonize(const char *path)
/* Create a new SID for the child process */ /* Create a new SID for the child process */
sid = setsid(); sid = setsid();
if (sid < 0) {
if (sid < 0)
{
/* Log the failure */ /* Log the failure */
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Change the current working directory */ /* Change the current working directory */
if ((chdir("/")) < 0) {
if ((chdir("/")) < 0)
{
/* Log the failure */ /* Log the failure */
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
int dev_null = open("/dev/null", O_WRONLY); int dev_null = open("/dev/null", O_WRONLY);
if (dev_null) {
if (dev_null)
{
/* Redirect to null device */ /* Redirect to null device */
dup2(dev_null, STDOUT_FILENO); dup2(dev_null, STDOUT_FILENO);
dup2(dev_null, STDERR_FILENO); dup2(dev_null, STDERR_FILENO);
} else {
}
else
{
/* Close the standard file descriptors */ /* Close the standard file descriptors */
close(STDOUT_FILENO); close(STDOUT_FILENO);
close(STDERR_FILENO); close(STDERR_FILENO);
@ -495,24 +471,30 @@ daemonize(const char *path)
} }
#ifdef HAVE_SETRLIMIT #ifdef HAVE_SETRLIMIT
int
set_nofile(int nofile)
int set_nofile(int nofile)
{ {
struct rlimit limit = { nofile, nofile }; /* set both soft and hard limit */
struct rlimit limit = {nofile, nofile}; /* set both soft and hard limit */
if (nofile <= 0) {
if (nofile <= 0)
{
FATAL("nofile must be greater than 0\n"); FATAL("nofile must be greater than 0\n");
} }
if (setrlimit(RLIMIT_NOFILE, &limit) < 0) {
if (errno == EPERM) {
if (setrlimit(RLIMIT_NOFILE, &limit) < 0)
{
if (errno == EPERM)
{
LOGE( LOGE(
"insufficient permission to change NOFILE, not starting as root?"); "insufficient permission to change NOFILE, not starting as root?");
return -1; return -1;
} else if (errno == EINVAL) {
}
else if (errno == EINVAL)
{
LOGE("invalid nofile, decrease nofile and try again"); LOGE("invalid nofile, decrease nofile and try again");
return -1; return -1;
} else {
}
else
{
LOGE("setrlimit failed: %s", strerror(errno)); LOGE("setrlimit failed: %s", strerror(errno));
return -1; return -1;
} }
@ -526,23 +508,25 @@ set_nofile(int nofile)
size_t size_t
readoff_from(char **content, const char *file) readoff_from(char **content, const char *file)
{ {
FILE *f = strcmp(file, "-") == 0 ?
stdin : fopen(file, "r");
if (f == NULL) {
FILE *f = strcmp(file, "-") == 0 ? stdin : fopen(file, "r");
if (f == NULL)
{
FATAL("Invalid file path %s", file); FATAL("Invalid file path %s", file);
} }
size_t pos = 0; size_t pos = 0;
char buf[1024] = { 0 }; char buf[1024] = { 0 };
while (fgets(buf, sizeof(buf), f)) {
while (fgets(buf, sizeof(buf), f))
{
size_t len = strlen(buf); size_t len = strlen(buf);
*content = ss_realloc(*content, pos + len); *content = ss_realloc(*content, pos + len);
strncpy(*content + pos, buf, len); strncpy(*content + pos, buf, len);
pos += len; pos += len;
} }
if (ferror(f)) {
if (ferror(f))
{
FATAL("Failed to read the file.") FATAL("Failed to read the file.")
} }
@ -557,22 +541,27 @@ get_default_conf(void)
#ifndef __MINGW32__ #ifndef __MINGW32__
static char sysconf[] = "/etc/shadowsocks-libev/config.json"; static char sysconf[] = "/etc/shadowsocks-libev/config.json";
static char *userconf = NULL; static char *userconf = NULL;
static int buf_size = 0;
static int buf_size = 0;
char *conf_home; char *conf_home;
conf_home = getenv("XDG_CONFIG_HOME"); conf_home = getenv("XDG_CONFIG_HOME");
// Memory of userconf only gets allocated once, and will not be // Memory of userconf only gets allocated once, and will not be
// freed. It is used as static buffer. // freed. It is used as static buffer.
if (!conf_home) {
if (buf_size == 0) {
if (!conf_home)
{
if (buf_size == 0)
{
buf_size = 50 + strlen(getenv("HOME")); buf_size = 50 + strlen(getenv("HOME"));
userconf = malloc(buf_size); userconf = malloc(buf_size);
} }
snprintf(userconf, buf_size, "%s%s", getenv("HOME"), snprintf(userconf, buf_size, "%s%s", getenv("HOME"),
"/.config/shadowsocks-libev/config.json"); "/.config/shadowsocks-libev/config.json");
} else {
if (buf_size == 0) {
}
else
{
if (buf_size == 0)
{
buf_size = 50 + strlen(conf_home); buf_size = 50 + strlen(conf_home);
userconf = malloc(buf_size); userconf = malloc(buf_size);
} }
@ -590,3 +579,43 @@ get_default_conf(void)
return "config.json"; return "config.json";
#endif #endif
} }
/**
* strtotime
* ---------------------
* Convert a string to a time value in seconds.
* written by Thomas Moestl.
rewritten in C from scratch by Paul A. Rombouts
*
*/
time_t
strtotime(char *str)
{
time_t retval = 0, t;
char c;
while (isalnum(c = *str))
{
if (!isdigit(c))
break;
t = strtol(str, &str, 10);
struct { char c; time_t t; } tmtable[] = {
{ 's', 1 }, { 'm', 60 }, { 'h', 60 },
{ 'd', 24 }, { 'w', 7 }
};
if (isalpha(c = *str))
{
for (int i = 0; i < nelem(tmtable) && tmtable[i++].c != c;) {
t *= tmtable[i].t;
}
++str;
}
retval += t;
}
return retval;
}

101
src/utils.h

@ -176,35 +176,22 @@ void ss_color_reset(void);
exit(-1); \ exit(-1); \
} }
char *ss_itoa(int i);
int ss_isnumeric(const char *s);
int run_as(const char *user); int run_as(const char *user);
void usage(void); void usage(void);
void daemonize(const char *path); void daemonize(const char *path);
char *ss_itoa(int i);
int ss_isnumeric(const char *s);
char *ss_strndup(const char *s, size_t n); char *ss_strndup(const char *s, size_t n);
#ifdef HAVE_SETRLIMIT #ifdef HAVE_SETRLIMIT
int set_nofile(int nofile); int set_nofile(int nofile);
#endif #endif
void *ss_malloc(size_t size);
void *ss_aligned_malloc(size_t size);
void *ss_realloc(void *ptr, size_t new_size);
void *ss_calloc(size_t num, size_t size);
#define ss_free(ptr) { \ #define ss_free(ptr) { \
free(ptr); \ free(ptr); \
ptr = NULL; \ ptr = NULL; \
} }
static inline char *
currtime_readable()
{
time_t now = time(NULL);
static char timestr[20] = {};
strftime(timestr, 20, TIME_FORMAT, localtime(&now));
return timestr;
}
#ifdef __MINGW32__ #ifdef __MINGW32__
#define ss_aligned_free(ptr) { \ #define ss_aligned_free(ptr) { \
_aligned_free(ptr); \ _aligned_free(ptr); \
@ -214,10 +201,92 @@ currtime_readable()
#define ss_aligned_free(ptr) ss_free(ptr) #define ss_aligned_free(ptr) ss_free(ptr)
#endif #endif
inline void *
ss_malloc(size_t size)
{
void *tmp = malloc(size);
if (tmp == NULL)
exit(EXIT_FAILURE);
return tmp;
}
inline void *
ss_aligned_malloc(size_t size)
{
int err;
void *tmp = NULL;
#ifdef HAVE_POSIX_MEMALIGN
/* ensure 16 byte alignment */
err = posix_memalign(&tmp, 16, size);
#elif __MINGW32__
tmp = _aligned_malloc(size, 16);
err = tmp == NULL;
#else
err = -1;
#endif
return err ? ss_malloc(size) : tmp;
}
inline void *
ss_realloc(void *ptr, size_t new_size)
{
void *new = realloc(ptr, new_size);
if (new == NULL)
{
free(ptr);
ptr = NULL;
exit(EXIT_FAILURE);
}
return new;
}
inline void *
ss_calloc(size_t num, size_t size)
{
void *tmp = calloc(num, size);
if (tmp == NULL)
exit(EXIT_FAILURE);
return tmp;
}
int ss_is_ipv6addr(const char *addr); int ss_is_ipv6addr(const char *addr);
char *trim_whitespace(char *str); char *trim_whitespace(char *str);
// file operations
#define current_dir(path) \
realpath(dirname(strdup(path)), NULL)
#define setcwd(path) \
if (chdir(path) != 0) \
ERROR("setcwd");
size_t readoff_from(char **content, const char *file); size_t readoff_from(char **content, const char *file);
char *get_default_conf(void); char *get_default_conf(void);
// time functions
time_t strtotime(char *str);
static inline char *
currtime_readable()
{
time_t now = time(NULL);
static char timestr[20] = {};
strftime(timestr, 20, TIME_FORMAT, localtime(&now));
return timestr;
}
// libcork extension functions
#include <libcork/ds.h>
#define cork_array_merge(dst, src) \
for (int i = 0; i < cork_array_size(src); i++) { \
cork_array_append(dst, cork_array_at(src, i)); \
}
inline void
cork_dllist_merge(struct cork_dllist *dst, struct cork_dllist *src)
{
struct cork_dllist_item *curr, *next;
cork_dllist_foreach_void(src, curr, next)
cork_dllist_add(dst, curr);
}
#endif // _UTILS_H #endif // _UTILS_H
Loading…
Cancel
Save