Skip to content

Commit

Permalink
Add Unix domain socket support (line#4846)
Browse files Browse the repository at this point in the history
Motivation:

In service mesh environment, it's quite common for a sidecar to
communicate with an app using a Unix domain socket. Armeria could be
used more efficiently than using TCP/IP in such a scenarios with
domain socket support.

Modifications:

- Updated the various places to support domain sockets, as listed
  below
- Common changes:
  - Added `DomainSocketAddress`
    - `DomainSocketAddress` is a subtype of `InetSocketAddress` for
      backward compatibility with the existing API that returns
      `InetSocketAddress` rather than `SocketAddress`.
- (Breaking) Changed the return type of `RequestContext.localAddress()`
    and `remoteAddress()` from `SocketAddress` and `InetSocketAddress`
    because `DomainSocketAddress` is now an `InetSocketAddress`.
  - (Breaking) The parameter type of the following methods has been
    changed from `SocketAddress` to `InetSocketAddress`:
    - `ClientRequestContextBuilder.localAddress()`
    - `ClientRequestContextBuilder.remoteAddress()`
    - `ServiceRequestContextBuilder.localAddress()`
    - `ClientRequestContextBuilder.remoteAddress()`
  - Added various getters to `TransportType` and `TransportTypeProvider`
so it provides the transport-specific information about domain socket
    classes and whether the transport supports domain sockets
  - Added `ChannelUtil.localAddress()` and `remoteAddress()` and replace
    the direct invocation of `Channel.localAddress()` and
`remoteAddress()` to convert Netty's `DomainSocketAddress` into ours.
- Updated Netty to 4.1.92 that contains the fix for the problem where
      `DomainSocketChannel` returns `null` addresses.
  - Added `ChannelUtil.isTcpOption()` to determine whether the given
    `ChannelOption` is for TCP or not, so that a user doesn't get a WARN
    log when they use domain sockets
- Server-side changes:
  - Added `ServerPort.isDomainSocket()`
  - Updated `Server` and `HttpServerHandler` to support domain sockets.
- Made `ServerRule` and `ServerExtension` not instantiate their default
    `WebClient`s lazily, so that a user can start a serer that listens
    only on a domain socket
- Client-side changes:
  - Added domain socket address support to `Endpoint`:
    - Made `Endpoint` accept a domain socket address in its `host` field
- Added `Endpoint.of(SocketAddress)` so that a user can easily create
an `Endpoint` from an `InetSocketAddress` or a `DomainSocketAddress`
    - Added `Endpoint.isDomainSocket()`
- Updated `HttpClientFactory`, `HttpChannelPool`, `HttpClientDelegate`,
`HttpClientPipelineConfigurator` and `SessionProtocolNegotiationCache`
    to support domain sockets

Result:

- Armeria is now capable of serving/sending requests from/to a Unix
domain
  socket, making it more service-mesh-friendly.
- (Breaking) Changed the return type of `RequestContext.localAddress()`
  and `remoteAddress()` from `SocketAddress` and `InetSocketAddress`
  because `DomainSocketAddress` is now an `InetSocketAddress`.
- (Breaking) The parameter type of the following methods has been
  changed from `SocketAddress` to `InetSocketAddress`:
  - `ClientRequestContextBuilder.localAddress()`
  - `ClientRequestContextBuilder.remoteAddress()`
  - `ServiceRequestContextBuilder.localAddress()`
  - `ClientRequestContextBuilder.remoteAddress()`

---------

Co-authored-by: jrhee17 <guins_j@guins.org>
Co-authored-by: Ikhun Um <ih.pert@gmail.com>
Co-authored-by: minux <songmw725@gmail.com>
  • Loading branch information
4 people committed Jun 12, 2023
1 parent c2d1dd3 commit d1774f9
Show file tree
Hide file tree
Showing 57 changed files with 1,976 additions and 490 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static com.google.common.base.MoreObjects.firstNonNull;

import java.net.SocketAddress;
import java.net.InetSocketAddress;

import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.common.HttpRequest;
Expand Down Expand Up @@ -94,12 +94,12 @@ public void parse(HttpResponse response, TraceContext context, SpanCustomizer sp
span.tag(SpanTags.TAG_HTTP_SERIALIZATION_FORMAT, serFmt);
}

final SocketAddress raddr = ctx.remoteAddress();
final InetSocketAddress raddr = ctx.remoteAddress();
if (raddr != null) {
span.tag(SpanTags.TAG_ADDRESS_REMOTE, raddr.toString());
}

final SocketAddress laddr = ctx.localAddress();
final InetSocketAddress laddr = ctx.localAddress();
if (laddr != null) {
span.tag(SpanTags.TAG_ADDRESS_LOCAL, laddr.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package com.linecorp.armeria.internal.common.brave;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

import com.linecorp.armeria.common.RequestContext;
import com.linecorp.armeria.common.logging.RequestLog;
Expand Down Expand Up @@ -54,19 +52,9 @@ public static void logWireReceive(Span span, long wireReceiveTimeNanos, RequestL
}

public static boolean updateRemoteEndpoint(Span span, RequestContext ctx) {
final SocketAddress remoteAddress = ctx.remoteAddress();
final InetAddress address;
final int port;
if (remoteAddress instanceof InetSocketAddress) {
final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
address = socketAddress.getAddress();
port = socketAddress.getPort();
} else {
address = null;
port = 0;
}
if (address != null) {
return span.remoteIpAndPort(address.getHostAddress(), port);
final InetSocketAddress remoteAddress = ctx.remoteAddress();
if (remoteAddress != null) {
return span.remoteIpAndPort(remoteAddress.getAddress().getHostAddress(), remoteAddress.getPort());
}
return false;
}
Expand Down
9 changes: 5 additions & 4 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,22 @@ dependencies {
api libs.netty.codec.haproxy
api libs.netty.codec.http2
api libs.netty.resolver.dns
implementation libs.netty.handler.proxy

// Netty native libraries
['linux-x86_64', 'linux-aarch_64'].each { arch ->
implementation(variantOf(libs.netty.transport.native.unix.common) { classifier(arch) })
implementation(variantOf(libs.netty.transport.native.epoll) { classifier(arch) })
optionalImplementation(variantOf(libs.netty.io.uring) { classifier(arch) })
}
['osx-x86_64', 'osx-aarch_64'].each { arch ->
implementation(variantOf(libs.netty.transport.native.unix.common) { classifier(arch) })
implementation(variantOf(libs.netty.transport.native.kqueue) { classifier(arch) })
implementation(variantOf(libs.netty.resolver.dns.native.macos) { classifier(arch) })
}

// Note that tcnative now needs explicit classifiers
// Related: https://github.com/netty/netty-tcnative/pull/709
['linux-x86_64', 'linux-aarch_64', 'osx-x86_64', 'osx-aarch_64', 'windows-x86_64'].each { arch ->
implementation(variantOf(libs.netty.tcnative.boringssl) { classifier(arch) })
}
implementation libs.netty.handler.proxy

// TestNG
testImplementation libs.testng
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ private HttpObject convertHeaders(RequestHeaders headers, boolean endStream) {

if (!nettyHeaders.contains(HttpHeaderNames.HOST)) {
final InetSocketAddress remoteAddress = (InetSocketAddress) channel().remoteAddress();
nettyHeaders.add(HttpHeaderNames.HOST, ArmeriaHttpUtil.authorityHeader(remoteAddress.getHostName(),
remoteAddress.getPort(),
protocol().defaultPort()));
nettyHeaders.add(HttpHeaderNames.HOST,
ArmeriaHttpUtil.authorityHeader(remoteAddress.getHostString(),
remoteAddress.getPort(),
protocol().defaultPort()));
}

if (endStream) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import static java.util.Objects.requireNonNull;

import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -200,12 +200,12 @@ public ClientRequestContextBuilder id(RequestId id) {
}

@Override
public ClientRequestContextBuilder remoteAddress(SocketAddress remoteAddress) {
public ClientRequestContextBuilder remoteAddress(InetSocketAddress remoteAddress) {
return (ClientRequestContextBuilder) super.remoteAddress(remoteAddress);
}

@Override
public ClientRequestContextBuilder localAddress(SocketAddress localAddress) {
public ClientRequestContextBuilder localAddress(InetSocketAddress localAddress) {
return (ClientRequestContextBuilder) super.localAddress(localAddress);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ private AbstractEventLoopState state(SessionProtocol sessionProtocol,
secondTryHost = null;
}

final int port = endpoint.hasPort() ? endpoint.port() : sessionProtocol.defaultPort();
final Endpoint endpointWithPort = endpoint.withPort(port);
final Endpoint endpointWithPort = endpoint.withDefaultPort(sessionProtocol);
final int port = endpointWithPort.port();
final boolean isHttp1 = isHttp1(sessionProtocol, endpointWithPort);
final StateKey firstKey = new StateKey(firstTryHost, port, isHttp1);
AbstractEventLoopState state = states.get(firstKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public HttpResponse execute(HttpRequest req, RequestOptions requestOptions) {
final RequestTarget reqTarget = RequestTarget.forClient(originalPath, prefix);
if (reqTarget == null) {
return abortRequestAndReturnFailureResponse(
req, new IllegalArgumentException("Invalid path: " + originalPath));
req, new IllegalArgumentException("Invalid request target: " + originalPath));
}

final EndpointGroup endpointGroup;
Expand Down
Loading

0 comments on commit d1774f9

Please sign in to comment.