How do I get the remote IP address for an Rsocket connection in SpringBoot
Asked Answered
P

3

7

I'm trying to get the remote IP of the browser that connects to a RSocket+SpringBoot webserver. Connection is RSocket over WebSocket.

The webserver is Java-8, SpringBoot-2, using RSocket over WebSocket and sending RequestStreams to the browser. I'm leveraging SpringBoot autoconfig for the RSocket setup, so really minimal code on the server side - see below.

@Headers and MessageHeader in the code below was just to see if they had anything that could lead to the remote IP, no other reason they're there.

I've scoured the web looking for an answer - lots for http, a few for websockets, zero for RSocket. This - https://github.com/rsocket/rsocket-java/issues/735 - looked promising, but was unable to get a handle to DuplexConnection, so no cigar there.

Any ideas ? Thx !

application.yml:

spring.rsocket.server:
  mapping-path: /rsocket-test
  transport: websocket

server.port: 8080

TestController.java

 /**
     * TODO: get the remote IP address and log.
     * Options:
     * 1. @see <a href="https://github.com/rsocket/rsocket-java/issues/735">Ability to intercept requests and access channel information such as remote address</a>.
     * 2. Inject IP in header by nginx. See if it shows up in the @Headers param here.
     * 3. Browser gets its public IP and adds it to the request object. Doable, but lame
     * 4. (Unlikely) Find a way to access thru this chain of private members: headers.req.rsocket().source.connection.source.connection.connection.channel.remoteAddress
     */
    @MessageMapping("subscribe-topic")
    public Flux<StreamingEvent> subscribeToEventStream(
                @Headers Map<String,Object> hdrs,
                MessageHeaders mh,
                testRequest request) {
        return testService.subscribeTopic(request.getRequestId(), request.getTopic());
    }
Perceptible answered 5/5, 2020 at 2:41 Comment(0)
B
3

Client's ip address is available in the DuplexConnection class. You can add an interceptor to the RSocketServer like this:

@Bean
public RSocketServerCustomizer ipCustomizer() {
    return rSocketServer -> rSocketServer.interceptors(registry -> registry.forConnection(new ConnectionInterceptor()));
}

where ConnectionInterceptor is:

static class ConnectionInterceptor implements DuplexConnectionInterceptor {
    @Override
    public DuplexConnection apply(Type type, DuplexConnection duplexConnection) {
        SocketAddress socketAddress = duplexConnection.remoteAddress();
        if (socketAddress instanceof InetSocketAddress) {
            InetSocketAddress iso = (InetSocketAddress) socketAddress;
            // Here is the ip: iso.getHostString()
        }
        return duplexConnection;
    }
}

Blenheim answered 11/5, 2021 at 16:2 Comment(0)
T
1

In my scenario, I wanted to pass the remote host and the original HTTP request headers to my @MessageMapping handlers. The best way I came up with is to create my own NettyRouteProvider and enrich the subscription context with desired data:

@Component
public class MyRouteProvider implements NettyRouteProvider {

    private final String mappingPath;
    private final SocketAcceptor socketAcceptor;
    private final List<RSocketServerCustomizer> customizers;

    @Autowired
    MyRouteProvider(RSocketProperties properties, RSocketMessageHandler messageHandler,
                    ObjectProvider<RSocketServerCustomizer> customizers) {
        this.mappingPath = properties.getServer().getMappingPath();
        this.socketAcceptor = messageHandler.responder();
        this.customizers = customizers.orderedStream().collect(Collectors.toList());
    }

    @Override
    public HttpServerRoutes apply(HttpServerRoutes httpServerRoutes) {
        RSocketServer server = RSocketServer.create(this.socketAcceptor);
        this.customizers.forEach((customizer) -> customizer.customize(server));
        ServerTransport.ConnectionAcceptor connectionAcceptor = server.asConnectionAcceptor();

        return httpServerRoutes.ws(this.mappingPath, (in, out) ->
                connectionAcceptor.apply(new WebsocketDuplexConnection((Connection) in)).then(out.neverComplete())
                        .contextWrite(context -> {
                            String ip = in.headers().get("x-forwarded-for");
                            SocketAddress remoteAddress = ((Connection) in).channel().remoteAddress();
                            
                            if(in.headers().contains("x-forwarded-for")){
                                return context
                                        .put("remoteHost",remoteAddress)
                                        .put(HttpHeaders.class, in.headers())
                                        .put("client-ip", ip);
                            }
                            return context
                                    .put("remoteHost",remoteAddress)
                                    .put(HttpHeaders.class, in.headers());
                        }));
    }

}

After this you will be able to get the data from the context in any of your @MessageMapping methods, for example:

@MessageMapping("remote-host")
fun remoteHost(): Mono<SocketAddress> {
    return Mono.deferContextual { context ->
        Mono.just(context.get<SocketAddress>("remoteHost"))
    }
}
Temptation answered 22/1, 2022 at 9:21 Comment(0)
F
1

I fought with this problem for quite a while. I did not like the accepted answer, since there is no way to pass the information to the handlers. The answer from Nokina worked, but only on the initial request. On subsequent requests, the context would not contain the address. After spending hours, I just went back to good ol' reflection, and came up with this:

import io.rsocket.DuplexConnection;
import io.rsocket.RSocket;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.net.SocketAddress;

public class RSocketUtil {

    public static DuplexConnection getConnectionFromRequester(RSocketRequester requester) {
        RSocket rsocket = requester.rsocket();
        Field connectionField = ReflectionUtils.findField(rsocket.getClass(), "connection");
        connectionField.setAccessible(true);
        return (DuplexConnection) ReflectionUtils.getField(connectionField, rsocket);
    }

    public static SocketAddress getRemoteAddressFromRequester(RSocketRequester requester) {
        DuplexConnection connection = getConnectionFromRequester(requester);
        return connection.remoteAddress();
    }

}
Featherhead answered 28/8, 2022 at 21:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.