1. 程式人生 > >防火牆內JVisualVM連線jstatd解決方案

防火牆內JVisualVM連線jstatd解決方案

 

 

jstatd啟動後會開啟兩個埠,其中一個埠可通過引數“-p”指定,如果不指定預設為1099,另一個是一個隨機埠,不能引數指定:

# netstat -lpnt|grep jstatd

tcp        0      0 0.0.0.0:47260           0.0.0.0:*               LISTEN      4998/jstatd         

tcp        0      0 0.0.0.0:1099            0.0.0.0:*               LISTEN      4998/jstatd 

 

47260是一個隨機埠,不方便穿透防火牆。這導致了一個問題,有防火牆時,JVisualVM將無法和jstatd正常通訊。看到的現象將如下:

較詳細: RMI TCP Connection(4063)-192.168.1.31: [192.168.1.31: sun.rmi.registry.RegistryImpl[0:0:0, 0]: java.rmi.Remote lookup(java.lang.String)]

十二月 05, 2018 7:15:45 上午 sun.rmi.server.UnicastServerRef logCall

較詳細: RMI TCP Connection(4064)-192.168.1.31: [192.168.1.31: sun.rmi.registry.RegistryImpl[0:0:0, 0]: java.rmi.Remote 

 

啟動引數:

jstatd -J-Djava.security.policy=/usr/local/jdk/bin/jstatd.policy -J-Djava.rmi.server.hostname=192.168.1.31 -p 1099

 

而正常的應當如下:

Dec 04, 2018 7:19:30 PM sun.rmi.server.UnicastServerRef logCall

FINER: RMI TCP Connection(5)-192.168.1.37: [192.168.1.37: sun.tools.jstatd.RemoteVmImpl[-220b68dc:16778f1bf45:-7ff2, -324702369529557764]: public abstract byte[] sun.jvmstat.monitor.remote.RemoteVm.getBytes() throws java.rmi.RemoteException]

 

原因正是JVisualVM和jstatd的隨機埠47260不通。

 

解決方案一:gdb修改監聽埠號

操作步驟:

# gdb /usr/local/jdk/bin/jstatd

(gdb) set args -J-Djava.security.policy=/usr/local/jdk/bin/jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.31.98 -J-Djava.net.preferIPv4Stack=true -J-Djava.rmi.server.logCalls=true -p 8080

(gdb) b bind

(gdb) r

(gdb) bt

#0  0x00007ffff74da040 in bind () from /lib64/libc.so.6

#1  0x00007fffd8eafc99 in Java_java_net_PlainSocketImpl_socketBind () from /usr/local/jdk1.8.0_121/jre/lib/amd64/libnet.so

#2  0x00007fffe1015834 in ?? ()

#3  0x00007ffff60eb260 in ?? ()

#4  0x00007fffe10155b9 in ?? ()

#5  0x00007ffff0008000 in ?? ()

#6  0x00007fffe1015582 in ?? ()

#7  0x00007ffff60eb220 in ?? ()

#8  0x00007fffdac2d250 in ?? ()

#9  0x00007ffff60eb290 in ?? ()

#10 0x00007fffdac33d28 in ?? ()

#11 0x0000000000000000 in ?? ()

(gdb) info reg

rax            0x0      0

rbx            0x7ffff00081f8   140737219953144

rcx            0x7ffff7374f60   140737340985184

rdx            0x10     16

rsi            0x7ffff60eb1a0   140737321546144

rdi            0x11     17

rbp            0x7ffff60eb1f0   0x7ffff60eb1f0

rsp            0x7ffff60eb188   0x7ffff60eb188

r8             0x7ffff0007730   140737219950384

r9             0x719d2f148      30498025800

r10            0x7ffff60ead50   140737321545040

r11            0x7ffff74da040   140737342447680

r12            0x7ffff60eb288   140737321546376

r13            0x0      0

r14            0x7ffff60eb290   140737321546384

r15            0x7ffff60eb1a0   140737321546144

rip            0x7ffff74da040   0x7ffff74da040 <bind>

eflags         0x206    [ PF IF ]

cs             0x33     51

ss             0x2b     43

ds             0x0      0

es             0x0      0

fs             0x0      0

gs             0x0      0

 

埠號是在呼叫系統函式bind時指定的,bind函式原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

也就是第二個引數,sockaddr的結構為:

struct sockaddr_in

{

    sa_family_t sin_family;

    in_port_t sin_port;

    struct in_addr sin_addr;

    unsigned char sin_zero[X];

};

typedef unsigned short int sa_family_t;

typedef uint16_t in_port_t;

 

暫存器rsi儲存了bind函式的第二個引數addr的地址0x7ffff60eb1a0(對應的十進位制值為140737321546144),對於IPv4,sin_family的值一般為AF_INET(2),可gdb確認(檢視addr的頭2個位元組值):

(gdb) p *(unsigned short*)0x7ffff60eb1a0

$10 = 2

 

結果符合預期,再檢視sin_port的值:

(gdb) p *(unsigned short*)(0x7ffff60eb1a0+2)

$11 = 0

 

值為0,表示隨機埠,這正是需要修改的地方,將它改成十進位制值443:

(gdb) set *(unsigned short*)(0x7ffff60eb1a0+2)=443

(gdb) p *(unsigned short*)(0x7ffff60eb1a0+2)      

$12 = 443

 

注意jstat即會繫結IPv4(AF_INET)地址,還會綁PF_NETLINK(16),而第一次bind時的埠正是隨機埠,因此只需要修改這一處。

傳遞給bind的埠號需為網路位元組序值,即大端值,所以不能簡單的修改為十進位制443,443的十六進位制值為0x01BB,這個為小端值,對應的大端值為0xBB01。

(gdb) set *(unsigned short*)(0x7ffff60eb1a0+2)=0xBB01

(gdb) d

(gdb) c

Continuing.

 

會遇到幾個SIGSEGV,均不用管,繼續執行即可進入正常工作狀態:

Program received signal SIGSEGV, Segmentation fault.

[Switching to Thread 0x7fffd9536700 (LWP 28085)]

 

可以看到jstatd工作在期望的埠上:

# netstat -lpnt|grep jstatd

tcp  0  0 0.0.0.0:443    0.0.0.0:*   LISTEN  28058/jstatd        

tcp  0  0 0.0.0.0:8080   0.0.0.0:*   LISTEN  28058/jstatd

 

至此JVisualVM已能夠正常連線jstatd了。可考慮使用gdb指令碼自動修改埠,這樣就可廣泛部署並且零門檻。

 

解決方案二:埠轉發方式

不需要懂gdb操作,在jstatd安裝反向代理,如rinetd或直接使用sshd或iptables做埠轉發也可以。在JVisualVM也安裝正向代理,如Proxifier等,資料路徑如下:

JVisualVM <-> Proxifier <-> rinetd <-> jstatd

網上搜索相關的資料即可。

 

解決方案三:使用增強型ejstatd

https://github.com/anthony-o/ejstatd

 

編譯需要訪問internet,執行“mvn package”編譯(一些環境可能需要配置maven的proxy才能訪問internet)。也可直接下載編譯好的ejstatd:

https://download.csdn.net/download/aquester/10829579