diff --git a/README.md b/README.md index 02bbb0d..a698cd0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ja-netfilter v1.2.0 +# ja-netfilter v2.0.0 ### A javaagent framework @@ -10,17 +10,7 @@ * some apps support the `JVM Options file`, you can add as a line of the `JVM Options file`. * **WARNING: DO NOT put some unnecessary whitespace characters!** -* edit your own rule list config file. The `ja-netfilter` will look for it in the following order(find one and stop searching): - * passed as args of `-javaagent`. eg: `-javaagent:/absolute/path/to/ja-netfilter.jar=/home/neo/downloads/janf_config.txt` - * file path in environment variable: `JANF_CONFIG` - * file path in `java` startup property: `janf.config`. `eg: java -Djanf.config="/home/neo/downloads/janf_config.txt"` - * some apps support the `JVM Options file`, you can add as a line of the `JVM Options file`. `eg: -Djanf.config="/home/neo/downloads/janf_config.txt"` - * file path in the same dir as the `ja-netfilter.jar`, no need for additional configuration (**PREFERRED!**) - * file path in your home directory, named: `.janf_config.txt`. `eg: /home/neo/.janf_config.txt` - * file path in the subdirectory named `.config` in your home directory. `eg: /home/neo/.config/janf_config.txt` - * file path in the subdirectory named `.local/etc` in your home directory. `eg: /home/neo/.local/ect/janf_config.txt` - * file path in the directory named `/usr/local/etc`. `eg: /usr/local/etc/janf_config.txt` - * file path in the directory named `/etc`. eg: `/etc/janf_config.txt` +* edit your plugin config files: `${lower plugin name}.conf` file in the `conf` dir where `ja-netfilter.jar` is located. * run your java application and enjoy~ @@ -28,8 +18,9 @@ ``` [ABC] -# for the specified plugin called "ABC" +# for the specified section name +# for example [URL] EQUAL,https://someurl @@ -47,6 +38,7 @@ EQUAL,somedomain # REGEXP Use regular expressions to match ``` + ## Debug info * the `ja-netfilter` will **NOT** output debugging information by default diff --git a/pom.xml b/pom.xml index bb0505d..612102e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.ja-netfilter ja-netfilter - 1.2.0 + 2.0.0 UTF-8 diff --git a/src/main/java/com/janetfilter/core/Dispatcher.java b/src/main/java/com/janetfilter/core/Dispatcher.java index 848cf56..b269516 100644 --- a/src/main/java/com/janetfilter/core/Dispatcher.java +++ b/src/main/java/com/janetfilter/core/Dispatcher.java @@ -12,7 +12,7 @@ public final class Dispatcher implements ClassFileTransformer { private final Set classSet = new TreeSet<>(); private final Map> transformerMap = new HashMap<>(); - public void addTransformer(MyTransformer transformer) { + public synchronized void addTransformer(MyTransformer transformer) { String className = transformer.getHookClassName(); classSet.add(className.replace('/', '.')); List transformers = transformerMap.computeIfAbsent(className, k -> new ArrayList<>()); diff --git a/src/main/java/com/janetfilter/core/Environment.java b/src/main/java/com/janetfilter/core/Environment.java index 0fbe28f..312fdd2 100644 --- a/src/main/java/com/janetfilter/core/Environment.java +++ b/src/main/java/com/janetfilter/core/Environment.java @@ -5,11 +5,13 @@ import java.io.File; public final class Environment { private final File baseDir; private final File agentFile; + private final File configDir; private final File pluginsDir; public Environment(File agentFile) { this.agentFile = agentFile; baseDir = agentFile.getParentFile(); + configDir = new File(baseDir, "config"); pluginsDir = new File(baseDir, "plugins"); } @@ -21,6 +23,10 @@ public final class Environment { return agentFile; } + public File getConfigDir() { + return configDir; + } + public File getPluginsDir() { return pluginsDir; } diff --git a/src/main/java/com/janetfilter/core/Initializer.java b/src/main/java/com/janetfilter/core/Initializer.java index ceadb51..9d887d6 100644 --- a/src/main/java/com/janetfilter/core/Initializer.java +++ b/src/main/java/com/janetfilter/core/Initializer.java @@ -1,32 +1,15 @@ package com.janetfilter.core; -import com.janetfilter.core.commons.ConfigDetector; -import com.janetfilter.core.commons.ConfigParser; import com.janetfilter.core.commons.DebugInfo; -import com.janetfilter.core.models.FilterConfig; import com.janetfilter.core.plugin.PluginManager; -import java.io.File; import java.lang.instrument.Instrumentation; import java.util.Set; public class Initializer { - public static void init(String args, Instrumentation inst, Environment environment) { - File configFile = ConfigDetector.detect(environment.getBaseDir(), 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 (Throwable e) { - DebugInfo.output(e.getMessage()); - } - + public static void init(Instrumentation inst, Environment environment) { Dispatcher dispatcher = new Dispatcher(); - new PluginManager(dispatcher, environment).loadPlugins(inst); + new PluginManager(inst, dispatcher, environment).loadPlugins(); inst.addTransformer(dispatcher, true); diff --git a/src/main/java/com/janetfilter/core/Launcher.java b/src/main/java/com/janetfilter/core/Launcher.java index a2d0b71..dfa71fc 100644 --- a/src/main/java/com/janetfilter/core/Launcher.java +++ b/src/main/java/com/janetfilter/core/Launcher.java @@ -9,7 +9,7 @@ import java.net.URL; import java.util.jar.JarFile; public class Launcher { - private static final String VERSION = "v1.2.0"; + private static final String VERSION = "v2.0.0"; public static void main(String[] args) { printUsage(); @@ -34,7 +34,7 @@ public class Launcher { return; } - Initializer.init(args, inst, new Environment(agentFile)); // for some custom UrlLoaders + Initializer.init(inst, new Environment(agentFile)); // for some custom UrlLoaders } private static void printUsage() { @@ -58,7 +58,7 @@ public class Launcher { return url.toURI(); } - String resourcePath = "/b7e909d6ba41ae03fb85af5b8ba702709f5798cf.txt"; + String resourcePath = "/5a1666cf298cd1d4fa64d62d123af55f5f39024f.txt"; url = Launcher.class.getResource(resourcePath); if (null == url) { throw new Exception("Can not locate resource file."); diff --git a/src/main/java/com/janetfilter/core/commons/ConfigDetector.java b/src/main/java/com/janetfilter/core/commons/ConfigDetector.java deleted file mode 100644 index 2c1c7be..0000000 --- a/src/main/java/com/janetfilter/core/commons/ConfigDetector.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.janetfilter.core.commons; - -import com.janetfilter.core.utils.StringUtils; - -import java.io.File; - -public class ConfigDetector { - private static final String CONFIG_FILENAME = "janf_config.txt"; - - public static File detect(File currentDirectory, String args) { - return detect(currentDirectory.getPath(), args); - } - - 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/com/janetfilter/core/commons/ConfigParser.java b/src/main/java/com/janetfilter/core/commons/ConfigParser.java index b913df6..a873038 100644 --- a/src/main/java/com/janetfilter/core/commons/ConfigParser.java +++ b/src/main/java/com/janetfilter/core/commons/ConfigParser.java @@ -17,7 +17,7 @@ public class ConfigParser { public static Map> parse(File file) throws Exception { Map> map = new HashMap<>(); - if (null == file) { + if (null == file || !file.exists() || !file.isFile() || !file.canRead()) { return map; } @@ -86,6 +86,7 @@ public class ConfigParser { } } + DebugInfo.output("Config file loaded: " + file); return map; } } diff --git a/src/main/java/com/janetfilter/core/commons/DebugInfo.java b/src/main/java/com/janetfilter/core/commons/DebugInfo.java index f950177..d93a1cd 100644 --- a/src/main/java/com/janetfilter/core/commons/DebugInfo.java +++ b/src/main/java/com/janetfilter/core/commons/DebugInfo.java @@ -16,5 +16,6 @@ public class DebugInfo { String caller = traces.length < 2 ? "UNKNOWN" : traces[1].toString(); System.out.printf(template, DateUtils.formatNow(), caller, content); + System.out.flush(); } } diff --git a/src/main/java/com/janetfilter/core/models/FilterConfig.java b/src/main/java/com/janetfilter/core/models/FilterConfig.java deleted file mode 100644 index 85a3ede..0000000 --- a/src/main/java/com/janetfilter/core/models/FilterConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.janetfilter.core.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/com/janetfilter/core/models/FilterRule.java b/src/main/java/com/janetfilter/core/models/FilterRule.java index bbacb48..409a645 100644 --- a/src/main/java/com/janetfilter/core/models/FilterRule.java +++ b/src/main/java/com/janetfilter/core/models/FilterRule.java @@ -55,9 +55,6 @@ public class FilterRule { @Override public String toString() { - return "{" + - "type=" + type + - ", rule='" + rule + '\'' + - '}'; + return "{type=" + type + ", rule=" + rule + "}"; } -} \ No newline at end of file +} diff --git a/src/main/java/com/janetfilter/core/plugin/PluginConfig.java b/src/main/java/com/janetfilter/core/plugin/PluginConfig.java new file mode 100644 index 0000000..9ca3b01 --- /dev/null +++ b/src/main/java/com/janetfilter/core/plugin/PluginConfig.java @@ -0,0 +1,30 @@ +package com.janetfilter.core.plugin; + +import com.janetfilter.core.models.FilterRule; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PluginConfig { + private final File file; + private final Map> data; + + public PluginConfig(File file, Map> data) { + this.file = file; + this.data = data; + } + + public List getBySection(String section) { + return data.getOrDefault(section, new ArrayList<>()); + } + + public File getFile() { + return file; + } + + public Map> getData() { + return data; + } +} diff --git a/src/main/java/com/janetfilter/core/plugin/PluginEntry.java b/src/main/java/com/janetfilter/core/plugin/PluginEntry.java index 5f838e2..961cc42 100644 --- a/src/main/java/com/janetfilter/core/plugin/PluginEntry.java +++ b/src/main/java/com/janetfilter/core/plugin/PluginEntry.java @@ -1,12 +1,11 @@ package com.janetfilter.core.plugin; import com.janetfilter.core.Environment; -import com.janetfilter.core.models.FilterRule; import java.util.List; public interface PluginEntry { - default void init(Environment environment, List filterRules) { + default void init(Environment environment, PluginConfig config) { // get plugin config } diff --git a/src/main/java/com/janetfilter/core/plugin/PluginManager.java b/src/main/java/com/janetfilter/core/plugin/PluginManager.java index 2fd8dc5..803edef 100644 --- a/src/main/java/com/janetfilter/core/plugin/PluginManager.java +++ b/src/main/java/com/janetfilter/core/plugin/PluginManager.java @@ -2,85 +2,99 @@ package com.janetfilter.core.plugin; import com.janetfilter.core.Dispatcher; import com.janetfilter.core.Environment; +import com.janetfilter.core.commons.ConfigParser; import com.janetfilter.core.commons.DebugInfo; -import com.janetfilter.core.models.FilterConfig; import com.janetfilter.core.utils.StringUtils; import java.io.File; import java.lang.instrument.Instrumentation; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.jar.JarFile; import java.util.jar.Manifest; public final class PluginManager { private static final String ENTRY_NAME = "JANF-Plugin-Entry"; + private final Instrumentation inst; private final Dispatcher dispatcher; private final Environment environment; - public PluginManager(Dispatcher dispatcher, Environment environment) { + public PluginManager(Instrumentation inst, Dispatcher dispatcher, Environment environment) { + this.inst = inst; this.dispatcher = dispatcher; this.environment = environment; } - public void loadPlugins(Instrumentation inst) { - for (Class klass : getAllPluginClasses(inst)) { - try { - addPluginEntry(klass); - } catch (Throwable e) { - DebugInfo.output("Init plugin failed: " + e.getMessage()); - } + public void loadPlugins() { + File pluginsDirectory = environment.getPluginsDir(); + if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { + return; } - } - private List> getAllPluginClasses(Instrumentation inst) { - List> classes = new ArrayList<>(); + File[] pluginFiles = pluginsDirectory.listFiles((d, n) -> n.endsWith(".jar")); + if (null == pluginFiles) { + return; + } - do { - File pluginsDirectory = environment.getPluginsDir(); - if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { - break; + try { + ExecutorService executorService = Executors.newCachedThreadPool(); + for (File pluginFile : pluginFiles) { + executorService.submit(new PluginLoadTask(pluginFile)); } - File[] pluginFiles = pluginsDirectory.listFiles((d, n) -> n.endsWith(".jar")); - if (null == pluginFiles) { - break; + executorService.shutdown(); + if (!executorService.awaitTermination(30L, TimeUnit.SECONDS)) { + throw new RuntimeException("Load plugin timeout"); } - for (File pluginFile : pluginFiles) { - try { - JarFile jarFile = new JarFile(pluginFile); - Manifest manifest = jarFile.getManifest(); - String entryClass = manifest.getMainAttributes().getValue(ENTRY_NAME); - if (StringUtils.isEmpty(entryClass)) { - continue; - } - - PluginClassLoader classLoader = new PluginClassLoader(jarFile); - Class klass = Class.forName(entryClass, false, classLoader); - if (!Arrays.asList(klass.getInterfaces()).contains(PluginEntry.class)) { - continue; - } + DebugInfo.output("============ All plugins loaded ============"); + } catch (Throwable e) { + DebugInfo.output("Load plugin failed: " + e.getMessage()); + } + } + + private class PluginLoadTask implements Runnable { + private final File pluginFile; + + public PluginLoadTask(File pluginFile) { + this.pluginFile = pluginFile; + } + + @Override + public void run() { + try { + JarFile jarFile = new JarFile(pluginFile); + Manifest manifest = jarFile.getManifest(); + String entryClass = manifest.getMainAttributes().getValue(ENTRY_NAME); + if (StringUtils.isEmpty(entryClass)) { + return; + } + + PluginClassLoader classLoader = new PluginClassLoader(jarFile); + Class klass = Class.forName(entryClass, false, classLoader); + if (!Arrays.asList(klass.getInterfaces()).contains(PluginEntry.class)) { + return; + } + synchronized (inst) { inst.appendToBootstrapClassLoaderSearch(jarFile); - classes.add((Class) Class.forName(entryClass)); - } catch (Throwable e) { - DebugInfo.output("Load plugin failed: " + e.getMessage()); } - } - } while (false); - return classes; - } + PluginEntry pluginEntry = (PluginEntry) Class.forName(entryClass).newInstance(); - private void addPluginEntry(Class entryClass) throws Exception { - PluginEntry pluginEntry = entryClass.newInstance(); + File configFile = new File(environment.getConfigDir(), pluginEntry.getName().toLowerCase() + ".conf"); + PluginConfig pluginConfig = new PluginConfig(configFile, ConfigParser.parse(configFile)); + pluginEntry.init(environment, pluginConfig); - pluginEntry.init(environment, FilterConfig.getBySection(pluginEntry.getName())); - dispatcher.addTransformers(pluginEntry.getTransformers()); + dispatcher.addTransformers(pluginEntry.getTransformers()); - DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}"); + DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}"); + } catch (Throwable e) { + DebugInfo.output("Parse plugin info failed: " + e.getMessage()); + } + } } } diff --git a/src/main/resources/b7e909d6ba41ae03fb85af5b8ba702709f5798cf.txt b/src/main/resources/5a1666cf298cd1d4fa64d62d123af55f5f39024f.txt similarity index 100% rename from src/main/resources/b7e909d6ba41ae03fb85af5b8ba702709f5798cf.txt rename to src/main/resources/5a1666cf298cd1d4fa64d62d123af55f5f39024f.txt