-
Notifications
You must be signed in to change notification settings - Fork 985
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
Select mailservers based on ping #9394
Comments
I'm not sure where this should be implemented though, |
Thanks to Eric I know where currently the selection code lives: |
This would even leave in status-protocol-go, in a separate package probably. We have two issues there: |
This must be the method that actually tells the node to connect to the mailserver: |
According to Adam the call to get fastest mailservers should be an RCP call. |
A request should send a list of mailservers to check, and possibly a timeout for the check, and return a list of objects with round trip time: [
{ addr: "A", rtt: 123 }, // milliseconds
{ addr: "B", rtt: null }, // timeout
{ addr: "C", rtt: 554 }, // milliseconds
] |
New RPC calls are exposed by adding methods here: |
Example interface for ICMP probing from Adam: package mailserver
type Prober interface {
Probe() ([]node.ID, error)
}
type ICMPProber struct {
items []node.ID
}
func NewICMPProber(addresses []string) *ICMPProber {
return &ICMPProber{items: parseAddresses(addresses)}
}
type ProtocolProber struct {
} |
At first I thought ICMP will be simplest, but it appears it is not because sending ICMP packets requires creating a raw socket which require special permissions:
And fails on Linux without
NOTE: This is true for Darwin as well. So the most sensible thing to do will be to do a TCP |
This package could be used as a template to write such a TCP handshake ping:
One thing that worries me though is that it's only for Linux, and their non-Linux code is a dummy: |
I tried reading the code of https://github.com/tevino/tcp-shaker but it's really obtuse. Syscalls verywhere, weird use of I could just use it, but |
MacOS lacks There's also no
But we don't intend to send any bytes, just close immediately, so it shouldn't have any effect. |
When I do those two changes listed above the only build errors appear to be related to lack of
So in theory I could port this code to use |
This is a nice example of how |
This might help me build a standalone binary for Android using Gomobile:
This way I could verify if this TCP knocking will even work. |
I made an example App using the package main
import (
"context"
"log"
"time"
"github.com/tevino/tcp-shaker"
"golang.org/x/mobile/app"
)
var addresses = []string{
"206.189.243.162:443",
"35.188.19.210:443",
"47.91.156.93:443",
}
func Ping(address string, timeout time.Duration) error {
c := tcp.NewChecker()
ctx, stopChecker := context.WithCancel(context.Background())
defer stopChecker()
go func() {
if err := c.CheckingLoop(ctx); err != nil {
log.Print("checking loop stopped due to fatal error: ", err)
}
}()
log.Print("Pinging:", address)
<-c.WaitReady()
err := c.CheckAddr(address, timeout)
switch err {
case tcp.ErrTimeout:
log.Print("Connect timed out")
case nil:
log.Print("Connect succeeded")
default:
log.Print("Error while connecting: ", err)
}
return nil
}
func main() {
app.Main(appMain)
}
func appMain(a app.App) {
log.SetPrefix("TCP_PING: ")
var timeout time.Duration = 1000 * time.Millisecond
var i int
for i = 0; i < len(addresses); i++ {
err := Ping(addresses[i], timeout)
if err != nil {
log.Print("Err:", err)
}
}
} And it fails on Android with this:
Which I guess suggests that Android blocks use of some of the socket flags that |
If you look at the |
I tested the ICMP ping using
Similar thing if I try to use an UDP ping:
Both fail on the So it doesn't look like ICMP ping is something that can be done on Android without extra permissions. |
I narrowed down the syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0) It seems like one of those flags is not allowed. But which one? |
GLORIOUS SUCCESS! <uses-permission android:name="android.permission.INTERNET" /> Looks like the default is not to give network access to an app. |
It doesn't seem like the adding of
|
Interestingly enough syscall.SetsockoptInt(fd, syscall.SOL_TCP, syscall.TCP_QUICKACK, 0) |
The timeout happens even through the event.Events = syscall.EPOLLOUT | syscall.EPOLLIN | epollET Where const epollET = 1 << 31 // 2147483648 Which shifts first bit 31 places left, and gives us
Which is the negative of The
|
I tried using
So I assume the local definition of |
I don't get why the value of type EpollEvent struct {
Events uint32
Fd int32
Pad int32
} https://golang.org/pkg/syscall/#EpollEvent |
Interestingly the
|
Instead of relying on the pipe that the default events := make([]syscall.EpollEvent, 1)
nc, err := syscall.EpollWait(c.PollerFd(), events, int(timeout.Milliseconds())) And what I get back is an event with |
Turns out that func pollEvents(pollerFd int, timeout time.Duration) ([]event, error) {
var timeoutMS = int(timeout.Nanoseconds() / 1000000)
var epollEvents [maxEpollEvents]syscall.EpollEvent
nEvents, err := syscall.EpollWait(pollerFd, epollEvents[:], timeoutMS)
if err != nil {
if err == syscall.EINTR {
return nil, nil
}
return nil, os.NewSyscallError("epoll_wait", err)
}
var events = make([]event, 0, nEvents)
for i := 0; i < nEvents; i++ {
var fd = int(epollEvents[i].Fd)
var evt = event{Fd: fd, Err: nil}
errCode, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ERROR)
if err != nil {
evt.Err = os.NewSyscallError("getsockopt", err)
}
if errCode != 0 {
evt.Err = newErrConnect(errCode)
}
events = append(events, evt)
}
return events, nil
} I need to debug why this call doesn't catch the socket being ready. |
This issue appears to be somehow related: golang/go#32192 |
I have opened an issue with the Go repo: golang/go#35479 |
Also created a PR for |
It looks like as long as I call But it also means that |
The issue with closing the connection right after I open it is that currently I don't know how I can actually check if it succeeded or not. I'm probably missing something, and maybe some combination of flags for |
Okay, I managed to get it working, but not exactly. The key was using the right eventFilter := unix.Kevent_t{
Ident: uint64(fd),
Filter: unix.EVFILT_WRITE,
Flags: unix.EV_ADD | unix.EV_ONESHOT,
} The implementation was done in: status-im/tcp-shaker@804f4fa ( But the issue is that it doesn't send the |
This article was very useful in figuring this shit out: |
@adambabik in your opinion, would |
Here's an initial implementation of the TCP ping RCP call in $ curl -s localhost:8545 -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","method":"mailservers_ping","params":[{"addresses":["1.1.1.1:53", "8.8.8.8:53", "1.1.1.1:231", "1.1.1.1:313121"], "timeoutMs": 500}],"id":1}' {
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"address": "1.1.1.1:313121",
"latency": -1,
"error": "unknown error: address 313121: invalid port"
},
{
"address": "1.1.1.1:53",
"latency": 7,
"error": null
},
{
"address": "8.8.8.8:53",
"latency": 9,
"error": null
},
{
"address": "1.1.1.1:231",
"latency": -1,
"error": "tcp check timeout: I/O timeout"
}
]
} |
I've finally merged the @yenda you can see how the RPC call should be used here: The call should be available as long as you use a version from @errorists what do you think about modifying the mailserver list to include some kind of indicator(color dot or something else) to show which mailservers are available/unavailable/fastest? This new call returns a list with RTTs(round trip times) or possible errors if they are unavailable, so we can give users some feedback on what works and what doesn't. |
as a side effect, all clients will be constantly broadcasting their IP to the list of available mailservers. |
I don't know about constantly. I guess only at login, and when a connection with current one fails, or when a user triggers a check. I don't think it should be periodic at all. |
as long as at the moment of choosing, the client has relatively refreshed response times, otherwise they may make a decision on stale data, leading to poor UX |
this will become more necessary as the available choices move outside of our control and community based. |
True fact, but that would mean it's necessary only when user opens the mailserver list view, otherwise a refresh of that data is only necessary when a connection to previously used mailserver fails. |
@jakubgs @corpetty the way I'm implementing it right now it uses the method whenever it needs to connect to a mailserver if the user didn't pin one already. This means that it does it at login, whenever going back online after being offline, and when removing a custom mailserver (if there is no selected mailserver). This will be superseeded soon by waku node anyway. |
@jakubgs you asked about the UI, so this is how I imagine it would look like. Split into two lists, one for recommended and other for everything else, each mailserver lists response time with relative ranges like Fast - Average - Slow - No response. This would leave any ambiguity out. For kicks on the left is how it currently looks like 🤢 |
User Story
Currently the mailservers from the fleet are selected randomly, which results in me getting connected to the Hong Kong mailservers almost every time when I'm in Europe and should be getting the european ones.
Description
This results in a really bad experience for users with slow sync times and bad latency.
Acceptance Criteria
The simplest solution would be to just do an ICMP ping to a few mailserver, group them into response time buckets, and pick a random one from the fastest bucket.
Notes
Example ping output:
As you can see for me from Poland the ranking of access time is simple:
29.1 ms
127 ms
279 ms
The text was updated successfully, but these errors were encountered: