Java 在不更改代码的情况下设置本地 ip 地址
Java set local ip address without changing code
我有一台服务器,我们将向其添加一个额外的 IP 地址。服务器是OpenVZ虚拟化平台上的CentOSVPS运行
我想做的是在不影响现有Java程序的情况下引入新地址,所有现有程序应继续使用主地址。
我意识到 Socket() 构造函数有一个变体,其中可以指定使用的 IP 地址 - 这不是一个理想的解决方案,因为它涉及更改相当多的代码 - 其中大部分是库代码根本不期望多个 IP,尤其是在执行客户端请求时。
对于新程序,我打算在Socket构造函数中显式指定次IP,但我希望现有程序直接使用"default" IP而不修改它们。
有没有办法在我的 JVM 配置或 OS 配置中执行此操作。我也有主机节点的管理员权限,如果有帮助的话。
编辑
这是已接受答案的一个实现。它可以应用于大多数进程,并且不限于 java 程序。代码是用 C 写的,但不需要了解 C 就可以使用它。说明在评论里。
说明here
/*
Copyright (C) 2000 Daniel Ryde
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
/*
LD_PRELOAD library to make bind and connect to use a virtual
IP address as localaddress. Specified via the enviroment
variable BIND_ADDR.
Compile on Linux with:
gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE
Example in bash to make inetd only listen to the localhost
lo interface, thus disabling remote connections and only
enable to/from localhost:
BIND_ADDR="127.0.0.1" LD_PRELOAD=./bind.so /sbin/inetd
Example in bash to use your virtual IP as your outgoing
sourceaddress for ircII:
BIND_ADDR="your-virt-ip" LD_PRELOAD=./bind.so ircII
Note that you have to set up your servers virtual IP first.
This program was made by Daniel Ryde
email: daniel@ryde.net
web: http://www.ryde.net/
TODO: I would like to extend it to the accept calls too, like a
general tcp-wrapper. Also like an junkbuster for web-banners.
For libc5 you need to replace socklen_t with int.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <errno.h>
int (*real_bind)(int, const struct sockaddr *, socklen_t);
int (*real_connect)(int, const struct sockaddr *, socklen_t);
char *bind_addr_env;
unsigned long int bind_addr_saddr;
unsigned long int inaddr_any_saddr;
struct sockaddr_in local_sockaddr_in[] = { 0 };
void _init (void)
{
const char *err;
real_bind = dlsym (RTLD_NEXT, "bind");
if ((err = dlerror ()) != NULL) {
fprintf (stderr, "dlsym (bind): %s\n", err);
}
real_connect = dlsym (RTLD_NEXT, "connect");
if ((err = dlerror ()) != NULL) {
fprintf (stderr, "dlsym (connect): %s\n", err);
}
inaddr_any_saddr = htonl (INADDR_ANY);
if (bind_addr_env = getenv ("BIND_ADDR")) {
bind_addr_saddr = inet_addr (bind_addr_env);
local_sockaddr_in->sin_family = AF_INET;
local_sockaddr_in->sin_addr.s_addr = bind_addr_saddr;
local_sockaddr_in->sin_port = htons (0);
}
}
int bind (int fd, const struct sockaddr *sk, socklen_t sl)
{
static struct sockaddr_in *lsk_in;
lsk_in = (struct sockaddr_in *)sk;
/* printf("bind: %d %s:%d\n", fd, inet_ntoa (lsk_in->sin_addr.s_addr),
ntohs (lsk_in->sin_port));*/
if ((lsk_in->sin_family == AF_INET)
&& (lsk_in->sin_addr.s_addr == inaddr_any_saddr)
&& (bind_addr_env)) {
lsk_in->sin_addr.s_addr = bind_addr_saddr;
}
return real_bind (fd, sk, sl);
}
int connect (int fd, const struct sockaddr *sk, socklen_t sl)
{
static struct sockaddr_in *rsk_in;
rsk_in = (struct sockaddr_in *)sk;
/* printf("connect: %d %s:%d\n", fd, inet_ntoa (rsk_in->sin_addr.s_addr),
ntohs (rsk_in->sin_port));*/
if ((rsk_in->sin_family == AF_INET)
&& (bind_addr_env)) {
real_bind (fd, (struct sockaddr *)local_sockaddr_in, sizeof (struct sockaddr));
}
return real_connect (fd, sk, sl);
}
编辑 2
如果进程试图将 IPv4 地址映射到 IPv6 地址,例如 192.0.2.128 变为 ::ffff:192.0.2.128,则该解决方案不起作用。要防止 java 这样做,请使用以下 VM 参数
-Djava.net.preferIPv4Stack=true
围绕 libc connect() 做一个包装器,它将 bind() 绑定到所需地址(如果尚未绑定),并继续使用原始 connect() 函数,并使用 LD_PRELOAD 将其附加到目标进程.我使用的是来自 FreeBSD 端口的名为 "libconnect" 的;遗憾的是,源代码不见了,但是对于有经验的 Linux 程序员来说,恢复它(并制作 Linux 端口)是一项非常容易的任务(1-2 小时)。
我有一台服务器,我们将向其添加一个额外的 IP 地址。服务器是OpenVZ虚拟化平台上的CentOSVPS运行
我想做的是在不影响现有Java程序的情况下引入新地址,所有现有程序应继续使用主地址。
我意识到 Socket() 构造函数有一个变体,其中可以指定使用的 IP 地址 - 这不是一个理想的解决方案,因为它涉及更改相当多的代码 - 其中大部分是库代码根本不期望多个 IP,尤其是在执行客户端请求时。
对于新程序,我打算在Socket构造函数中显式指定次IP,但我希望现有程序直接使用"default" IP而不修改它们。
有没有办法在我的 JVM 配置或 OS 配置中执行此操作。我也有主机节点的管理员权限,如果有帮助的话。
编辑
这是已接受答案的一个实现。它可以应用于大多数进程,并且不限于 java 程序。代码是用 C 写的,但不需要了解 C 就可以使用它。说明在评论里。
说明here
/*
Copyright (C) 2000 Daniel Ryde
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
/*
LD_PRELOAD library to make bind and connect to use a virtual
IP address as localaddress. Specified via the enviroment
variable BIND_ADDR.
Compile on Linux with:
gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE
Example in bash to make inetd only listen to the localhost
lo interface, thus disabling remote connections and only
enable to/from localhost:
BIND_ADDR="127.0.0.1" LD_PRELOAD=./bind.so /sbin/inetd
Example in bash to use your virtual IP as your outgoing
sourceaddress for ircII:
BIND_ADDR="your-virt-ip" LD_PRELOAD=./bind.so ircII
Note that you have to set up your servers virtual IP first.
This program was made by Daniel Ryde
email: daniel@ryde.net
web: http://www.ryde.net/
TODO: I would like to extend it to the accept calls too, like a
general tcp-wrapper. Also like an junkbuster for web-banners.
For libc5 you need to replace socklen_t with int.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <errno.h>
int (*real_bind)(int, const struct sockaddr *, socklen_t);
int (*real_connect)(int, const struct sockaddr *, socklen_t);
char *bind_addr_env;
unsigned long int bind_addr_saddr;
unsigned long int inaddr_any_saddr;
struct sockaddr_in local_sockaddr_in[] = { 0 };
void _init (void)
{
const char *err;
real_bind = dlsym (RTLD_NEXT, "bind");
if ((err = dlerror ()) != NULL) {
fprintf (stderr, "dlsym (bind): %s\n", err);
}
real_connect = dlsym (RTLD_NEXT, "connect");
if ((err = dlerror ()) != NULL) {
fprintf (stderr, "dlsym (connect): %s\n", err);
}
inaddr_any_saddr = htonl (INADDR_ANY);
if (bind_addr_env = getenv ("BIND_ADDR")) {
bind_addr_saddr = inet_addr (bind_addr_env);
local_sockaddr_in->sin_family = AF_INET;
local_sockaddr_in->sin_addr.s_addr = bind_addr_saddr;
local_sockaddr_in->sin_port = htons (0);
}
}
int bind (int fd, const struct sockaddr *sk, socklen_t sl)
{
static struct sockaddr_in *lsk_in;
lsk_in = (struct sockaddr_in *)sk;
/* printf("bind: %d %s:%d\n", fd, inet_ntoa (lsk_in->sin_addr.s_addr),
ntohs (lsk_in->sin_port));*/
if ((lsk_in->sin_family == AF_INET)
&& (lsk_in->sin_addr.s_addr == inaddr_any_saddr)
&& (bind_addr_env)) {
lsk_in->sin_addr.s_addr = bind_addr_saddr;
}
return real_bind (fd, sk, sl);
}
int connect (int fd, const struct sockaddr *sk, socklen_t sl)
{
static struct sockaddr_in *rsk_in;
rsk_in = (struct sockaddr_in *)sk;
/* printf("connect: %d %s:%d\n", fd, inet_ntoa (rsk_in->sin_addr.s_addr),
ntohs (rsk_in->sin_port));*/
if ((rsk_in->sin_family == AF_INET)
&& (bind_addr_env)) {
real_bind (fd, (struct sockaddr *)local_sockaddr_in, sizeof (struct sockaddr));
}
return real_connect (fd, sk, sl);
}
编辑 2 如果进程试图将 IPv4 地址映射到 IPv6 地址,例如 192.0.2.128 变为 ::ffff:192.0.2.128,则该解决方案不起作用。要防止 java 这样做,请使用以下 VM 参数
-Djava.net.preferIPv4Stack=true
围绕 libc connect() 做一个包装器,它将 bind() 绑定到所需地址(如果尚未绑定),并继续使用原始 connect() 函数,并使用 LD_PRELOAD 将其附加到目标进程.我使用的是来自 FreeBSD 端口的名为 "libconnect" 的;遗憾的是,源代码不见了,但是对于有经验的 Linux 程序员来说,恢复它(并制作 Linux 端口)是一项非常容易的任务(1-2 小时)。