• 防火墙内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]

     

    原因正是JVisualVMjstatd的随机端口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,对于IPv4sin_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的端口号需为网络字节序值,即大端值,所以不能简单的修改为十进制443443的十六进制值为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或直接使用sshdiptables做端口转发也可以。在JVisualVM也安装正向代理,如Proxifier等,数据路径如下:

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

    网上搜索相关的资料即可。

     

    解决方案三:使用增强型ejstatd

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

     

    编译需要访问internet,执行“mvn package”编译(一些环境可能需要配置mavenproxy才能访问internet)。也可直接下载编译好的ejstatd

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

  • 相关阅读:
    Linux基础命令mv
    Linux基础命令cp
    闭包函数
    函数的嵌套
    函数对象
    global与nonlocal
    名称空间与作用域
    函数的参数(总结)
    函数的基本使用
    文件的操作之指针移动
  • 原文地址:https://www.cnblogs.com/aquester/p/10069949.html
Copyright © 2020-2023  润新知