Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unix domain socket support #4846

Merged
merged 34 commits into from
Jun 8, 2023
Merged

Add Unix domain socket support #4846

merged 34 commits into from
Jun 8, 2023

Conversation

trustin
Copy link
Member

@trustin trustin commented Apr 24, 2023

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
      WebClients 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()

@trustin
Copy link
Member Author

trustin commented Apr 24, 2023

The memory leak is very likely from DomainSocketServerTest which I forgot to release the received buffers. Let me fix it later this week.

@trustin trustin added this to the 1.24.0 milestone Apr 24, 2023
@trustin trustin changed the title Add server-side Unix domain socket support Add Unix domain socket support May 2, 2023
req, new IllegalArgumentException("Invalid path: " + originalPath));
req, new IllegalArgumentException("Invalid request target: " + originalPath));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't always a path but can be an absolute URI, so I changed it to 'request target'.

Comment on lines 388 to 397
void addBeforeSessionHandler(ChannelPipeline pipeline, ChannelHandler handler) {
// Get the name of the HttpSessionHandler so that we can put our handlers before it.
final ChannelHandlerContext lastContext = pipeline.lastContext();
assert lastContext.handler().getClass() == HttpSessionHandler.class;

pipeline.addBefore(lastContext.name(), null, handler);
if (lastContext.handler().getClass() == HttpSessionHandler.class) {
// Get the name of the HttpSessionHandler so that we can put our handlers before it.
pipeline.addBefore(lastContext.name(), null, handler);
} else {
// HttpSessionHandler was not added yet.
pipeline.addLast(handler);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a little bit tricky because a connect() operation in domain sockets succeeds immediately in Linux and thus this method can be invoked even before HttpSessionHandler is added.

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good all in all. 👍

// Use an IPv6 address that falls into RFC 6666 (IPv6 Discard Prefix),
// which will never reach anywhere. See https://datatracker.ietf.org/doc/rfc6666/
private static final byte[] IPV6_DISCARD_ADDR = {
1, 0, 0, 0, 0, 0, 0, 0, // 0100::/64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Isn't it 0, 0, 0, 1, 0, 0, 0, 0?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess not:

jshell> var addr = InetAddress.getByAddress(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
addr ==> /100:0:0:0:0:0:0:0

jshell> var addr = InetAddress.getByAddress(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
addr ==> /0:1:0:0:0:0:0:0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. 🙇

@trustin trustin requested a review from minwoox May 8, 2023 11:28
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I learned a lot from this work.

@trustin trustin force-pushed the uds branch 2 times, most recently from 41c1ce6 to e65ad13 Compare May 19, 2023 03:38
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 in such a scenarios if it is capable of serving requests on a Unix
domain socket.

Modifications:

- Added `DomainSocketAddress`
- Added `ServerPort.isDomainSocket()`
- 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 work around the issue where Netty's domain socket
  channel returns `null`
  - See: netty/netty#13323
- 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
- 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

Result:

- Armeria is now capable of serving requests from a Unix domain socket,
  making it more service-mesh-friendly.

Fix leaks

Update Netty to 4.1.92

Cache remote/localAddress / SocketAddress -> InetSocketAddress

Add client-side domain socket support

Lint

Windows

Formatting

Windows / Lint

Review comments

Remove an unused field
@trustin trustin requested a review from ikhoon May 22, 2023 02:59
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @trustin! 💯 👍

}
['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) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇‍♂️

@trustin trustin requested a review from ikhoon May 23, 2023 14:11
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome feature! 🚀 💡

@trustin
Copy link
Member Author

trustin commented May 30, 2023

I see the following test failures on my Mac:

FileWatcherRegistryTest. runnableWithExceptionContinuesRun
PropertiesEndpointGroupTest. endpointChangePropagatesToListeners
PropertiesEndpointGroupTest. propertiesFileRestart
PropertiesEndpointGroupTest. propertiesFileUpdatesCorrectly

Do you get the same failures, @ikhoon? They look irrelevant to this pull request 🤔

@trustin
Copy link
Member Author

trustin commented May 30, 2023

I see the following test failures on my Mac

This should fix them: #4903

@trustin
Copy link
Member Author

trustin commented May 30, 2023

I also see some flaky tests, which may or may not be related to this PR:

  • HttpServerKeepAliveHandlerTest
  • PortUnificationServerTest
  • ServerMaxConnectionAgeTest

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a minor suggestion but looks good to me 👍 Thanks @trustin ! 🙇 👍 🙇

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM. 👍

@trustin
Copy link
Member Author

trustin commented Jun 7, 2023

Addressed all comments 😆

@ikhoon
Copy link
Contributor

ikhoon commented Jun 7, 2023

Looks good with the new changes.

@trustin trustin merged commit 9a8c0a0 into line:main Jun 8, 2023
@trustin trustin deleted the uds branch June 8, 2023 06:58
@trustin
Copy link
Member Author

trustin commented Jun 8, 2023

Thanks for reviewing this large PR, folks! 🙇

ikhoon added a commit to be-hase/armeria that referenced this pull request Jun 12, 2023
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants