/*
* Copyright 2017 ZhangJiupeng
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.agentx.server;
import cc.agentx.protocol.request.XRequestWrapper;
import cc.agentx.protocol.request.XRequestWrapperFactory;
import cc.agentx.server.cache.DnsCache;
import cc.agentx.util.tunnel.SocketTunnel;
import cc.agentx.wrapper.Wrapper;
import cc.agentx.wrapper.WrapperFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.Executors;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public class Configuration {
private static final Logger log = LoggerFactory.getLogger(Configuration.class);
private static final String BASE_PATH = System.getProperty("user.dir").replaceAll("\\\\", "/");
private static final String[] CONFIG_FILE_PATH = {
"/conf/server.json",
"/server.json",
"/conf/agentx.json",
"/agentx.json",
"/conf/config.json",
"/config.json"
};
public static Configuration INSTANCE;
public static GlobalTrafficShapingHandler TRAFFIC_HANDLER;
@Expose
private String host = "0.0.0.0";
@Expose
private int port = 9999;
@Expose
private int[] relayPort = {};
@Expose
private String protocol = "shadowsocks";
@Expose
private String encryption = "aes-256-cfb";
@Expose
private String password = "my_password";
@Expose
private String[] process = {"encrypt"};
@Expose
private int dnsCacheCapacity = 1000;
@Expose
private int writeLimit = 0;
@Expose
private int readLimit = 0;
private SocketTunnel[] relays;
private Configuration() {
}
@Override
public String toString() {
return "{\n" +
" \"host\": \"" + host + "\",\n" +
" \"port\": " + port + ",\n" +
" \"relayPort\": " + Arrays.toString(relayPort) + ",\n" +
" \"protocol\": \"" + protocol + "\",\n" +
" \"encryption\": \"" + encryption + "\",\n" +
" \"password\": \"" + password + "\",\n" +
" \"process\": " + Arrays.toString(process) + ",\n" +
" \"dnsCacheCapacity\": " + dnsCacheCapacity + ",\n" +
" \"writeLimit\": " + writeLimit + ",\n" +
" \"readLimit\": " + readLimit + "\n" +
"}";
}
@SuppressWarnings("Duplicates")
private static void load() throws Exception {
String json = "";
InputStream inputStream = null;
for (String path : CONFIG_FILE_PATH) {
inputStream = Configuration.class.getResourceAsStream(path);
if (inputStream != null) {
log.info("\tFound resource [{}]", path);
break;
}
log.debug("\tCould NOT find resource [{}]", path);
}
for (String path : CONFIG_FILE_PATH) {
File file = new File(BASE_PATH, path);
if (file.exists()) {
inputStream = new FileInputStream(file);
log.info("\tFound resource [{}]", file.getAbsolutePath());
break;
}
log.debug("\tCould NOT find resource [{}]", file.getAbsolutePath());
}
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
if (inputStream == null) {
log.warn("\tCould NOT find resource [{}]", "config.json");
File configFile = new File(BASE_PATH, "config.json");
if (configFile.createNewFile()) {
log.warn("\tCreate default [config.json] at [{}]", configFile.getPath());
BufferedWriter writer = new BufferedWriter(new FileWriter(configFile));
writer.write(json = gson.toJson(new Configuration()));
writer.close();
log.warn("\tPlease reboot this program after configuration.");
System.exit(0);
} else {
throw new RuntimeException("file not found (" + configFile.getPath() + ")");
}
} else {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
while (reader.ready()) {
json += reader.readLine().concat("\n");
}
reader.close();
}
Configuration.INSTANCE = gson.fromJson(json, Configuration.class);
log.debug(INSTANCE.toString());
}
private static void check() throws Exception {
if (!XRequestWrapperFactory.exists(INSTANCE.protocol)) {
throw new Exception("unknown protocol \"" + INSTANCE.protocol + "\"");
}
for (String processFunction : INSTANCE.process) {
if (!WrapperFactory.exists(INSTANCE, processFunction)) {
throw new Exception("unknown encryption \"" + INSTANCE.encryption + "\"" +
" or process function \"" + processFunction + "\"");
}
}
}
public static void startupRelays() {
if (INSTANCE.relays == null) {
int[] ports = INSTANCE.relayPort;
INSTANCE.relays = new SocketTunnel[ports.length];
for (int i = 0; i < ports.length; i++) {
INSTANCE.relays[i] = new SocketTunnel(new InetSocketAddress(ports[i]), new InetSocketAddress(INSTANCE.port));
}
}
for (SocketTunnel tunnel : INSTANCE.relays) {
try {
tunnel.startup();
log.info("\tRelay: [{}] -> [{}] started", tunnel.getSrcAddr(), tunnel.getDstAddr());
} catch (IOException e) {
log.error("\tRelay: [{}] -> [{}] start failed ({})", tunnel.getSrcAddr(), tunnel.getDstAddr(), e.getMessage());
log.warn("\tRolling back...");
shutdownRelays();
throw new RuntimeException("relays startup aborted");
}
}
}
public static void shutdownRelays() {
if (INSTANCE != null && INSTANCE.relays != null) {
for (SocketTunnel t : INSTANCE.relays) {
try {
t.shutdown();
log.info("\tRelay: [{}] -> [{}] stopped", t.getSrcAddr(), t.getDstAddr());
} catch (IOException ignored) {
}
}
}
}
public static void init() throws Exception {
if (INSTANCE != null) {
return;
}
log.info("\tLoading configuration file...");
load();
log.info("\tChecking configuration items...");
check();
if (INSTANCE.relayPort.length > 0) {
log.info("\tStarting Relays...");
startupRelays();
}
log.info("\tInitializing dns cache...");
DnsCache.init(INSTANCE.dnsCacheCapacity);
log.info("\tInitializing global network traffic handler...");
TRAFFIC_HANDLER = new GlobalTrafficShapingHandler(Executors.newScheduledThreadPool(1), 1000);
TRAFFIC_HANDLER.setWriteLimit(INSTANCE.writeLimit);
TRAFFIC_HANDLER.setReadLimit(INSTANCE.readLimit);
log.info("\tEnd of configuration");
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getProtocol() {
return protocol;
}
public String getEncryption() {
return encryption;
}
public String getPassword() {
return password;
}
public int getDnsCacheCapacity() {
return dnsCacheCapacity;
}
public int getWriteLimit() {
return writeLimit;
}
public int getReadLimit() {
return readLimit;
}
public Wrapper getWrapper() {
Wrapper[] wrappers = new Wrapper[process.length];
for (int i = 0; i < process.length; i++) {
try {
wrappers[i] = WrapperFactory.getInstance(this, process[i]);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
return WrapperFactory.getInstance(wrappers);
}
public XRequestWrapper getXRequestWrapper() {
try {
return XRequestWrapperFactory.getInstance(protocol);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}