add config file

Signed-off-by: pengzhile <pengzhile@gmail.com>
This commit is contained in:
pengzhile 2021-11-29 16:21:12 +08:00
parent f4a6ff6d43
commit 52af80fb7f
9 changed files with 283 additions and 27 deletions

View File

@ -1,5 +1,11 @@
package io.zhile.research.ja.netfilter;
import io.zhile.research.ja.netfilter.commons.ConfigDetector;
import io.zhile.research.ja.netfilter.commons.ConfigParser;
import io.zhile.research.ja.netfilter.commons.DebugInfo;
import io.zhile.research.ja.netfilter.models.FilterConfig;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.net.MalformedURLException;
@ -25,6 +31,19 @@ public class Launcher {
throw new RuntimeException("Can not access ja-netfilter jar file.", e);
}
File configFile = ConfigDetector.detect(new File(jarURL.getPath()).getParentFile().getPath(), args);
if (null == configFile) {
DebugInfo.output("Could not find any configuration files.");
} else {
DebugInfo.output("Current config file: " + configFile.getPath());
}
try {
FilterConfig.setCurrent(new FilterConfig(ConfigParser.parse(configFile)));
} catch (Exception e) {
DebugInfo.output(e.getMessage());
}
for (Class<?> c : inst.getAllLoadedClasses()) {
try {
inst.retransformClasses(c);

View File

@ -34,7 +34,7 @@ public class TransformDispatcher implements ClassFileTransformer {
try {
return transformer.transform(className, classFileBuffer);
} catch (Exception e) {
DebugInfo.output("=== Transform class failed: " + e.getMessage());
DebugInfo.output("Transform class failed: " + e.getMessage());
}
} while (false);

View File

@ -0,0 +1,89 @@
package io.zhile.research.ja.netfilter.commons;
import io.zhile.research.ja.netfilter.utils.StringUtils;
import java.io.File;
public class ConfigDetector {
private static final String CONFIG_FILENAME = "janf_config.txt";
public static File detect(String currentDirectory, String args) {
File configFile = tryFile(args); // by javaagent argument
if (null == configFile) {
configFile = tryFile(System.getenv("JANF_CONFIG")); // by env
}
if (null == configFile) {
configFile = tryFile(System.getProperty("janf.config")); // by -D argument
}
if (null == configFile) {
configFile = searchDirectory(currentDirectory); // in the same dir as the jar
}
String userHome = System.getProperty("user.home");
if (null == configFile) {
configFile = searchDirectory(userHome, "." + CONFIG_FILENAME); // $HOME/.janf_config.txt
}
if (null == configFile) {
configFile = searchDirectory(userHome + File.pathSeparator + ".config"); // $HOME/.config/janf_config.txt
}
if (null == configFile) {
configFile = searchDirectory(userHome + File.pathSeparator + ".local" + File.pathSeparator + "/etc"); // $HOME/.local/etc/janf_config.txt
}
if (null == configFile) {
configFile = searchDirectory("/usr/local/etc"); // /usr/local/etc/janf_config.txt
}
if (null == configFile) {
configFile = searchDirectory("/etc"); // /etc/janf_config.txt
}
return configFile;
}
private static File searchDirectory(String dirPath) {
return searchDirectory(dirPath, CONFIG_FILENAME);
}
private static File searchDirectory(String dirPath, String filename) {
if (StringUtils.isEmpty(dirPath)) {
return null;
}
File dirFile = new File(dirPath);
if (!dirFile.isDirectory()) {
return null;
}
return tryFile(new File(dirFile, filename));
}
private static File tryFile(String filePath) {
if (StringUtils.isEmpty(filePath)) {
return null;
}
return tryFile(new File(filePath));
}
private static File tryFile(File file) {
if (!file.exists()) {
return null;
}
if (!file.isFile()) {
return null;
}
if (!file.canRead()) {
return null;
}
return file;
}
}

View File

@ -0,0 +1,90 @@
package io.zhile.research.ja.netfilter.commons;
import io.zhile.research.ja.netfilter.models.FilterRule;
import io.zhile.research.ja.netfilter.utils.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ConfigParser {
public static Map<String, List<FilterRule>> parse(File file) throws Exception {
Map<String, List<FilterRule>> map = new HashMap<>();
if (null == file) {
return map;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
int lineNumber = 0;
String line, lastSection = null;
while (null != (line = reader.readLine())) {
lineNumber++;
line = line.trim();
if (StringUtils.isEmpty(line)) {
continue;
}
int len = line.length();
switch (line.charAt(0)) {
case '[':
if (']' != line.charAt(len - 1)) {
throw new Exception("Invalid section! Line: " + lineNumber);
}
String section = line.substring(1, len - 1);
if (StringUtils.isEmpty(section)) {
throw new Exception("Empty section name! Line: " + lineNumber);
}
lastSection = section;
map.put(lastSection, new ArrayList<>());
break;
case '#':
case ';':
break; // comment
case '/':
if (len > 1 && '/' == line.charAt(1)) {
break; // comment
}
throw new Exception("Invalid character! Line: " + lineNumber);
default:
if (null == lastSection) {
break; // ignore rules without section
}
String[] parts = line.split(",", 2);
if (2 != parts.length) {
throw new Exception("Invalid rule! Line: " + lineNumber);
}
String type = parts[0].trim();
String content = parts[1].trim();
if (StringUtils.isEmpty(type) || StringUtils.isEmpty(content)) {
throw new Exception("Invalid rule! Line: " + lineNumber);
}
if (!Character.isAlphabetic(type.charAt(0))) {
throw new Exception("Invalid rule! Line: " + lineNumber);
}
FilterRule rule = FilterRule.of(type, content);
if (null == rule) {
throw new Exception("Invalid rule type! Line: " + lineNumber);
}
map.get(lastSection).add(rule);
DebugInfo.output("Add section: " + lastSection + ", rule: " + rule);
break;
}
}
}
return map;
}
}

View File

@ -1,33 +1,26 @@
package io.zhile.research.ja.netfilter.filters;
import io.zhile.research.ja.netfilter.commons.DebugInfo;
import io.zhile.research.ja.netfilter.enums.RuleType;
import io.zhile.research.ja.netfilter.models.FilterConfig;
import io.zhile.research.ja.netfilter.models.FilterRule;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
public class DNSFilter {
public static final List<FilterRule> RULES;
static {
RULES = new ArrayList<>(); // TODO read from config file
RULES.add(new FilterRule(RuleType.REGEXP, ".*?zhile.io"));
}
private static final String SECTION_NAME = "DNS";
public static String testQuery(String host) throws IOException {
if (null == host) {
return null;
}
for (FilterRule rule : RULES) {
for (FilterRule rule : FilterConfig.getBySection(SECTION_NAME)) {
if (!rule.test(host)) {
continue;
}
DebugInfo.output("=== reject dns query: " + host + ", rule: " + rule);
DebugInfo.output("Reject dns query: " + host + ", rule: " + rule);
throw new java.net.UnknownHostException();
}
@ -39,12 +32,12 @@ public class DNSFilter {
return null;
}
for (FilterRule rule : RULES) {
for (FilterRule rule : FilterConfig.getBySection(SECTION_NAME)) {
if (!rule.test(n.getHostName())) {
continue;
}
DebugInfo.output("=== reject dns reachable test: " + n.getHostName() + ", rule: " + rule);
DebugInfo.output("Reject dns reachable test: " + n.getHostName() + ", rule: " + rule);
return false;
}

View File

@ -1,34 +1,27 @@
package io.zhile.research.ja.netfilter.filters;
import io.zhile.research.ja.netfilter.commons.DebugInfo;
import io.zhile.research.ja.netfilter.enums.RuleType;
import io.zhile.research.ja.netfilter.models.FilterConfig;
import io.zhile.research.ja.netfilter.models.FilterRule;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class URLFilter {
public static final List<FilterRule> RULES;
static {
RULES = new ArrayList<>(); // TODO read from config file
RULES.add(new FilterRule(RuleType.PREFIX, "https://zhile.io"));
}
public static final String SECTION_NAME = "URL";
public static URL testURL(URL url) throws IOException {
if (null == url) {
return null;
}
for (FilterRule rule : RULES) {
for (FilterRule rule : FilterConfig.getBySection(SECTION_NAME)) {
if (!rule.test(url.toString())) {
continue;
}
DebugInfo.output("=== reject url: " + url + ", rule: " + rule);
DebugInfo.output("Reject url: " + url + ", rule: " + rule);
throw new SocketTimeoutException("connect timed out");
}

View File

@ -0,0 +1,44 @@
package io.zhile.research.ja.netfilter.models;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FilterConfig {
private static FilterConfig current;
private final Map<String, List<FilterRule>> data;
public FilterConfig(Map<String, List<FilterRule>> data) {
this.data = data;
}
public static FilterConfig getCurrent() {
return current;
}
public static void setCurrent(FilterConfig current) {
FilterConfig.current = current;
}
public static List<FilterRule> getBySection(String section) {
do {
if (null == current) {
break;
}
if (null == current.data) {
break;
}
List<FilterRule> list = current.data.get(section);
if (null == list) {
break;
}
return list;
} while (false);
return new ArrayList<>();
}
}

View File

@ -2,9 +2,21 @@ package io.zhile.research.ja.netfilter.models;
import io.zhile.research.ja.netfilter.enums.RuleType;
public class FilterRule {
private RuleType type;
import java.util.HashMap;
import java.util.Map;
public class FilterRule {
private static final Map<String, RuleType> SUPPORTED_TYPE_MAP;
static {
SUPPORTED_TYPE_MAP = new HashMap<>();
for (RuleType ruleType : RuleType.values()) {
SUPPORTED_TYPE_MAP.put(ruleType.name(), ruleType);
}
}
private RuleType type;
private String rule;
public FilterRule(RuleType type, String rule) {
@ -12,6 +24,15 @@ public class FilterRule {
this.rule = rule;
}
public static FilterRule of(String typeStr, String content) {
RuleType type = SUPPORTED_TYPE_MAP.get(typeStr.toUpperCase());
if (null == type) {
return null;
}
return new FilterRule(type, content);
}
public RuleType getType() {
return type;
}

View File

@ -0,0 +1,7 @@
package io.zhile.research.ja.netfilter.utils;
public class StringUtils {
public static boolean isEmpty(String str) {
return null == str || str.isEmpty();
}
}