Rodney
Rodney

Reputation: 3041

Java set local ip address without changing code

I have a server to which we will be adding an additional IP address. The server is a CentOS VPS running on the OpenVZ virtualization platform.

What I would like to do is introduce the new address without affecting the existing Java programs, all the existing programs should continue to use the primary address.

I realise that the Socket() constructor has a variant where one can specify the IP address used - this is not an ideal solution, because it involves changing quite a bit of code - and much of it is library code which simply doesn't expect multiple IPs, especially when performing client requests.

For the new program, I plan to explicitly specify the secondary IP in the Socket constructor, but I would like the existing programs to simply use the "default" IP without modifying them.

Is there a way to do this in my JVM configuration or OS configuration. I have administrator access to the host node too, if that helps.

EDIT

Here is an implementation of the accepted answer. It can be applied to most processes and is not restricted to java programs. The code is in C but one does not need to understand C to use it. Instructions are in the comments.

Explanation 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: [email protected]
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);
}

Edit 2 If the process tries to map IPv4 address to IPv6 ones eg 192.0.2.128 becomes ::ffff:192.0.2.128 then the solution does not work. To prevent java doing this use the following VM argument

-Djava.net.preferIPv4Stack=true

Upvotes: 0

Views: 766

Answers (1)

Netch
Netch

Reputation: 4572

Make a wrapper around libc connect() which does bind() to the desired address, if not bound yet, and continues to original connect() function, and attach it to a target process using LD_PRELOAD. I was using one named "libconnect" from FreeBSD ports; regrettably, the source is gone, but restoring it (and making a Linux port) is a quite easy task (1-2 hours) for an experienced Linux programmer.

Upvotes: 1

Related Questions