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

IPv6 support, phase 1 (direct connection) #1938

Merged
merged 2 commits into from
Aug 10, 2021
Merged

Conversation

softins
Copy link
Member

@softins softins commented Aug 2, 2021

This implementation of IPv6 on Jamulus is based on #1017 by @jardous, with further enhancements by @softins. It is an alternative to #1455 by @mstolle629.

It has been tested on Linux, Mac and Windows. It is currently running on jamminthejazz, at privjam.softins.co.uk:22123 (IPv4) and privjam6.softins.co.uk:22123 (IPv6). There is also a private instance at privjam.softins.co.uk and privjam6.softins.co.uk, which may be used for testing.

Servers running this version can accept a mix of IPv4 and IPv6 connections.

Server addresses for direct connection can be specified in the following formats:

  • [v6addr]
  • [v6addr]:port
  • [v4addr]
  • [v4addr]:port
  • v4addr
  • v4addr:port
  • hostname
  • hostname:port

A literal IPv6 address must always be enclosed in [ ], to avoid ambiguity with a port.

Phase 2 will be the addition of Directory Server support, which will require some
protocol enhancements. Phase 1 is worth adding on its own before Phase 2 is ready.

Potential ChangeLog entry

Add support for direct connections using IPv6

Documentation needed

  • Command line options -6 and --enableipv6
  • Address formats as shown above

Status of this Pull Request

Working implementation

What is missing until this pull request can be merged?

More testing is always good!

Checklist

  • I've verified that this Pull Request follows the general code principles
  • I tested my code and it does what I want
  • My code follows the style guide
  • I waited some time after this Pull Request was opened and all GitHub checks completed without errors.
  • I've filled all the content above

src/socket.cpp Outdated Show resolved Hide resolved
src/socket.cpp Outdated Show resolved Hide resolved
@@ -31,6 +31,7 @@ CClient::CClient ( const quint16 iPortNumber,
const QString& strMIDISetup,
const bool bNoAutoJackConnect,
const QString& strNClientName,
const bool bNEnableIPv6,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It doesn't need the ugly N, generally.

Copy link
Member Author

Choose a reason for hiding this comment

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

Was just following what appeared to be the majority convention. No strong view either way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not going to block it for that, either. (It's just on my list of things to get rid of.)

@pljones
Copy link
Collaborator

pljones commented Aug 2, 2021

Um... Given IPv4 is always enabled and given that only IPv4 is used to communicate with directory servers (for now), is there any reason not to have IPv6 always enabled?

@softins
Copy link
Member Author

softins commented Aug 2, 2021

I wondered that. It would fail on a system that didn't have an IPv6 stack. Such as a Linux box with a file containing "blacklist ipv6" in /etc/modprobe.d/ (I did test that on my Pi). We could try creating an AF_INET6 socket first and falling back to AF_INET, but would then need to propagate the result of that to a global variable that all classes could consult.

@pljones
Copy link
Collaborator

pljones commented Aug 2, 2021

Well, rather than set bEnableIPv6 in main.cpp based on a command line parameter, set it based on the result of the check, leaving the rest the same? (More bIPv6Available, in other words.) Then it could be invisible to the user.

@softins
Copy link
Member Author

softins commented Aug 2, 2021

I did try something like that earlier, but found that it wasn't easy to pass the result of the test back up from the CSocket, given that the client and server sockets are not newed, but instantiated as members of CClient or CServer.

I could perhaps create a static method of CSocket that just tries to create a temporary AF_INET6 socket and returns a success indication, and call that from main.cpp

But we also need to consider the implications for:

  • A client on a computer that is IPv6-capable, but connected via an internet connection that does not support IPv6
  • A server that supports IPv6 and has a routable IPv6 address, but the operator does not want their Jamulus server to listen on IPv6, for whatever reason.

@pljones
Copy link
Collaborator

pljones commented Aug 3, 2021

You've given reasons to switch IPv6 off. Maybe the command line parameter should do that?

I'm thinking of other software with internet connectivity -- I don't have to tell my web browser or email client whether or not to support IPv6, for example.

@ann0see
Copy link
Member

ann0see commented Aug 3, 2021

A client on a computer that is IPv6-capable, but connected via an internet connection that does not support IPv6

Best case if host name is given: try to request an A record via DNS. Use ipv4 as fallback. But does Jamulus need to handle that? Isn’t that an OS functionality? Not only Jamulus will have this problem?!

If I‘m giving an ipv6 address it should fail and not try to get a connection over ipv4.

How does Firefox/Chrome/whatever handle this?

Edit see this: https://blog.counterpath.com/ipv6

@ann0see
Copy link
Member

ann0see commented Aug 3, 2021

A server that supports IPv6 and has a routable IPv6 address, but the operator does not want their Jamulus server to listen on IPv6, for whatever reason.

Use the bind IP option and only provide an ipv6/ipv4 address?

@atsampson
Copy link
Contributor

How does Firefox/Chrome/whatever handle this?

Happy Eyeballs.

Firefox does have a configuration option to disable IPv6 entirely to speed things up a bit if you know you're on a network without external IPv6 connectivity (it's still not that unusual to have local IPv6 but not routable to the world).

@softins
Copy link
Member Author

softins commented Aug 3, 2021

How does Firefox/Chrome/whatever handle this?

Happy Eyeballs.

Certainly very interesting, but quite complex, and I feel it's likely to be overkill for Jamulus. It's primarily concerned with resolving DNS names using both AAAA and A records, and then trying to connect with the returned addresses, preferring IPv6. In Jamulus, an operator of a private server using IPv6 will likely give his invitees a literal address or have a specific AAAA record for it. And once we extend to directory servers, they will return to the client literal IPv6 (if any) and IPv4 addresses, not DNS names.

@softins
Copy link
Member Author

softins commented Aug 3, 2021

You've given reasons to switch IPv6 off. Maybe the command line parameter should do that?

Maybe, but I think the number of people with an IPv6-capable computer but an IPv4-only internet connection would be a lot larger than the number of people who specifically want to use IPv6. So we would be requiring the much larger number of people to do something different.

I would prefer to have the new IPv6 functionality be optional in the first one or two releases that contain it, and once we have gained more experience with it, we could make it the default.

@ann0see
Copy link
Member

ann0see commented Aug 3, 2021

In Jamulus, an operator of a private server using IPv6 will likely give his invitees a literal address or have a specific AAAA record for it.

What makes you sure about that?

From a UX point of view, I think just giving one address (with AAAA and A records) is the preferable outcome. Maybe - but for debugging purposes only - the admin sends over an IP to force ipv6/ipv4. That’s just my opinion…

For directories we‘re skipping DNS?

Can we send ping messages over IPv6 and IPv4, and constantly calculate the time difference between both as soon as the connection window is open? The client will then know which one to choose before connecting.

Issues:

  • Worst case: double the ping traffic
  • Complexity on the client side
  • Constant calculation might not be necessary. We might stop after a few ping messages.

Pro:

  • Probably similar to "Happy eyeballs"
  • User doesn‘t need to decide in normal mode, but could still use the direct IP to connect to the server

@ann0see
Copy link
Member

ann0see commented Aug 3, 2021

So -6 is a client and server option?

Tested ipv4 and ipv6 over a dual stack VDSL line. To me it seems as if ipv4 has a little better connectivity in terms of latency. But probably this varies depending on the provider.

@softins
Copy link
Member Author

softins commented Aug 3, 2021

So -6 is a client and server option?

Yes.

@softins
Copy link
Member Author

softins commented Aug 3, 2021

In Jamulus, an operator of a private server using IPv6 will likely give his invitees a literal address or have a specific AAAA record for it.

What makes you sure about that?

I'm not sure, it just seems likely to me. Most people I know with private servers haven't set up a DNS name and just give their friends the IP. There are a few more technical ones who have set up a DNS name.

For directories we‘re skipping DNS?

We can use a DNS name to find a directory, but when a server registers with a directory, it does so just by IP (the directory stores the IP it registered from). The directory then tells a requesting client just the server's IP, not a DNS name, plus other relevant information (name, private IP, port number, etc).

Can we send ping messages over IPv6 and IPv4, and constantly calculate the time difference between both as soon as the connection window is open? The client will then know which one to choose before connecting.

That might well be a useful enhancement for the future, but I don't think we should hold up the basic feature waiting for that particular lily to be gilded.

@ann0see
Copy link
Member

ann0see commented Aug 6, 2021

Ok. So what really needs to be tested to get this merged?

IPv6/IPv4 over a dual stack connection seems to work.

@softins
Copy link
Member Author

softins commented Aug 6, 2021

Ok. So what really needs to be tested to get this merged?

Well I've tested it quite a bit in my environment, on Linux, Windows and Mac, and it's running on my server as mentioned above. I think it's good to go, and that it is beneficial to have -6 to explicitly turn it on for now. Then others who want to try out IPv6 could do so, without it having unexpected impacts on those who don't.

So it just needs two approvals (which can be tricky), and it could be merged. I would then start thinking about Directory Servers.

IPv6/IPv4 over a dual stack connection seems to work.

Cool!

@pljones
Copy link
Collaborator

pljones commented Aug 6, 2021

it is beneficial to have -6 to explicitly turn it on for now.

Agreed for the "experimental support" version.

If the short version is going to change to mean --disable-ipv6, then that needs to be made very clear. Or maybe for now don't have the short version, just --enable-ipv6?


UdpPort = &UdpSocketAddr.sa6.sin6_port; // where to put the port number

// FIXME: If binding a dual-protocol interface to a specific address, does it cease to be dual-protocol?
Copy link
Collaborator

@pljones pljones Aug 6, 2021

Choose a reason for hiding this comment

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

A socket listening on an IPv4 address is only listening for IPv4 traffic. But the same interface could have a separate socket listening on an IPv6 address, I believe. The interface has the MAC address (or equivalent). The protocol has the IPv4 or IPv6 address that goes with the socket layer in use. (That's why the union is used for the address: there can be only one - on that socket.)

At least, that's my understanding.

Copy link
Member Author

Choose a reason for hiding this comment

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

A socket listening on an IPv4 address is only listening for IPv4 traffic. But the same interface could have a separate socket listening on an IPv6 address, I believe.

Yes it could, but Jamulus only expects to listen on one socket. So I think that makes it unable to bind to a specific interface address and remain dual-stack. I don't think it's a problem, unless someone flags up a scenario where it is. The default of binding to :: will allow it to receive both IPv4 and IPv6 traffic without needing two sockets.

Copy link
Collaborator

@pljones pljones Aug 7, 2021

Choose a reason for hiding this comment

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

Yes it could, but Jamulus only expects to listen on one socket.
The default of binding to :: will allow it to receive both IPv4 and IPv6 traffic without needing two sockets.

How does an IPv4 client know what address to use? Does the IPv6 socket "just know" it's got two addresses really? (I've some vague memory that every IPv4 address has an IPv6 address direct equivalent...)

Copy link
Member Author

Choose a reason for hiding this comment

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

The IPv4 client will connect in the same way as at present, by being told the IP address, either explicitly, or from a hostname lookup, or by a directory server.

Binding an AF_INET6 socket to :: (declared as in6addr_any) is the IPv6 equivalent of binding an AF_INET socket to 0.0.0.0 (INADDR_ANY), which means it will accept any incoming packet that arrives on any interface. If the socket has been setsockopted to dual stack (as we do), it will accept both IPv4 and IPv6 packets.

An IPv4 address mapped to IPv6 uses 0000:0000:0000:0000:0000:ffff as the first 96 bits, which denotes that the last 32 bits contain an IPv4 address in network byte order. Often expressed like ::ffff:192.168.1.1. This mapping is used in CSocket::SendPacket() and checked for in CSocket::OnDataReceived().

src/socket.cpp Outdated
@@ -172,6 +232,15 @@ CSocket::~CSocket()

void CSocket::SendPacket ( const CVector<uint8_t>& vecbySendBuf, const CHostAddress& HostAddr )
{
union
Copy link
Collaborator

@pljones pljones Aug 6, 2021

Choose a reason for hiding this comment

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

Can this go in a header somewhere as a type, so it can be re-used more easily? socket.h?

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, although that makes it more opaque when reading socket.cpp.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You could argue that for every definition that isn't in line.

@softins softins requested a review from pljones August 6, 2021 22:38
@softins softins requested a review from ann0see August 8, 2021 10:42
Copy link
Member

@ann0see ann0see left a comment

Choose a reason for hiding this comment

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

Seems simple. Nevertheless some comments:

  1. You're removing the chinese .qm file? Any reason for that?
  2. See comments
  3. As it works on my ipv6 connection (and you tested it), I think the functionality is ok.

@@ -937,6 +955,7 @@ QString UsageArguments ( char** argv )
" -Q, --qos set the QoS value. Default is 128. Disable with 0\n"
" (see the Jamulus website to enable QoS on Windows)\n"
" -t, --notranslation disable translation (use English language)\n"
" -6, --enableipv6 enable IPv6 addressing (IPv4 is always enabled)\n"
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
" -6, --enableipv6 enable IPv6 addressing (IPv4 is always enabled)\n"
" -6, --enableipv6 enable IPv6 addressing for direct connection to client/server (IPv4 is always enabled).\n"

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this adds anything at all. It still requires further explanation as to what "direct connection to client/server" means, so it's useless verbosity.

Copy link
Member

Choose a reason for hiding this comment

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

But do we agree that the original proposal needs more clarification?

Copy link
Collaborator

@pljones pljones Aug 9, 2021

Choose a reason for hiding this comment

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

I'm happy with the wording Tony used. It's concise and says what it does.

Copy link
Member

Choose a reason for hiding this comment

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

I still think it's not clear. Probably the question is:
Does the user need to know that connections to public servers is still IPv4 only?
If we want to be specific: Yes. If we think it confuses the user: No.
Maybe it's worth mentioning it at least in the release announcement.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the user need to know that connections to public servers is still IPv4 only?

Not "public", use the word "registered". You can register with a directory no one knows about. That's not particularly public.

To operate the software, no. They can just select a listed entry from the directory and the software will use the returned IPv4 address.

If you want to use IPv6, you need to:
a) pass "-6" on the command line, thus indicating you understand how to operate with IPv6 addresses, so need no further help
b) enter an IPv6 name or address in the connection dialogue "Server Address", by hand, so you know that you've chosen a specific server rather than a registered one.

As I say, the help text need not say anything else. This is not the final form for the feature. It's intended to be somewhat experimental and the documentation should make that clear. Those who choose to use it should thus be given the credit for understanding what they've chosen to do.

src/serverlist.cpp Show resolved Hide resolved
src/serverlist.cpp Show resolved Hide resolved
if ( UdpSocket == -1 )
{
// IPv6 requested but not available, throw error
throw CGenErr ( "IPv6 requested but not available on this system.", "Network Error" );
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
throw CGenErr ( "IPv6 requested but not available on this system.", "Network Error" );
throw CGenErr ( "You requested IPv6, but it's not available with your system configuration.", "Network Error" );

As far as I know active voice is preferred to passive voice (https://jamulus.io/contribute/Style-and-Tone)

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 can't see any reference to active vs passive voice in that page, although I know some other wider contexts promote a preference for active voice. In this case, I have a slight preference for the shorter message.

Copy link
Member

Choose a reason for hiding this comment

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

I think @gilgongo mentioned it somewhere.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I halluciated it then - I recall @mulyaj mentioning it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I've mentioned a preference for active voice. Reading this at first glance, the subject is vague:

"IPv6 requested..." - who requested?

Then, if the subject is vague, the guideline for concise and specific is broken.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd be happy with "IPv6 is not available." It's issued as a response to the user making a request to use an address. It doesn't need to tell the user they used an address, just why it hasn't worked.

if ( UdpSocket == -1 )
{
// IPv4 requested but not available, throw error (should never happen, but check anyway)
throw CGenErr ( "IPv4 requested but not available on this system.", "Network Error" );
Copy link
Member

Choose a reason for hiding this comment

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

As above, but now with ipv4 ofc.

src/socket.cpp Show resolved Hide resolved
src/util.cpp Show resolved Hide resolved
@ann0see ann0see mentioned this pull request Aug 8, 2021
@ann0see ann0see mentioned this pull request Aug 8, 2021
@ann0see ann0see added this to the Release 3.8.1 milestone Aug 8, 2021
@ann0see
Copy link
Member

ann0see commented Aug 8, 2021

Since it's not enabled by default, I think we can tag it for 3.8.1.

@softins
Copy link
Member Author

softins commented Aug 8, 2021

Seems simple. Nevertheless some comments:

  1. You're removing the chinese .qm file? Any reason for that?

No, must be an accident. I'll correct that before merging.

  1. See comments

Thanks, will respond individually.

  1. As it works on my ipv6 connection (and you tested it), I think the functionality is ok.

Cool.

@ann0see
Copy link
Member

ann0see commented Aug 9, 2021

As soon as the Chinese qm file is added, I think it’s ready to merge.

Copy link
Member

@ann0see ann0see left a comment

Choose a reason for hiding this comment

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

Since it works and only one critical step is missing (.qm file), I‘ll approve it.

@softins softins force-pushed the ipv6 branch 2 times, most recently from 57fbd63 to 611e11a Compare August 9, 2021 21:02
@softins
Copy link
Member Author

softins commented Aug 9, 2021

I have squashed all the commits except for the first one by @jardous. I'll wait until the checks are complete (looks like they may need to be rerun) and merge tomorrow.

@ann0see
Copy link
Member

ann0see commented Aug 9, 2021

I think you should use @jardous E-Mail or jardous@users.noreply.github.com to link his GitHub account to the first commit. This would add him to the contributor entry on GitHub.

@softins
Copy link
Member Author

softins commented Aug 9, 2021

I think you should use @jardous E-Mail or jardous@users.noreply.github.com to link his GitHub account to the first commit. This would add him to the contributor entry on GitHub.

Not exactly sure how to do that.

@pljones
Copy link
Collaborator

pljones commented Aug 10, 2021

I think you should use @jardous E-Mail or jardous@users.noreply.github.com to link his GitHub account to the first commit. This would add him to the contributor entry on GitHub.

Not exactly sure how to do that.

To set the values:

git commit --author="Jiri Popek <jardous@users.noreply.github.com>" --date="2021-02-18T12:34:56" ...

You'd need to do that during either a git rebase -i or manually git reset --hard master and reapplying the commits.

jardous and others added 2 commits August 10, 2021 15:35
Note that it is backwards compatible with IPv4.

Co-authored-by: Tony Mountifield <tony@mountifield.org>
Reworked ParseNetworkAddress() to use regex to validate format.
Allows literal IP address (IP4 or IP6) between [ ],
or else IP4 address or hostname without [ ].
Optional :port follows.

Add code to determine routable IPv6 interface address.
Uses Cloudflare's IPv6 DNs resolver address (no traffic sent).

Use LocalHostIPv6 if no local IPv6 address

Add command option and flag for enabling IPv6

Reworked sockaddr handling in OnDataReceived()

Change in_port_t to uint16_t for Windows compatibility

Allow IPv4 only for communicating with Directory Servers

Rework dual-stack socket handling for Windows compatibility

Move sockaddr union definition into socket.h
@softins
Copy link
Member Author

softins commented Aug 10, 2021

I think you should use @jardous E-Mail or jardous@users.noreply.github.com to link his GitHub account to the first commit. This would add him to the contributor entry on GitHub.

OK, that seems to have worked. Just waiting for the Checks to run and then I'll merge.

@softins
Copy link
Member Author

softins commented Aug 10, 2021

Well although Android and Windows failed this time, they have previously worked with identical code (I diffed the before and after of the rebases to be sure), so I'll go ahead and merge anyway.

@softins softins merged commit 694473a into jamulussoftware:master Aug 10, 2021
@jardous
Copy link
Contributor

jardous commented Aug 10, 2021 via email

@softins
Copy link
Member Author

softins commented Aug 10, 2021

hi. Feel free to use my real email: @.*** J.

I already used your generic Github address jardous@users.noreply.github.com, which was enough to link the rebased version of your original commit to your Github ID. Thanks!

@softins
Copy link
Member Author

softins commented Aug 10, 2021

@ALL, please see #1950 for discussion of the technical approach to adding IPv6 support in Directory Servers.

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.

7 participants