double check telnet port and pid before attach agent (#1119)

pull/1413/head
gongdewei 5 years ago committed by GitHub
parent 1dd7f6c9bd
commit 0d84baaf05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -475,6 +475,34 @@ find_listen_port_process()
fi
}
# Status from com.taobao.arthas.client.TelnetConsole
# Execute commands timeout
STATUS_EXEC_TIMEOUT=100
# Execute commands error
STATUS_EXEC_ERROR=101
# find the process pid of target telnet port
# maybe another user start an arthas server at the same port, but invisible for current user
find_listen_port_process_by_client()
{
local arthas_lib_dir="${ARTHAS_HOME}"
# http://www.inonit.com/cygwin/faq/
if [ "${OS_TYPE}" = "Cygwin" ]; then
arthas_lib_dir=`cygpath -wp "$arthas_lib_dir"`
fi
"${JAVA_HOME}/bin/java" ${ARTHAS_OPTS} ${JVM_OPTS} \
-jar "${arthas_lib_dir}/arthas-client.jar" \
${TARGET_IP} \
${TELNET_PORT} \
-c "session" \
--execution-timeout 2000 \
2>&1
# return java process exit status code !
return $?
}
parse_arguments()
{
POSITIONAL=()
@ -699,9 +727,7 @@ parse_arguments()
# check the process already using telnet port if equals to target pid
if [[ ($telnetPortPid) && ($TARGET_PID != $telnetPortPid) ]]; then
echo "[ERROR] Target process $TARGET_PID is not the process using port $TELNET_PORT, you will connect to an unexpected process."
echo "[ERROR] 1. Try to restart as.sh, select process $telnetPortPid, shutdown it first with running the 'stop' command."
echo "[ERROR] 2. Try to use different telnet port, for example: as.sh --telnet-port 9998 --http-port -1"
print_telnet_port_pid_error
exit 1
fi
if [[ ($httpPortPid) && ($TARGET_PID != $httpPortPid) ]]; then
@ -798,6 +824,52 @@ sanity_check() {
fi
}
port_pid_check() {
if [[ $TELNET_PORT > 0 ]]; then
local telnet_output
local find_process_status
# declare local var before var=$()
telnet_output=$(find_listen_port_process_by_client)
find_process_status=$?
#echo "find_process_status: $find_process_status"
#echo "telnet_output: $telnet_output"
#check return code
if [[ $find_process_status -eq $STATUS_EXEC_TIMEOUT ]]; then
print_telnet_port_used_error "detection timeout"
exit 1
elif [[ $find_process_status -eq $STATUS_EXEC_ERROR ]]; then
print_telnet_port_used_error "detection error"
exit 1
fi
if [[ -n $telnet_output ]]; then
# check JAVA_PID
telnetPortPid=$(echo "$telnet_output" | grep JAVA_PID | awk '{ print $2 }')
#echo "telnetPortPid: $telnetPortPid"
# check the process already using telnet port if equals to target pid
if [[ -n $telnetPortPid && ($TARGET_PID != $telnetPortPid) ]]; then
print_telnet_port_pid_error
exit 1
fi
fi
fi
}
print_telnet_port_pid_error() {
echo "[ERROR] The telnet port $TELNET_PORT is used by process $telnetPortPid instead of target process $TARGET_PID, you will connect to an unexpected process."
echo "[ERROR] 1. Try to restart as.sh, select process $telnetPortPid, shutdown it first with running the 'stop' command."
echo "[ERROR] 2. Try to stop the existing arthas instance: java -jar arthas-client.jar 127.0.0.1 $TELNET_PORT -c \"stop\""
echo "[ERROR] 3. Try to use different telnet port, for example: as.sh --telnet-port 9998 --http-port -1"
}
print_telnet_port_used_error() {
local error_msg=$1
echo "[ERROR] The telnet port $TELNET_PORT is used, but process $error_msg, you will connect to an unexpected process."
echo "[ERROR] Try to use different telnet port, for example: as.sh --telnet-port 9998 --http-port -1"
}
# active console
# $1 : arthas_lib_dir
active_console()
@ -899,6 +971,8 @@ main()
sanity_check
port_pid_check
echo "Calculating attach execution time..."
time (attach_jvm "${ARTHAS_HOME}" || exit 1)
@ -915,4 +989,4 @@ main()
main "${@}"
main "${@}"

@ -1,5 +1,6 @@
package com.taobao.arthas.boot;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@ -11,6 +12,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@ -34,6 +36,9 @@ import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
import static com.taobao.arthas.boot.ProcessUtils.STATUS_EXEC_ERROR;
import static com.taobao.arthas.boot.ProcessUtils.STATUS_EXEC_TIMEOUT;
/**
* @author hengyunabc 2018-10-26
*
@ -353,14 +358,7 @@ public class Bootstrap {
}
}
if (telnetPortPid > 0 && pid != telnetPortPid) {
AnsiLog.error("Target process {} is not the process using port {}, you will connect to an unexpected process.",
pid, bootstrap.getTelnetPort());
AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
telnetPortPid);
AnsiLog.error("2. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
System.exit(1);
}
checkTelnetPortPid(bootstrap, telnetPortPid, pid);
if (httpPortPid > 0 && pid != httpPortPid) {
AnsiLog.error("Target process {} is not the process using port {}, you will connect to an unexpected process.",
@ -474,6 +472,10 @@ public class Bootstrap {
if (telnetPortPid > 0 && pid == telnetPortPid) {
AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPort());
} else {
//double check telnet port and pid before attach
telnetPortPid = findProcessByTelnetClient(arthasHomeDir.getAbsolutePath(), bootstrap.getTelnetPort());
checkTelnetPortPid(bootstrap, telnetPortPid, pid);
// start arthas-core.jar
List<String> attachArgs = new ArrayList<String>();
attachArgs.add("-jar");
@ -556,6 +558,78 @@ public class Bootstrap {
mainMethod.invoke(null, new Object[] { telnetArgs.toArray(new String[0]) });
}
private static void checkTelnetPortPid(Bootstrap bootstrap, long telnetPortPid, long targetPid) {
if (telnetPortPid > 0 && targetPid != telnetPortPid) {
AnsiLog.error("The telnet port {} is used by process {} instead of target process {}, you will connect to an unexpected process.",
bootstrap.getTelnetPort(), telnetPortPid, targetPid);
AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
telnetPortPid);
AnsiLog.error("2. Or try to stop the existing arthas instance: java -jar arthas-client.jar 127.0.0.1 {} -c \"stop\"", bootstrap.getTelnetPort());
AnsiLog.error("3. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
System.exit(1);
}
}
private static long findProcessByTelnetClient(String arthasHomeDir, int telnetPort) {
// start java telnet client
List<String> telnetArgs = new ArrayList<String>();
telnetArgs.add("-c");
telnetArgs.add("session");
telnetArgs.add("--execution-timeout");
telnetArgs.add("2000");
// telnet port ,ip
telnetArgs.add("127.0.0.1");
telnetArgs.add("" + telnetPort);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
String error = null;
int status = ProcessUtils.startArthasClient(arthasHomeDir, telnetArgs, out);
if (status == STATUS_EXEC_TIMEOUT) {
error = "detection timeout";
} else if (status == STATUS_EXEC_ERROR) {
error = "detection error";
AnsiLog.error("process status: {}", status);
AnsiLog.error("process output: {}", out.toString());
} else {
// ignore connect error
}
if (error != null) {
AnsiLog.error("The telnet port {} is used, but process {}, you will connect to an unexpected process.", telnetPort, error);
AnsiLog.error("Try to use a different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
System.exit(1);
}
//parse output, find java pid
String output = out.toString("UTF-8");
String javaPidLine = null;
Scanner scanner = new Scanner(output);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.contains("JAVA_PID")) {
javaPidLine = line;
break;
}
}
if (javaPidLine != null) {
// JAVA_PID 10473
try {
String[] strs = javaPidLine.split("JAVA_PID");
if (strs.length > 1) {
return Long.parseLong(strs[strs.length - 1].trim());
}
} catch (NumberFormatException e) {
// ignore
}
}
} catch (Throwable ex) {
AnsiLog.error("Detection telnet port error");
AnsiLog.error(ex);
}
return -1;
}
private static String listVersions(String mavenMetaData) {
StringBuilder result = new StringBuilder(1024);
List<String> versionList = listNames(ARTHAS_LIB_DIR);

@ -3,6 +3,11 @@ package com.taobao.arthas.boot;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -28,6 +33,24 @@ import com.taobao.arthas.common.PidUtils;
public class ProcessUtils {
private static String FOUND_JAVA_HOME = null;
//status code from com.taobao.arthas.client.TelnetConsole
/**
* Process success
*/
public static final int STATUS_OK = 0;
/**
* Generic error
*/
public static final int STATUS_ERROR = 1;
/**
* Execute commands timeout
*/
public static final int STATUS_EXEC_TIMEOUT = 100;
/**
* Execute commands error
*/
public static final int STATUS_EXEC_ERROR = 101;
@SuppressWarnings("resource")
public static long select(boolean v, long telnetPortPid, String select) throws InputMismatchException {
Map<Long, String> processMap = listProcessByJps(v);
@ -290,7 +313,52 @@ public class ProcessUtils {
} catch (Throwable e) {
// ignore
}
}
public static int startArthasClient(String arthasHomeDir, List<String> telnetArgs, OutputStream out) throws Throwable {
// start java telnet client
// find arthas-client.jar
URLClassLoader classLoader = new URLClassLoader(
new URL[]{new File(arthasHomeDir, "arthas-client.jar").toURI().toURL()});
Class<?> telnetConsoleClas = classLoader.loadClass("com.taobao.arthas.client.TelnetConsole");
Method processMethod = telnetConsoleClas.getMethod("process", String[].class);
//redirect System.out/System.err
PrintStream originSysOut = System.out;
PrintStream originSysErr = System.err;
PrintStream newOut = new PrintStream(out);
PrintStream newErr = new PrintStream(out);
// call TelnetConsole.process()
// fix https://github.com/alibaba/arthas/issues/833
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try {
System.setOut(newOut);
System.setErr(newErr);
Thread.currentThread().setContextClassLoader(classLoader);
return (Integer) processMethod.invoke(null, new Object[]{telnetArgs.toArray(new String[0])});
} catch (Throwable e) {
//java.lang.reflect.InvocationTargetException : java.net.ConnectException
e = e.getCause();
if (e instanceof IOException || e instanceof InterruptedException) {
// ignore connection error and interrupted error
return STATUS_ERROR;
} else {
// process error
AnsiLog.error("process error: {}", e.toString());
AnsiLog.error(e);
return STATUS_EXEC_ERROR;
}
} finally {
Thread.currentThread().setContextClassLoader(tccl);
//reset System.out/System.err
System.setOut(originSysOut);
System.setErr(originSysErr);
//flush output
newOut.flush();
newErr.flush();
}
}
private static File findJava() {

@ -53,7 +53,6 @@ public final class IOUtil {
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
};

@ -14,6 +14,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.commons.net.telnet.InvalidTelnetOptionException;
import org.apache.commons.net.telnet.TelnetClient;
@ -53,6 +54,25 @@ public class TelnetConsole {
private static final byte CTRL_C = 0x03;
// ------- Status codes ------- //
/**
* Process success
*/
public static final int STATUS_OK = 0;
/**
* Generic error
*/
public static final int STATUS_ERROR = 1;
/**
* Execute commands timeout
*/
public static final int STATUS_EXEC_TIMEOUT = 100;
/**
* Execute commands error
*/
public static final int STATUS_EXEC_ERROR = 101;
private boolean help = false;
private String targetIp = "127.0.0.1";
@ -60,6 +80,7 @@ public class TelnetConsole {
private String command;
private String batchFile;
private int executionTimeout = -1;
private Integer width = null;
private Integer height = null;
@ -94,6 +115,12 @@ public class TelnetConsole {
this.batchFile = batchFile;
}
@Option(shortName = "t", longName = "execution-timeout")
@Description("The timeout (ms) of execute commands or batch file ")
public void setExecutionTimeout(int executionTimeout) {
this.executionTimeout = executionTimeout;
}
@Option(shortName = "w", longName = "width")
@Description("The terminal width")
public void setWidth(int width) {
@ -133,147 +160,190 @@ public class TelnetConsole {
return list;
}
public static void main(String[] args) throws IOException {
public static void main(String[] args) throws Exception {
try {
int status = process(args, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(STATUS_OK);
}
});
System.exit(status);
} catch (Throwable e) {
e.printStackTrace();
CLI cli = CLIConfigurator.define(TelnetConsole.class);
System.out.println(usage(cli));
System.exit(STATUS_ERROR);
}
}
/**
* arthas-boot使
*
* @param args
* @return status code
* @throws IOException
* @throws InterruptedException
*/
public static int process(String[] args) throws IOException, InterruptedException {
return process(args, null);
}
/**
* arthas client
* process()arthas-boot使System.exit()
*
* @param telnetConsole
* @param cli
* @param args
* @param eotEventCallback Ctrl+D signals an End of Transmission (EOT) event
* @return status code
* @throws IOException
* @throws InterruptedException
*/
public static int process(String[] args, ActionListener eotEventCallback) throws IOException, InterruptedException {
// support mingw/cygw jline color
if (OSUtils.isCygwinOrMinGW()) {
System.setProperty("jline.terminal", System.getProperty("jline.terminal", "jline.UnixTerminal"));
}
TelnetConsole telnetConsole = new TelnetConsole();
CLI cli = CLIConfigurator.define(TelnetConsole.class);
try {
CommandLine commandLine = cli.parse(Arrays.asList(args));
CommandLine commandLine = cli.parse(Arrays.asList(args));
CLIConfigurator.inject(commandLine, telnetConsole);
CLIConfigurator.inject(commandLine, telnetConsole);
if (telnetConsole.isHelp()) {
System.out.println(usage(cli));
System.exit(0);
}
if (telnetConsole.isHelp()) {
System.out.println(usage(cli));
return STATUS_ERROR;
}
// Try to read cmds
List<String> cmds = new ArrayList<String>();
if (telnetConsole.getCommand() != null) {
for (String c : telnetConsole.getCommand().split(";")) {
cmds.add(c.trim());
}
} else if (telnetConsole.getBatchFile() != null) {
File file = new File(telnetConsole.getBatchFile());
if (!file.exists()) {
throw new IllegalArgumentException("batch file do not exist: " + telnetConsole.getBatchFile());
} else {
cmds.addAll(readLines(file));
}
// Try to read cmds
List<String> cmds = new ArrayList<String>();
if (telnetConsole.getCommand() != null) {
for (String c : telnetConsole.getCommand().split(";")) {
cmds.add(c.trim());
}
} else if (telnetConsole.getBatchFile() != null) {
File file = new File(telnetConsole.getBatchFile());
if (!file.exists()) {
throw new IllegalArgumentException("batch file do not exist: " + telnetConsole.getBatchFile());
} else {
cmds.addAll(readLines(file));
}
}
final ConsoleReader consoleReader = new ConsoleReader(System.in, System.out);
consoleReader.setHandleUserInterrupt(true);
Terminal terminal = consoleReader.getTerminal();
final ConsoleReader consoleReader = new ConsoleReader(System.in, System.out);
consoleReader.setHandleUserInterrupt(true);
Terminal terminal = consoleReader.getTerminal();
if (terminal instanceof TerminalSupport) {
((TerminalSupport) terminal).disableInterruptCharacter();
}
if (terminal instanceof TerminalSupport) {
((TerminalSupport) terminal).disableInterruptCharacter();
}
// support catch ctrl+c event
terminal.disableInterruptCharacter();
if (terminal instanceof UnixTerminal) {
((UnixTerminal) terminal).disableLitteralNextCharacter();
}
// support catch ctrl+c event
terminal.disableInterruptCharacter();
if (terminal instanceof UnixTerminal) {
((UnixTerminal) terminal).disableLitteralNextCharacter();
}
int width = TerminalSupport.DEFAULT_WIDTH;
int height = TerminalSupport.DEFAULT_HEIGHT;
int width = TerminalSupport.DEFAULT_WIDTH;
int height = TerminalSupport.DEFAULT_HEIGHT;
if (!cmds.isEmpty()) {
// batch mode
if (telnetConsole.getWidth() != null) {
width = telnetConsole.getWidth();
}
if (telnetConsole.getheight() != null) {
height = telnetConsole.getheight();
}
if (!cmds.isEmpty()) {
// batch mode
if (telnetConsole.getWidth() != null) {
width = telnetConsole.getWidth();
}
if (telnetConsole.getheight() != null) {
height = telnetConsole.getheight();
}
} else {
// normal telnet client, get current terminal size
if (telnetConsole.getWidth() != null) {
width = telnetConsole.getWidth();
} else {
// normal telnet client, get current terminal size
if (telnetConsole.getWidth() != null) {
width = telnetConsole.getWidth();
} else {
width = terminal.getWidth();
// hack for windows dos
if (OSUtils.isWindows()) {
width--;
}
}
if (telnetConsole.getheight() != null) {
height = telnetConsole.getheight();
} else {
height = terminal.getHeight();
width = terminal.getWidth();
// hack for windows dos
if (OSUtils.isWindows()) {
width--;
}
}
if (telnetConsole.getheight() != null) {
height = telnetConsole.getheight();
} else {
height = terminal.getHeight();
}
}
final TelnetClient telnet = new TelnetClient();
telnet.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
final TelnetClient telnet = new TelnetClient();
telnet.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
// send init terminal size
TelnetOptionHandler sizeOpt = new WindowSizeOptionHandler(width, height, true, true, false, false);
try {
telnet.addOptionHandler(sizeOpt);
} catch (InvalidTelnetOptionException e) {
// ignore
}
// send init terminal size
TelnetOptionHandler sizeOpt = new WindowSizeOptionHandler(width, height, true, true, false, false);
try {
telnet.addOptionHandler(sizeOpt);
} catch (InvalidTelnetOptionException e) {
// ignore
}
// ctrl + c event callback
consoleReader.getKeys().bind(new Character((char) CTRL_C).toString(), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
consoleReader.getCursorBuffer().clear(); // clear current line
telnet.getOutputStream().write(CTRL_C);
telnet.getOutputStream().flush();
} catch (Exception e1) {
e1.printStackTrace();
}
// ctrl + c event callback
consoleReader.getKeys().bind(new Character((char) CTRL_C).toString(), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
consoleReader.getCursorBuffer().clear(); // clear current line
telnet.getOutputStream().write(CTRL_C);
telnet.getOutputStream().flush();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
});
// ctrl + d event call back
consoleReader.getKeys().bind(new Character(KeyMap.CTRL_D).toString(), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
// ctrl + d event call back
consoleReader.getKeys().bind(new Character(KeyMap.CTRL_D).toString(), eotEventCallback);
try {
telnet.connect(telnetConsole.getTargetIp(), telnetConsole.getPort());
} catch (IOException e) {
System.out.println("Connect to telnet server error: " + telnetConsole.getTargetIp() + " "
+ telnetConsole.getPort());
throw e;
}
try {
telnet.connect(telnetConsole.getTargetIp(), telnetConsole.getPort());
} catch (IOException e) {
System.out.println("Connect to telnet server error: " + telnetConsole.getTargetIp() + " "
+ telnetConsole.getPort());
throw e;
}
if (cmds.isEmpty()) {
IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in,
consoleReader.getOutput());
} else {
batchModeRun(telnet, cmds);
telnet.disconnect();
if (cmds.isEmpty()) {
IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in,
consoleReader.getOutput());
} else {
try {
return batchModeRun(telnet, cmds, telnetConsole.getExecutionTimeout());
} catch (Throwable e) {
System.out.println("Execute commands error: " + e.getMessage());
e.printStackTrace();
return STATUS_EXEC_ERROR;
} finally {
try {
telnet.disconnect();
} catch (IOException e) {
//ignore ex
}
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(usage(cli));
System.exit(1);
}
return STATUS_OK;
}
private static void batchModeRun(TelnetClient telnet, List<String> commands)
private static int batchModeRun(TelnetClient telnet, List<String> commands, final int executionTimeout)
throws IOException, InterruptedException {
if (commands.size() == 0) {
return;
return STATUS_OK;
}
long startTime = System.currentTimeMillis();
final InputStream inputStream = telnet.getInputStream();
final OutputStream outputStream = telnet.getOutputStream();
@ -305,14 +375,22 @@ public class TelnetConsole {
}
}
});
printResultThread.start();
// send commands to arthas server
for (String command : commands) {
if (command.trim().isEmpty()) {
continue;
}
receviedPromptQueue.take();
// try poll prompt and check timeout
while (receviedPromptQueue.poll(100, TimeUnit.MILLISECONDS) == null) {
if (executionTimeout > 0) {
long now = System.currentTimeMillis();
if (now - startTime > executionTimeout) {
return STATUS_EXEC_TIMEOUT;
}
}
}
// send command to server
outputStream.write((command + " | plaintext\n").getBytes());
outputStream.flush();
@ -323,6 +401,8 @@ public class TelnetConsole {
outputStream.write("quit\n".getBytes());
outputStream.flush();
System.out.println();
return STATUS_OK;
}
private static String usage(CLI cli) {
@ -349,6 +429,10 @@ public class TelnetConsole {
return batchFile;
}
public int getExecutionTimeout() {
return executionTimeout;
}
public Integer getWidth() {
return width;
}

Loading…
Cancel
Save