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

Fix #39: getaddrinfo numeric error codes are now translated #40

Merged
merged 7 commits into from
Sep 11, 2022

Conversation

LeeTibbert
Copy link
Collaborator

@LeeTibbert LeeTibbert commented Sep 10, 2022

Numeric error codes returned from getaddrinfo are now translated to text.
Fixes #39.

Previously:

==> X epollcat.TcpSuite.LeeT gai error msg 0.01s
java.io.IOException: getaddrinfo: -9

After this PR:

==> X epollcat.TcpSuite.LeeT gai error msg 0.01s
java.io.IOException: getaddrinfo:
  Address family for hostname not supported address: 240.0.0.1  port: 80

Most developers will never see these messages, but those who do will
appreciate the time savings.

@LeeTibbert
Copy link
Collaborator Author

The (relatively) new Suite 20 second timeout has already saved me
a lot of time today, thank you.

@LeeTibbert
Copy link
Collaborator Author

The context for this change is that as I probe IPv6 support, I cause lots of gaiaddrinfo errors, sometimes
intentionally and sometimes not. I have always slacked off on gai errors and printed the code. A few
years of "I have been meaning to fix that!" translated into action. Especially as I face the prospect
of my introducing more gai errors.

@LeeTibbert
Copy link
Collaborator Author

A CI that does not take 2 hours to run (and then fail). What a joy!

Copy link
Owner

@armanbilge armanbilge 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 PR! Happy to see error messages being improved.

Do you think we can simulate some of these errors in our test suite? If so, then what would you think about aiming to make our error messages consistent with those used by the JVM/JDK?


val port = addr.getPort.toString

s"${gaiMsg} address: ${adr} port: ${port}"
Copy link
Owner

Choose a reason for hiding this comment

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

What are your thoughts on the security concersn with including the address and port in the error message? We recently had this exact discussion on the Typelevel Discord.

Some insights from Michael Pilquist:

any security concerns about including host/port in the error message? e.g., imagine an http4s route that tries to make a request to some other service and fails with timeout -- that exception message would get propagated to client, exposing internal hosts/port info

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for the review & raising the concern.

TL;DR
If there is any chance that the info will impede upstream adoption, I will remove
it and use a private version. No problem.

Longer discussion:

Personal opinion:

Bad actors are incredibly clever at exploiting any lack of entropy. Witness all the
Intel & AMD speculative execution hacks.

People trying to fix problems depend upon there being a lack of entropy: i.e. information.

The very fact that a getaddrinfo message of any kind is being reported gives
suitably motivated bad actors information. Removing even a simple message
makes life difficult/impossible for the "fixers".

"fixers" can be overwhelmed by too much information, so finding the appropriate
amount is a changing goal.

Here the port number probably gives away the most information.
The address is in n.n.n.n or (soon) xxxx::x notation, not a string
"secrets.com". The numeric address can be translated, but
it takes someone's time and a bit of skill.

The understanding of "Industry best-practice" changes over time.
At this point and for the foreseeable future I think giving at
least the basic getaddrinfo code translation can be defended
as "Industry standard".

I think we concur that Rapid prototyping is not an occasion for a security give-away.
Security analysis is essential but needs to be considered in scope: valid security, extra-cautious security,
paranoid security, and security-theater.

I think the base PR is safe but not worth investing the time to defend to the
"deny any information at all" folks.

Copy link
Owner

Choose a reason for hiding this comment

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

Thanks for sharing your thoughts. Unfortunately I am not a security expert, so in doubt I defer to the JVM: can we simulate some of these errors in our test suite, and make the exceptions match what the JVM does in the same situation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Looks like Java 17 ServerSocket bind distinguishes EAI_FAMILY (unsupported address family)
and throws an IllegalArgumentException. In other cases it throws IOException. The
current epoll code always throws IOException (with a message that would give the unsupported
AF, but probably not tell which af it got). That is fixable.

I can try to provoke the a SocketServer bind IOException and see exactly what
text it returns. Will take time (day or two-ish).

Then I have to check the java.nio Channel area to see if there is any
complication/change in message.

I can submit the simple (no address, no port) gai_message updates
so they do not get lost on my end. Understanding that they are on
hold whilst I find out what Java does.

Copy link
Owner

Choose a reason for hiding this comment

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

Thanks for understanding! That sounds like a good plan to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Update:

Java 17 AsynchronousSeverSocketChannel throws UnsupportedAddressTypeException not IllegalArgumentException
for wrong AF. I expect AsynchronousSocketChannel to to the same but will check before implementing.

@LeeTibbert
Copy link
Collaborator Author

Arman,

Per our helpful discussion,:

  1. I altered two getaddrinfo error paths to return the same kind of exception on both JVM & Native.
    That exception has no arguments, so no issues with host names. UnsupportedAddressTypeException
    is a RuntimeException while IOException is a java.lang.Exception.

This particular path should never be seen, but if it is, they now match

JVM describes no getaddrinfo messages. If any of those show up in
epollcat native, we will be glad to have them to give us a clue. They
are 'should never happen'.

  1. I edited AsynchronousServerSocket#bind so that now both JVM & Native
    throw the same EADDRNOTAVAIL exception.

I had to fudge things a bit for 0.4.5 (?) because EADDRNOTAVAIL is not available there.
It should be there if the backport merge from 0.5.0 happened correctly. It is in 0.5.0.
If you have a better solution, let me know. Else avert your eyes for that short section.

  1. I checked & tickled AsynchronousSocket#connect to see if I could get the Native
    and JVM Exception to align in at least the common case. Limited joy.
    When presented with a known bogus IPv4 address, "10%en0", they both
    give the same message. Good so far.

    Where things differ is that JVM rightly detects that InetAddress.getByName("240.0.0.") is
    bogus and throws an Exception. SN does not detect the bogus address and attempt
    to connect to something (I ran out of time & drive before I could ascertain what), and
    eventually times out.

    I think the proper path forward is for me to create a Scala Native Issue. I had been
    hoping to do some simplification in that now 12+ years old code. Harmony did
    not have the advantage of inet_pton or inet_ntop.

This is a second evolution, there are probably 99 or more evolutions to go, but
not tonight.

PS: During the course of this work, I discovered that strerror & kin are totally
missing from Scala Native. They are used to convert errno.errno numerics
to text strings. I created a Scala Native Issue. epollcat cuts another notch
on the stick used to record SN bugs found & reported.

@armanbilge
Copy link
Owner

Thanks, Lee, that's looking great!

For every new exception that you add, would you mind adding a test as well? I already have a few existing tests to verify that exceptions match.

test("ConnectException") {
IOServerSocketChannel
.open
.evalTap(_.bind(new InetSocketAddress(0)))
.use(_.localAddress)
.flatMap(addr => IOSocketChannel.open.use(_.connect(addr)))
.interceptMessage[ConnectException]("Connection refused")
}
test("BindException") {
IOServerSocketChannel
.open
.evalTap(_.bind(new InetSocketAddress(0)))
.evalMap(_.localAddress)
.use(addr => IOServerSocketChannel.open.use(_.bind(addr)))
.interceptMessage[BindException]("Address already in use")
}
test("ClosedChannelException") {
IOServerSocketChannel
.open
.evalTap(_.bind(new InetSocketAddress(0)))
.evalMap(_.localAddress)
.use { addr =>
IOSocketChannel.open.use { ch =>
ch.connect(addr) *> ch.shutdownOutput *> ch.write(ByteBuffer.wrap(Array(1, 2, 3)))
}
}
.intercept[ClosedChannelException]
}

@LeeTibbert
Copy link
Collaborator Author

Agreed, a good concept.

When you say "new" do you mean new as in adding new "FooException.scala"
or new as in adding a new use of an existing Exception?

java.nio.channels.UnsupportedAddressTypeException

In this particular case, I had that concern in mind, as it
is my usual practice, albeit usually in an "AdvancedSuite"
and not a daily driver Suite.

I could not figure out a way to cause the Exception at runtime.
I had to hand edit local copies of each EpollAsynchronousSocketChannel and
EpollAsynchronousServerSocketChannel to inject the error.
If I missed something or if you can thing of a way, I can write a suitable test for each.
I will keep the consideration on the back of my brain.

EADDRNOTAVAIL

I will add a runtime test for this. I have a number of cases which
evoke it in my private development area.

@armanbilge
Copy link
Owner

When you say "new" do you mean new as in adding new "FooException.scala"
or new as in adding a new use of an existing Exception?

Yes, every new use. Essentially every time we add a new conditional branch to our sources.

I could not figure out a way to cause the Exception at runtime.

Yeah, no problem. If we can find a way it's always good to add a test to lock in the behavior, if it's non-trivial then let's just move on.

Thanks!

@LeeTibbert LeeTibbert force-pushed the PR_GaiErrorString_I39 branch from 4bc03c3 to 5fadc54 Compare September 11, 2022 20:38
@LeeTibbert
Copy link
Collaborator Author

I added a test for BindException EADDRNOTAVAIL. It can probably be minimized and/or
tightened up by someone with better cats skills than I. I believe that what is there is correct, albeit
probably not idiomatic.

The current test focuses on the failing call bind. The models I had to work from
all did distracting things after the bind before they intercepted the Exception.

@LeeTibbert
Copy link
Collaborator Author

Yes, every new use. Essentially every time we add a new conditional branch to our sources.
Understood.

if it's non-trivial then let's just move on.
Agreed.

@LeeTibbert
Copy link
Collaborator Author

And, of course, macOS has a slightly different message "'Can't assign requested address" (macOS).
Linux uses "Cannot assign requested address"

A learning opportunity for me to figure out how to get interceptMessage to handle "or" or
regex.

@armanbilge
Copy link
Owner

Ha, annoying! Instead of writing a regex why not use an if-else switch with the OS check?

@LeeTibbert
Copy link
Collaborator Author

Ha, annoying! Instead of writing a regex why not use an if-else switch with the OS check?

I am studying munit-cats-effect in another window. Looks like .interceptMessage does not
provide alternatives. I need to figure out how to either set an expectedText variable before
or how to do a ".if" with the Resource (?) currently being piped to .interceptMessage.
I guess that getting the back of the gargoyle's nose hairs carved correctly is a sign
of craftsmanship.

I am off to look for models.

_ <- ch.bind(new InetSocketAddress("240.0.0.1", 0))
} yield ()
}
.interceptMessage[BindException]("Cannot assign requested address")
Copy link
Owner

Choose a reason for hiding this comment

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

@LeeTibbert why not do this?

Suggested change
.interceptMessage[BindException]("Cannot assign requested address")
.interceptMessage[BindException](
if (isLinux) "Cannot assign requested address"
else "Can't assign requested address"
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for the suggestion. I did not know that was possible. Learning new things is good,
but also an exercise in humility.

The code snippet works fine on SN, after importing LinktimeInfo.

Understandably, it fails on JVM. May need to factor this test out and
use the fact that epollcat is a multi-project to have a new Suite
of OS specific tests. I hate having duplicate code, as it
is hard to keep in sync. So, worst case, I/we can fix this.

I am off to look around to see how the test might determine
the OS it is running on. (perhaps more complicate code
within the intercept message to read the System Property
"os.name") Can the code (thunk?) have more than one
statement, as long as it returns a String?)

Copy link
Owner

Choose a reason for hiding this comment

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

You can add this definition at the top of the suite:

val isLinux = {
  val osName = Option(System.getProperty("os.name"))
  osName.exists(_.toLowerCase().contains("linux"))
}

@LeeTibbert
Copy link
Collaborator Author

LeeTibbert commented Sep 11, 2022

I have the runtime "os.name" test working JVM & SN on Linux. Off to
check my mac to see what it uses. I am trying not to peek at the SN code,
which I have seen a thousand times already.

As events unfold, we will probably want to move this test to
someplace common in the Suite. Or use another alternative.

Copy link
Owner

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

Well that was quite an adventure! Thanks for chasing those down 😃

Is this ready for final review? Because it is LGTM. Merge at will :)

@LeeTibbert
Copy link
Collaborator Author

Thank you for the review and tutorage. I may end up learning some Cats IO.

I think this is ready for merge at your convenience.

@armanbilge
Copy link
Owner

I'd say you've got the hang of it, you did a very nice job with the test :)

@armanbilge armanbilge merged commit 67ca8bc into armanbilge:main Sep 11, 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.

Error codes from getaddrinfo could be translated.
2 participants