schollz / croc

Easily and securely send things from one computer to another :crocodile: :package:
https://schollz.com/software/croc6
MIT License
28.1k stars 1.11k forks source link

Further IPv6 improvements for dual-stack systems #229

Closed lpar closed 7 months ago

lpar commented 4 years ago

The IPv6 patch should fix pure-IPv6 use without breaking pure-IPv4. However, it's not quite the full story, as there are some additional improvements needed to deal better with dual-stack hosts.

Improving address advertisement

Right now, croc calls https://canhazip.com. For a dual-stack host, this will likely result in an IPv6 address. (It does for me), hence an IPv6 address will be sent to the relay. This will mean that an IPv4-only receiver will be unable to reach the sender using the address obtained from the relay, even though the sender has a viable IPv4 address. I think that everything will then get transported via the relay and still work, but it's sub-optimal.

Instead, the sender should use both https://ipv4.canhazip.com and https://ipv6.canhazip.com to look up both a v4 and a v6 address, and send both to the relay. The receiver should then try both.

Improving localhost and private network handling

In addition, there are more local network address options with an IPv6-capable system. So a dual-stack machine will have:

  1. Two localhost addresses, 127.0.0.1 and ::1
  2. Possibly one or more IPv4 private network addresses such as 10.0.1.123 or 192.168.1.5.
  3. Possibly a public IPv4 address.
  4. Possibly an IPv6 local unicast address in the FC00::/7 range.
  5. One or more temporary public IPv6 addresses assigned and rotated periodically for privacy reasons.
  6. A global public IPv6 address.
  7. A link-local IPv6 address starting fe80::.

2-6 should probably all be advertised as possible ways to connect to the sender, and the receiver should probably run through them in that order of preference. I haven't looked closely enough at the relay handling to see how hard that might be, but I assume since it already handles cases 2 and 3 that it should just mean updating the sender code to look up and send the extra addresses. If you could confirm that, I'll take a look at putting together a pull request.

schollz commented 4 years ago

@lpar thanks for tracking this issue!

I think a lot of things are already in place. Two comments for you:

One - "icanhazip.com" is not used by the program - it's only for testing purposes.

Second - the local network discovery is automatically handled by peerdiscovery, and the address is determined from the connection to the broadcaster. Also, peerdiscovery already has ipv6 support that is exposed via the options. So, getting LAN to work with ipv6 might be as simple as adding a goroutine in croc to run peerdiscovery with ipv6 enabled to see if it can discover ipv6 local connections in addition to ipv4 local connections.

Just to be clear - you're seeing ipv6 working on non-LAN?

Also, let me know if you have any questions. I put together a PR too but more than happy to accept one if you want to add it! I could use your help especially since I have trouble testing ipv6 systems.

schollz commented 4 years ago

I don't know why I can't get ipv6 to work even in docker.

Here's peerdiscovery: https://github.com/schollz/peerdiscovery/tree/master/examples/ipv6, which should work with ipv6.

I configured Docker to use ipv6 and then do

> make build

and then in two separate terminals I do:

> make run

but no peers are discovered :(

I think its an issue with my computer, as others have used it successfully.

lpar commented 4 years ago

With two actual machines (my Mac and a Synology box), the peerdiscovery ipv6 example works for me and finds 6 link-local addresses. The Synology box even finds both link-local addresses for my laptop, one via each ethernet connection.

I believe Docker requires some special configuration to enable IPv6. I've only really used CentOS and podman for containers.

Another option would be to use AWS's free tier to spin up a couple of t2.micro instances and set up IPv6 on them.

lpar commented 4 years ago

My tests were with globally routable IPv6 addresses, but I didn't actually make sure the systems were on separate networks. Other than firewalls, it shouldn't make a difference that they weren't.

I didn't try link-local IPv6 addresses as I thought it was unlikely the code would handle them, and in general the LAN should do the right thing if you address machines by their global addresses. If I traceroute6 another machine by its 2605: address, the packets get there in 1 hop across the LAN.

schollz commented 4 years ago

Okay, cool. That is great info! I will look into AWS for my own ipv6 testing

schollz commented 4 years ago

@lpar okay, so I just quickly added in the LAN search for connecting to ipv6 clients. I just tested it in my LAN between a Linux host and Windows receiver and imagine my surprise:

[debug] 14:56:21 croc.go:737: connecting to [fe80::xx]:9013
[debug] 14:56:21 croc.go:737: connecting to [fe80::xx]:9012
[debug] 14:56:21 croc.go:737: connecting to [fe80::xx]:9010
[debug] 14:56:21 croc.go:737: connecting to [fe80::xx]:9011

it works! I didn't realize I even had ipv6 :\

Could you check out the latest master and see it works for you?

lpar commented 4 years ago

You'll have a link-local IPv6 address (fe80:) by default, whether or not you have an IPv6 Internet connection, as long as IPv6 isn't explicitly disabled. The address will be allocated by stateless autoconfig, so your DHCP server, router and cable modem don't need to know about it.

You might also try pinging 192.88.99.1 to see if your ISP provides an undocumented 6to4 tunnel at the anycast address as per RFC 3068. If so you can set up Linux to use that, and get a real IPv6 address. Windows is supposed to do so by default.

Just tried the latest commit, and with no IPv4 on the receiver and a dual-stack sender, I get a crash on the sender:

[error] 2020/08/26 17:29:26 croc.go:733: bad relay address localhost
[error] 2020/08/26 17:29:26 croc.go:733: bad relay address localhost
[error] 2020/08/26 17:29:26 croc.go:733: bad relay address localhost
[error] 2020/08/26 17:29:26 croc.go:733: bad relay address localhost

Sending (->[fe80::10e1:1e0:8d10:47f5%eth0]:53057)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6035f9]

goroutine 16 [running]:
github.com/schollz/croc/v8/src/comm.(*Comm).Write(0x0, 0xc000476000, 0x802e, 0xa000, 0x20, 0x20, 0xc000476000)
    /Users/meta/Programming/Go/src/github.com/schollz/croc/src/comm/comm.go:69 +0x1b9
github.com/schollz/croc/v8/src/comm.(*Comm).Send(...)
    /Users/meta/Programming/Go/src/github.com/schollz/croc/src/comm/comm.go:143
github.com/schollz/croc/v8/src/croc.(*Client).sendData(0xc0000ae780, 0x0)
    /Users/meta/Programming/Go/src/github.com/schollz/croc/src/croc/croc.go:1308 +0x466
created by github.com/schollz/croc/v8/src/croc.(*Client).updateState
    /Users/meta/Programming/Go/src/github.com/schollz/croc/src/croc/croc.go:1160 +0x354

The other way around, no IPv4 on the sender and a dual-stack receiver, I get a different error:

connecting...[error]    2020/08/26 17:44:11 croc.go:531: ips unmarshal error: invalid character '\x01' looking for beginning of value
securing channel...[error]  2020/08/26 17:44:12 compress.go:46: error copying data: unexpected EOF
problem with decoding: unexpected end of JSON input

With both dual stack, it now tries to receive the file from localhost via IPv6 and fails:

Accept 'irc.zip' (31.2 MB)? (y/n) y

Receiving (<-[::1]:47337)
lpar commented 4 years ago

Trying again with debug enabled:

[debug] 18:06:56 croc.go:284: banner: 
[debug] 18:06:56 croc.go:287: could not connect to localhost:: dial tcp [::1]:0: connect: can't assign requested address

I think that's the problem, somehow it's ending up trying to open port zero on the sender to listen, and failing.

lpar commented 4 years ago

OK, found the cause of that: net.SplitHostPort returns an error if there's no port to split off.

@@ -728,10 +728,15 @@ func (c *Client) procesMessagePake(m message.Message) (err error) {
      log.Debugf("port: [%s]", c.Options.RelayPorts[i])
      go func(j int) {
        defer wg.Done()
-       host, _, err := net.SplitHostPort(c.Options.RelayAddress)
-       if err != nil {
-         log.Errorf("bad relay address %s", c.Options.RelayAddress)
-         return
+       var host string
+       if c.Options.RelayAddress == "localhost" {
+         host = c.Options.RelayAddress
+       } else {
+         host, _, err = net.SplitHostPort(c.Options.RelayAddress)
+         if err != nil {
+           log.Errorf("bad relay address %s", c.Options.RelayAddress)
+           return
+         }
        }
        server := net.JoinHostPort(host, c.Options.RelayPorts[j])
        log.Debugf("connecting to %s", server)

Or if RelayAddress can never have a port attached, you could just get rid of the SplitHostPort entirely.

With that change, it now uses the link-local IPv6 addresses, but gives some (apparently harmless) errors at the end, presumably because the sender closed the connections already:

[debug] 19:21:13 croc.go:618: got error processing: wanted to write 54 but wrote 0: write tcp [fe80::211:32ff:fe6a:f450%eth1]:55935->[fe80::10e1:1e0:8d10:47f5%eth1]:9009: write: broken pipe
[debug] 19:21:13 croc.go:628: purging error: wanted to write 54 but wrote 0: write tcp [fe80::211:32ff:fe6a:f450%eth1]:55935->[fe80::10e1:1e0:8d10:47f5%eth1]:9009: write: broken pipe
schollz commented 4 years ago

Yeah, those are harmless. So its transferring files now over ipv6 with that patch?

lpar commented 4 years ago

Yes, at least for LAN-connected systems. Needs some testing across the Internet next, I guess.

schollz commented 4 years ago

Great patch, merged in! Great stuff overall, thanks so much @lpar

makew0rld commented 4 years ago

Should this be reopened? I see that IPv6 was disabled in the most recent release, v8.5.2. See commit b66cfb4.

lpar commented 4 years ago

Hmm, I'd be interested to know what the issues are that local IPv6 discovery causes.

schollz commented 4 years ago

The IPv6 discovery works great. The latest commit (https://github.com/schollz/croc/commit/b66cfb4cc1dfe8e1c72b832623dc110035d4b88e) just disables ipv6 local discovery for the time being.

For some reason I am consistently getting errors with connecting to the discovered ipv6 address. The ipv6 discovery works but there is something going wrong with transfering the files over the discovered address. It could be a multitude of things:

An easy fix is to make a ipv4 local discovery fallback automatic. But an even easier fix is to just disable it for now until I figure out whats going on. I use croc a lot and wanted to get rid of this error for the meantime so I just disabled it for the time being.

makew0rld commented 4 years ago

Why not just prefer IPv4 connections when both are available? Am I missing something?

lpar commented 4 years ago

At first glance it doesn't look as if schollz/peerdiscovery is implementing neighbor advertisement validation and Neighbor Unreachability Detection as detailed in RFC 4861. It's possible that validation would eliminate the "bad" advertised addresses from consideration, and also apparently after receiving a neighbor advertisement you're supposed to send a followup neighbor discovery packet unicast to the advertised address, and see if you get a response before using it for anything else.

makew0rld commented 4 years ago

Looks like it was re-enabled in v8.5.3, see ea0e334. @schollz What was fixed to allow it to be enabled? I don't see any relevant change elsewhere.

github-actions[bot] commented 7 months ago

Stale issue message