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 a filter for applying Android socket tags #2423

Merged
merged 36 commits into from
Aug 23, 2022

Conversation

RyanTheOptimist
Copy link
Contributor

@RyanTheOptimist RyanTheOptimist commented Jul 14, 2022

Add a filter for applying Android socket tags

Testing: Functionality verified as part of the run of the experimentation example app. Also filed #2494 to track improvements.
Docs Changes: Updated docs/root/api/starting_envoy.rst
Release Notes: Added

Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
@RyanTheOptimist
Copy link
Contributor Author

@alyssawilk can you take a look? I know I need tests, but hopefully I can lean on whatever testing gets set up for the cleartext check.

Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

wohoo progress!
high level pass because I feel guilty for prioritizing my FINALLY PASSING factory PR.
let's sort out where the code should live filter-wise with Lyfties since I'm hoping we can remove some of the boilerplate

library/common/config/config.cc Outdated Show resolved Hide resolved
library/common/network/socket_tag_socket_option_impl.cc Outdated Show resolved Hide resolved
test/java/integration/AndroidEnvoyExplicitFlowTest.java Outdated Show resolved Hide resolved
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

Sweet, very cool. Next pass comments :-)

docs/root/intro/version_history.rst Show resolved Hide resolved
uid_t uid;
uint32_t traffic_stats_tag;
if (!absl::SimpleAtoi(data.first, &uid) || !absl::SimpleAtoi(data.second, &traffic_stats_tag)) {
return Http::FilterHeadersStatus::StopIteration;
Copy link
Contributor

Choose a reason for hiding this comment

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

StopIteration or sendLocalReply with error? I'd think we wanted the latter? do we have an e2e test for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. sendLocalReply sounds like a good idea. I think I've done the needful.

There are no end-to-end tests because the socket tagging code, like isCleartextPermitted, needs to run in a simulator and I think we're waiting on Lyft to sort that out?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh but this is a failure case based on getting bad headers from the API - you should totally be able to test sending an invalid request with the filter configured, and getting the local response you expect, so let's do that!

you could also optionally add a test for the happy path, which would create the custom transport socket and make sure hashing works, if we don't have that tested upstream. even if the tagging is a no-op it would at least cover that the memory model works.

also clean up the commented out return value above this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, ok. Java tests added and removed the commented out code.

I looked at test/common/integration/client_integration_test.cc, which we discussed offline. I wasn't quite sure how to configure it to add the socket tagging support. Maybe we can discuss that offine?

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. I opened a GH issue to track the work related to adding support for running tests on Android emulator An ability to run tests on Android emulator #2450.
  2. In the meantime you can run Envoy Mobile example app to verify your changes following the setup steps from https://envoymobile.io/docs/envoy-mobile/latest/development/debugging/android_local.html
  3. It's strongly recommended that you enable your changes in Android experimental app. You can do this following this change from one of the previous PRs https:/envoyproxy/envoy-mobile/pull/2379/files#diff-b15ec59c29644d8c6089ad0ea7370859a690563f75d02c819679270b61ab9520R60. This experimental app as runs on an Android emulator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the issue! I'll give the demo app a shot. Updated test/kotlin/apps/experimental/MainActivity.kt per that previous commit. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you. I am happy to help with the setup of the demo app if you run into any issues with it. It's also possible that the documentation is lacking or making some assumptions - let me know and I am happy to help.

Re experimental app - I think that you can go even one step further (sorry for not pointing this out in my previous comment) and adding a socket-tag header to requests performed by the experimental app. This way you would get an end-to-end confirmation that requests with socket-tag go through (we verify whether our experimental example is getting 200 from the server). You could add the header somewhere in here

library/common/extensions/filters/http/socket_tag/filter.h Outdated Show resolved Hide resolved
library/common/network/socket_tag_socket_option_impl.cc Outdated Show resolved Hide resolved
library/java/org/chromium/net/AndroidNetworkLibrary.java Outdated Show resolved Hide resolved
Copy link
Contributor Author

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

Thanks for the great review! Sorry for misunderstanding the API.

uid_t uid;
uint32_t traffic_stats_tag;
if (!absl::SimpleAtoi(data.first, &uid) || !absl::SimpleAtoi(data.second, &traffic_stats_tag)) {
return Http::FilterHeadersStatus::StopIteration;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. sendLocalReply sounds like a good idea. I think I've done the needful.

There are no end-to-end tests because the socket tagging code, like isCleartextPermitted, needs to run in a simulator and I think we're waiting on Lyft to sort that out?

library/common/extensions/filters/http/socket_tag/filter.h Outdated Show resolved Hide resolved
library/common/network/socket_tag_socket_option_impl.cc Outdated Show resolved Hide resolved
library/java/org/chromium/net/AndroidNetworkLibrary.java Outdated Show resolved Hide resolved
docs/root/intro/version_history.rst Show resolved Hide resolved
Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

Looking good! FWIW I really only glanced over the Java as I'm not a useful reviewer for that. tests (when we have the infra) will verify if it's all good.

docs/root/intro/version_history.rst Show resolved Hide resolved
uid_t uid;
uint32_t traffic_stats_tag;
if (!absl::SimpleAtoi(data.first, &uid) || !absl::SimpleAtoi(data.second, &traffic_stats_tag)) {
return Http::FilterHeadersStatus::StopIteration;
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh but this is a failure case based on getting bad headers from the API - you should totally be able to test sending an invalid request with the filter configured, and getting the local response you expect, so let's do that!

you could also optionally add a test for the happy path, which would create the custom transport socket and make sure hashing works, if we don't have that tested upstream. even if the tagging is a no-op it would at least cover that the memory model works.

also clean up the commented out return value above this line?

Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
@RyanTheOptimist
Copy link
Contributor Author

/retest

Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

Ok I think I'm hitting the end of my useful review comments, so let's see if we can get a Lyft volunteer to take it from here :-)
LGTM modulo the few remaining comments

Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Copy link
Contributor Author

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

Thanks for the review! Github didn't seem to properly sign-off the commits it landed so I ended up having to force merge :/


Http::FilterHeadersStatus SocketTagFilter::decodeHeaders(Http::RequestHeaderMap& request_headers,
bool) {
auto socket_tag_header = Http::LowerCaseString("socket-tag");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd be inclined to avoid the "x-" prefix because of RFC 6648, "Deprecating the "X-" Prefix and Similar Constructs in Application Protocols". But happy to use it if that's the convention.

https://datatracker.ietf.org/doc/html/rfc6648

uid_t uid;
uint32_t traffic_stats_tag;
if (!absl::SimpleAtoi(data.first, &uid) || !absl::SimpleAtoi(data.second, &traffic_stats_tag)) {
decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, "Invalid socket-tag header.",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Done.

library/java/org/chromium/net/ThreadStatsUid.java Outdated Show resolved Hide resolved
library/cc/engine_builder.h Show resolved Hide resolved
uid_t uid;
uint32_t traffic_stats_tag;
if (!absl::SimpleAtoi(data.first, &uid) || !absl::SimpleAtoi(data.second, &traffic_stats_tag)) {
return Http::FilterHeadersStatus::StopIteration;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the issue! I'll give the demo app a shot. Updated test/kotlin/apps/experimental/MainActivity.kt per that previous commit. Thanks!

library/common/jni/android_jni_utility.cc Show resolved Hide resolved
Copy link
Contributor

@Augustyniak Augustyniak left a comment

Choose a reason for hiding this comment

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

Thank you for addressing/responding to comments. Tests looks so much cleaner/simpler now. 💯

Left a few more comments

// The socket-tag header must contain a pair of number separated by a comma, e.g.:
// socket-tag: 123,456
// The first number contains the UID and the second contains the traffic stats tag.
std::string tag_string(request_headers.get(socket_tag_header)[0]->value().getStringView());
Copy link
Contributor

Choose a reason for hiding this comment

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

can we reorganize the code to avoid having to call equest_headers.get(socket_tag_header) twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Done.

Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
@RyanTheOptimist
Copy link
Contributor Author

I'm really confused by GitHub. I see an email from 2:04 PM Pacific today (2022-08-11) with a few comments that were sent after I uploaded a few new commits. But github says the commits were yesterday, and some of my comments are missing from the conversation view, or at least hidden. And some seem completely lost. I'm not sure what happened.

In any case, @Augustyniak I tried to explain that I updated the sample app to set socket tags, but end up with a java class not found error for the class org.chromium.net.AndroidNetworkLibrary which implements the tagSocket() method. I'm not quite sure how to resolve this. Any advice?

``enableSocketTagging``
~~~~~~~~~~~~~~~~~~~~~~~

Specify whether to enable support for Android socket tagging. Defaults to false.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Specify whether to enable support for Android socket tagging. Defaults to false.
Specify whether to enable support for Android socket tagging. Unavailable on iOS. Defaults to false.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add this note to make it clear that it's no intended for iOS?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, whoops. I missed your suggestion earlier. Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

I do not want to be annoying but i still do not see it being updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Really? Hm. If I go to https:/envoyproxy/envoy-mobile/pull/2423/files I see:


~~~~~~~~~~~~~~~~~~~~~~~
``enableSocketTagging``
~~~~~~~~~~~~~~~~~~~~~~~

Specify whether to enable support for Android socket tagging. Unavailable on iOS. Defaults to false.

**Example**::

  // Kotlin
  builder.enableSocketTagging(true)

I wonder if this is one of the many different ways that the github UI hides comments and changes by default :(

@Augustyniak
Copy link
Contributor

@RyanTheOptimist I modified the example app locally to enable socket tagging and run it to see whether I can repro the crashes that you mentioned locally. This is what I got:

Screen Shot 2022-08-17 at 10 56 24 PM

That's despite your BUILD file changes in here https:/envoyproxy/envoy-mobile/pull/2423/files#diff-bee4aeb6cd03b6684900f7c2d8bd51dec467011c33929eb3b34008a2a44deb23R19. This issue seems to be exactly the same issue as the one that's related is_cleartext_permitted and which was detailed in #2432.

@jpsim
Copy link
Contributor

jpsim commented Aug 18, 2022

/retest

Signed-off-by: Ryan Hamilton <[email protected]>
void tag_socket(int ifd, int uid, int tag) {
#if defined(__ANDROID_API__)
JNIEnv* env = get_env();
jclass jcls_AndroidNetworkLibrary = env->FindClass("org/chromium/net/AndroidNetworkLibrary");
Copy link
Contributor

Choose a reason for hiding this comment

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

The one thing that you need to do here is to start using the find_class that was introduced in #2483

Suggested change
jclass jcls_AndroidNetworkLibrary = env->FindClass("org/chromium/net/AndroidNetworkLibrary");
jclass jcls_AndroidNetworkLibrary = find_class("org.chromium.net.AndroidNetworkLibrary");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Contributor

@Augustyniak Augustyniak left a comment

Choose a reason for hiding this comment

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

NICE! Great to see it working - this is awesome.

Left a few comments related to tests - apologies for not catching whatever I commented on earlier but I was focused on the functionality more.

General comment: Can we update the description of the PR, please? In particular, for testing can we mention that the functionality is verified as part of the run of the experimentation example app?

``enableSocketTagging``
~~~~~~~~~~~~~~~~~~~~~~~

Specify whether to enable support for Android socket tagging. Defaults to false.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add this note to make it clear that it's no intended for iOS?

RequestScenario requestScenario = new RequestScenario()
.setHttpMethod(RequestMethod.GET)
.setUrl(mockWebServer.url("post/flowers").toString())
.addHeader("x-envoy-mobile-socket-tag", "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.

RequestScenario becomes a little bit of a leaky abstraction as in real application we are not supposed to call addHeader("x-envoy-mobile-socket-tag", "0,0"); directly (since we added addSocketTag).

In fact, I am not sure whether this test works the way it's intended too as it tries to add x-envoy-mobile-socket-tag header in

headers.forEach(entry -> requestHeadersBuilder.add(entry.getKey(), entry.getValue()));
and add(...) call does not accept headers that start with x-envoy-mobile prefix
name.startsWith("x-envoy-mobile", ignoreCase = true) || name.equals("host", ignoreCase = true)

Maybe we could

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Blergh! Yes, good point. Ok, I've added an addSocketTag() method to RequestScenario which delegates to the underlying builder. I verified (via printf) that the socket tagging code is in fact called.

Copy link
Contributor

Choose a reason for hiding this comment

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

great, thank you - I think that adding addSocketTag method to the test scenario is a way to go 👍 . That being said, I think that the right final solution in here is to change the underlying implementation of TestScenario so that it stores test state inside of RequestHeadersBuilder as opposed to on-the-side headers map. Only with RequestHeadersBuilder we can ensure that our tests simulate real-life condition.

I do not think that this has to be done as part of your PR so I am fine with it being merged the way it is now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'm certainly not going to argue with "fine with it being merged the way it is now" :) I'm happy to do the needful in a followup.

That being said, I'm not entirely sure I understand the suggestion. The implementation in RequestScenario currently calls addSocketTag() on RequestHeadersBuilder which sounds like what you were asking for? Or is it not the socket tagging stuff that is problematic, but rather the general header stuff? Are you thinking we should get rid of the headers member in RequestScenario and use the RequestHeadersBuilder directly?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or is it not the socket tagging stuff that is problematic, but rather the general header stuff?

Yes, that's what I meant

Are you thinking we should get rid of the headers member in RequestScenario and use the RequestHeadersBuilder directly?

That was my idea. This way our tests would be guaranteed to "get an update" every time we update RequestHeadersBuilder.

@Override
public MockResponse dispatch(RecordedRequest recordedRequest) {
assertThat(recordedRequest.getMethod()).isEqualTo(RequestMethod.GET.name());
assertThat(recordedRequest.getHeader("x-envoy-mobile-socket-tag")).isEqualTo(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

why is it expected to be equal to null if we set a valid socket tag on the request?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, because we remove it when we detect that it's present

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, exactly!

Copy link
Contributor

Choose a reason for hiding this comment

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

Any way we could ensure that we are really adding x-envoy-mobile-socket-tag at some point? As it is now this test is always green - even if socket tagging is not enabled / does not end up adding a tag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed #2494 as a result of offline discussions. Thanks for working through this!

@alyssawilk alyssawilk removed their assignment Aug 22, 2022
Signed-off-by: Ryan Hamilton <[email protected]>
Signed-off-by: Ryan Hamilton <[email protected]>
Copy link
Contributor

@Augustyniak Augustyniak left a comment

Choose a reason for hiding this comment

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

Left 3 more comments related to your responses/updates from the previous iterations of my review. Happy to approve once you respond to my comments.

Copy link
Contributor

@Augustyniak Augustyniak left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. Really excited to see this landing 🎉 Congratz - great job.

@RyanTheOptimist RyanTheOptimist merged commit 66ecf9f into envoyproxy:main Aug 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants