First, you seem to confuse concepts here:
* SetKeepAlive() controls the in-kernel TCP stack's ability to sent
specifically crafted TCP packets over the connection to 1) try to keep
the connection from being dropped by the middleware NAT devices; 2)
proactively detect connection death.
In other words, this feature belongs in the TCP protocol and is
controlled by the kernel using appropriate knobs it provides for the
userspace.
* SetReadDeadline(), on the other hand, merely controls the timeout,
local to your Go process, for read operation on this particular
socket. The kernel is not involved with this in any way, and tracking
of this deadline time is done in the Go runtime scheduler which
manages events on the sockets.
Consequently, when no I/O happens on the socket before the deadline,
the Go runtime synthesizes an error which you observe as "i/o timeout".
On the other hand, "connection timed out", which is ETIMEDOUT errno
value in POSIX, is returned by the kernel's TCP stack and is merely
propagated up by the Go runtime to your code.
What's more, if you'll think about these errors and their causes a bit
more, you'll conclude that these errors are truly different
conceptually: a timeout on a TCP connection (as perceived by the
kernel's TCP stack) is not the same thing as the policy you impose in
your own program. Notice that these concepts are even named
differently: deadline, not timeout. Deadline means an artifical limit
you impose on some event to happen.