1. change the location of config file
  1. split config file
  2. use multiple threads to load plugins

Signed-off-by: pengzhile <pengzhile@gmail.com>
This commit is contained in:
pengzhile 2021-12-27 14:16:35 +08:00
parent 881c5d2522
commit 7ba3c94e53
15 changed files with 121 additions and 235 deletions

View File

@ -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 (<font color=green>**PREFERRED!**</font>)
* 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

View File

@ -6,7 +6,7 @@
<groupId>com.ja-netfilter</groupId>
<artifactId>ja-netfilter</artifactId>
<version>1.2.0</version>
<version>2.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -12,7 +12,7 @@ public final class Dispatcher implements ClassFileTransformer {
private final Set<String> classSet = new TreeSet<>();
private final Map<String, List<MyTransformer>> transformerMap = new HashMap<>();
public void addTransformer(MyTransformer transformer) {
public synchronized void addTransformer(MyTransformer transformer) {
String className = transformer.getHookClassName();
classSet.add(className.replace('/', '.'));
List<MyTransformer> transformers = transformerMap.computeIfAbsent(className, k -> new ArrayList<>());

View File

@ -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;
}

View File

@ -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);

View File

@ -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.");

View File

@ -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;
}
}

View File

@ -17,7 +17,7 @@ public class ConfigParser {
public static Map<String, List<FilterRule>> parse(File file) throws Exception {
Map<String, List<FilterRule>> 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;
}
}

View File

@ -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();
}
}

View File

@ -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<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

@ -55,9 +55,6 @@ public class FilterRule {
@Override
public String toString() {
return "{" +
"type=" + type +
", rule='" + rule + '\'' +
'}';
return "{type=" + type + ", rule=" + rule + "}";
}
}
}

View File

@ -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<String, List<FilterRule>> data;
public PluginConfig(File file, Map<String, List<FilterRule>> data) {
this.file = file;
this.data = data;
}
public List<FilterRule> getBySection(String section) {
return data.getOrDefault(section, new ArrayList<>());
}
public File getFile() {
return file;
}
public Map<String, List<FilterRule>> getData() {
return data;
}
}

View File

@ -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<FilterRule> filterRules) {
default void init(Environment environment, PluginConfig config) {
// get plugin config
}

View File

@ -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<? extends PluginEntry> 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;
}
File[] pluginFiles = pluginsDirectory.listFiles((d, n) -> n.endsWith(".jar"));
if (null == pluginFiles) {
return;
}
try {
ExecutorService executorService = Executors.newCachedThreadPool();
for (File pluginFile : pluginFiles) {
executorService.submit(new PluginLoadTask(pluginFile));
}
executorService.shutdown();
if (!executorService.awaitTermination(30L, TimeUnit.SECONDS)) {
throw new RuntimeException("Load plugin timeout");
}
DebugInfo.output("============ All plugins loaded ============");
} catch (Throwable e) {
DebugInfo.output("Load plugin failed: " + e.getMessage());
}
}
private List<Class<? extends PluginEntry>> getAllPluginClasses(Instrumentation inst) {
List<Class<? extends PluginEntry>> classes = new ArrayList<>();
private class PluginLoadTask implements Runnable {
private final File pluginFile;
do {
File pluginsDirectory = environment.getPluginsDir();
if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
break;
}
public PluginLoadTask(File pluginFile) {
this.pluginFile = pluginFile;
}
File[] pluginFiles = pluginsDirectory.listFiles((d, n) -> n.endsWith(".jar"));
if (null == pluginFiles) {
break;
}
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;
}
inst.appendToBootstrapClassLoaderSearch(jarFile);
classes.add((Class<? extends PluginEntry>) Class.forName(entryClass));
} catch (Throwable e) {
DebugInfo.output("Load plugin failed: " + e.getMessage());
@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);
}
PluginEntry pluginEntry = (PluginEntry) Class.forName(entryClass).newInstance();
File configFile = new File(environment.getConfigDir(), pluginEntry.getName().toLowerCase() + ".conf");
PluginConfig pluginConfig = new PluginConfig(configFile, ConfigParser.parse(configFile));
pluginEntry.init(environment, pluginConfig);
dispatcher.addTransformers(pluginEntry.getTransformers());
DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}");
} catch (Throwable e) {
DebugInfo.output("Parse plugin info failed: " + e.getMessage());
}
} while (false);
return classes;
}
private void addPluginEntry(Class<? extends PluginEntry> entryClass) throws Exception {
PluginEntry pluginEntry = entryClass.newInstance();
pluginEntry.init(environment, FilterConfig.getBySection(pluginEntry.getName()));
dispatcher.addTransformers(pluginEntry.getTransformers());
DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}");
}
}
}