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

Use the correct source IP when binding multiple IPs #3067

Merged
merged 1 commit into from
Mar 16, 2021

Conversation

rs
Copy link
Contributor

@rs rs commented Mar 4, 2021

When the server is listening on multiple interfaces or interfaces with multiple IPs, the outgoing datagrams are sometime delivered with the wrong source IP address.

In order to fix that, each quic connection needs to extract the destination IP (and optionally interface id) of the received datagrams, and set it as source IP (and interface) on the sent datagrams.

On most platforms, this can be done using ancillary data with recvmsg() and sendmsg(). Some of the machinery for this is already there for ECN, this change extends it to read the destination IP info and write it to the outgoing packets.

Fix #1736

Note that this change is highly untested at this point.

@codecov
Copy link

codecov bot commented Mar 4, 2021

Codecov Report

Merging #3067 (eb6bdfd) into master (3bce408) will decrease coverage by 0.43%.
The diff coverage is 54.60%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3067      +/-   ##
==========================================
- Coverage   85.72%   85.29%   -0.43%     
==========================================
  Files         131      131              
  Lines        9520     9607      +87     
==========================================
+ Hits         8161     8194      +33     
- Misses        990     1044      +54     
  Partials      369      369              
Impacted Files Coverage Δ
packet_handler_map.go 75.10% <0.00%> (ø)
send_conn.go 33.33% <38.46%> (-66.67%) ⬇️
conn_oob.go 54.89% <54.89%> (ø)
server.go 80.06% <58.33%> (-0.41%) ⬇️
client.go 78.33% <100.00%> (ø)
conn.go 100.00% <100.00%> (ø)
session.go 75.94% <100.00%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3bce408...eb6bdfd. Read the comment docs.

@rs rs force-pushed the packetinfo branch 2 times, most recently from efd277f to 8707051 Compare March 4, 2021 18:43
@marten-seemann
Copy link
Member

Thank you for this PR! Before I do an in-depth review, 3 questions:

  1. Do you see any problems when using batch reads and writes (see use batch read / writes, if available #2607)? Can we just use the serialized pktinfo in the ipv{4,6}.Message.OOB?
  2. Is there anything we can do about the FreeBSD special casing? We can't test FreeBSD on GitHub Actions (and most other CIs), and I've made the painful experience that everything that's not tested regularly will end up broken rather sooner than later.
  3. What's the reason you're not using the functions in https://github.com/golang/net/blob/e18ecbb05110/ipv4/control_pktinfo.go for encoding / decoding the pktinfo? Is it because they're unexported?

@rs
Copy link
Contributor Author

rs commented Mar 5, 2021

  1. I see no issue as OOB seems to be supported with this API
  2. I guess we could use x/net/ipv{4,6}
  3. I did not want to add a dep and I had hard time following the code of the lib regarding control messages.

@marten-seemann
Copy link
Member

marten-seemann commented Mar 9, 2021

  1. Great!
  2. / 3. Does that allow us to get rid of the special case for FreeBSD? If so, that would be a big win in terms of testing of this change.

@rs
Copy link
Contributor Author

rs commented Mar 9, 2021

  • / 3. Does that allow us to get rid of the special case for FreeBSD? If so, that would be a big win in terms of testing of this change.

Yes but x/net does not support ECN so we will have to use two different ways to deal with OOB. It means we will be parsing OOB twice.

Copy link
Member

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

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

This looks pretty good to me. I left a few comments on the PR, please have a look.

Test-wise, it would be nice if we could have a unit test, along the lines of the ECN test: https://github.com/lucas-clemente/quic-go/blob/9dcb56b76f21241216e90009f3ed26d82c6c163f/conn_ecn_test.go#L56-L74
Making the unit test platform independent is a good way to test that all the platform-dependent code behaves as expected.

conn_oob.go Outdated
// int cmsg_level;
// int cmsg_type;
// }
hdrLen := 16
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
hdrLen := 16
const hdrLen = 16

conn_oob.go Outdated
// struct in_addr ipi_addr; /* Header Destination
// address */
// };
oob := make([]byte, hdrLen+12)
Copy link
Member

Choose a reason for hiding this comment

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

What happens to the 4 leftover bytes on FreeBSD?

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 catch

}

func (c *sconn) Write(p []byte) error {
_, err := c.PacketConn.WriteTo(p, c.remoteAddr)
_, err := c.WritePacket(p, c.remoteAddr, c.info)
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to serialize the packetInfo once, instead of doing it for every WritePacket call? This would save a lot of garbage, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's a good point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We also probably don't need all this machinery in case the server does not listen on ANY.

On freebsd, there is also the limitation that it does not seem to work if ANY v4+v6 is bound, you need to create v4 and v6 listeners separately.

return addr
}

type spconn struct {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand the logic here. As far as I can see, the spconn is used by the client, whereas the server would use the sconn. That means we assume that the client's kernel will choose the right interface to send the packet from. Is my reasoning correct?

Can we achieve this by using the same struct though? WritePacket also works if the packetInfo is nil, doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your reasoning is correct, although, using the same struct for client would require the client code to wrap the net.PacketConn to make it a connection in all the client code too.

@marten-seemann
Copy link
Member

Looks like the cross-compilation test is failing on some architectures. Can you please check?
As far as I can see, we're also still missing a unit test, right?

@rs
Copy link
Contributor Author

rs commented Mar 11, 2021

Yes that’s next on my list. I need to learn this testing framework first.

@marten-seemann
Copy link
Member

@rs I'm happy to help if you run into any problem, please let me know.
This is a really valuable PR, and I'd like to get it merged as soon as possible.

@rs
Copy link
Contributor Author

rs commented Mar 14, 2021

@marten-seemann I'm not sure why the keepalive test fails since I rebased.

@marten-seemann
Copy link
Member

Unit tests LGTM. Don't worry about the timeout test, it's been flaky recently. Will investigate it soon.

Can you please rebase & squash your PR?

@rs
Copy link
Contributor Author

rs commented Mar 15, 2021

Done

When the server is listening on multiple interfaces or interfaces with
multiple IPs, the outgoing datagrams are sometime delivered with the
wrong source IP address.

In order to fix that, each quic connection needs to extract the
destination IP (and optionally interface id) of the received datagrams,
and set it as source IP (and interface) on the sent datagrams.

On most platforms, this can be done using ancillary data with recvmsg()
and sendmsg(). Some of the machinery for this is already there for ECN,
this change extends it to read the destination IP info and write it to
the outgoing packets.

Fix quic-go#1736
Copy link
Member

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

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

@rs LGTM now. Thank you for your contribution!

@marten-seemann marten-seemann merged commit e9ea484 into quic-go:master Mar 16, 2021
@aschmahmann aschmahmann mentioned this pull request May 14, 2021
71 tasks
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.

Incorrect source ip address in outgoing UDP datagrams
2 participants