diff --git a/src/main/java/io/zhile/research/ja/netfilter/Launcher.java b/src/main/java/io/zhile/research/ja/netfilter/Launcher.java index 89cfdf3..6d8a320 100644 --- a/src/main/java/io/zhile/research/ja/netfilter/Launcher.java +++ b/src/main/java/io/zhile/research/ja/netfilter/Launcher.java @@ -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); diff --git a/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java b/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java index b5fb5f8..d27ab65 100644 --- a/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java +++ b/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java @@ -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); diff --git a/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigDetector.java b/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigDetector.java new file mode 100644 index 0000000..3fd2ae2 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigDetector.java @@ -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; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigParser.java b/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigParser.java new file mode 100644 index 0000000..eb2542b --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/commons/ConfigParser.java @@ -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> parse(File file) throws Exception { + Map> 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; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java b/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java index cda875a..dfe0baf 100644 --- a/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java +++ b/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java @@ -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 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; } diff --git a/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java b/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java index 7038b81..1fdf48c 100644 --- a/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java +++ b/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java @@ -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 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"); } diff --git a/src/main/java/io/zhile/research/ja/netfilter/models/FilterConfig.java b/src/main/java/io/zhile/research/ja/netfilter/models/FilterConfig.java new file mode 100644 index 0000000..6ec4507 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/models/FilterConfig.java @@ -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> data; + + public FilterConfig(Map> data) { + this.data = data; + } + + public static FilterConfig getCurrent() { + return current; + } + + public static void setCurrent(FilterConfig current) { + FilterConfig.current = current; + } + + public static List getBySection(String section) { + do { + if (null == current) { + break; + } + + if (null == current.data) { + break; + } + + List list = current.data.get(section); + if (null == list) { + break; + } + + return list; + } while (false); + + return new ArrayList<>(); + } +} \ No newline at end of file diff --git a/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java b/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java index 659b3ec..b4c7a65 100644 --- a/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java +++ b/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java @@ -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 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; } diff --git a/src/main/java/io/zhile/research/ja/netfilter/utils/StringUtils.java b/src/main/java/io/zhile/research/ja/netfilter/utils/StringUtils.java new file mode 100644 index 0000000..c78a8e3 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/utils/StringUtils.java @@ -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(); + } +}