mirror of
https://gitee.com/ja-netfilter/ja-netfilter.git
synced 2024-12-22 08:59:25 +08:00
v2.0.0
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:
parent
881c5d2522
commit
7ba3c94e53
18
README.md
18
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 (<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
|
||||
|
2
pom.xml
2
pom.xml
@ -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>
|
||||
|
@ -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<>());
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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<>();
|
||||
}
|
||||
}
|
@ -55,9 +55,6 @@ public class FilterRule {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"type=" + type +
|
||||
", rule='" + rule + '\'' +
|
||||
'}';
|
||||
return "{type=" + type + ", rule=" + rule + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
src/main/java/com/janetfilter/core/plugin/PluginConfig.java
Normal file
30
src/main/java/com/janetfilter/core/plugin/PluginConfig.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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() + "}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user