How to Leverage Go for Your Networking Needs
- 56. How do we read a packet?
How do we read a frame?
- 57. raw socket: a socket that allows sending
and receiving of data without
protocol-specific transport layer
formatting
- 58. raw socket: IP layer (3
socket(AF_INET,SOCK_RAW,protocol)
packet socket: link layer (2
socket(AF_PACKET,SOCK_RAW,protocol)
- 69. Building a layer-7 LB
1. Use HTTP protocol.
2. Accept client-side connections.
3. Pass client-side request to one of the
backends.
4. Return server-response back to the client.
- 70. HTTP
HyperText Transfer
Protocol
An application layer (7
protocol:
● request-response based
● stateless
● Has different methods
GET, PUT, etc)
● Uses TCP or UDP sockets
under-the-hood
network definition
Source:
https://www.ntu.edu.sg/home/ehchua/programming/webprogramming/HTTP_Basic
s.html
- 72. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
n := rand.Intn(len(backends))
r.URL.Host = backends[n]
r.URL.Scheme = "https"
req, err := http.NewRequest(r.Method, r.URL.String(), r.Body)
...
res, err := client.Do(req)
...
}
2. Read and send request to
backend
- 73. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
...
w.WriteHeader(res.StatusCode)
_, err = io.Copy(w, res.Body)
if err != nil {
log.Printf("error writing response to client: %v", err)
}
...
}
3. Return response to client
- 75. Building a layer-4 LB
1. Use TCP protocol (via a streaming
socket).
2. Accept client-side connections.
3. Open backend connection.
4. Use one goroutine to shuttle packets from
the client to the backend.
5. Use another goroutine to shuttle packets
from the backend to the client.
- 76. TCP Transmission
Control Protocol
Connection-oriented protocol
with 3 phases:
● Connection establishment
TCP handshake)
● Data transfer
● Connection termination
network definition
https://www.researchgate.net/figure/Three-way-Handshake-in-TCP-Connection-
Establishment-Process_fig7_313951935
- 79. listener, err := net.Listen("tcp","127.0.0.1:9090")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("error accepting client conn: %v", err)
continue
}
go handleConn(conn)
}
1. Open and bind client TCP socket
- 80. func handleConn(clientConn net.Conn) {
n := rand.Intn(len(backends))
backendConn, err := net.Dial("tcp", backends[n])
if err != nil {
log.Printf("error %s: %v", backends[n], err)
return
}
...
}
2. Open and connect backend TCP
socket
- 81. var g run.Group
{
g.Add(func() error {
return copy(clientConn, backendConn)
}, func(error) {
clientConn.Close()
backendConn.Close()
})
}
{
g.Add(func() error {
return copy(backendConn, clientConn)
}, ...)
}
err = g.Run()
3. Configure rungroups
- 82. func copy(from net.Conn, to net.Conn) error {
for {
readBytes := make([]byte, 1024)
n, err := from.Read(readBytes)
if err != nil {
...
}
if _, err = to.Write(readBytes[:n]); err != nil {
...
}
}
}
4. Read and copy
- 83. Loadbalancer IRL
● Exact mechanism of handling connections and/or requests
depends on the layer.
● Layer-4 loadbalancers should be more efficient:
○ Won’t necessarily create a new backend connection per
client conn.
○ Use NAT and munging incoming packets.
● Better loadbalancing algorithms.
● Client stickiness.
- 87. Building a TCP port-scanner
1. Select range of ports to scan.
2. Try to open and connect a TCP socket to a
remote address (and port)..
3. Print results.
- 88. addr := fmt.Sprintf("%s:%d", hostname, port)
conn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
fmt.Printf("port %d closed: %vn", port, err)
} else {
fmt.Printf("port %d openn", port)
conn.Close()
}
1. Open and connect TCP socket
- 89. conSema := make(chan struct{}, 10)
var wg sync.WaitGroup
for i := 1; i <= 65535; i++ {
wg.Add(1)
conSema <- struct{}{}
go func(port int) {
...
wg.Done()
<-conSema
}(i)
}
wg.Wait()
2. Use waitgroup and channels
- 90. Nmap IRL
● Checks UDP and TCP ports on local or remote
hosts.
● Host discovery
● OS detection
● Auditing security of a firewall
- 94. DHCP Protocol
● Dynamic Host Configuration
Protocol is used by routers to
allocate IP addresses to
network interfaces
● DHCPv6 uses NDP and
DHCPv4 uses ARP
network definition
https://study-ccna.com/dhcp-dns/
- 96. Building a DHCP server:
1. Open and bind a raw socket to an interface.
2. Read data from socket into bytes buffer.
3. Unmarshal into DHCP message and retrieve
sender hardware address.
4. Switch between handlers based on message
type.
5. Validate DHCP request message and craft
response.
6. Unicast response back to sender.
- 98. GO FEATURE
filename := "eth-packet-socket"
typ := unix.SOCK_RAW
if cfg.LinuxSockDGRAM {
filename = "packet-socket"
typ = unix.SOCK_DGRAM
}
sock, err := unix.Socket(unix.AF_PACKET, typ, 0)
...
f := os.NewFile(uintptr(sock), filename)
sc, err := f.SyscallConn()
...
n, addr, err = unix.Recvfrom(int(fd), p, flags)
github.com/mdlayher/raw/raw_linux.go
- 99. GO FEATURE
// Try to find an available BPF device
for i := 0; i <= 10; i++ {
bpfPath := fmt.Sprintf( "/dev/bpf%d", i)
f, err = os.OpenFile(bpfPath, os.O_RDWR, 0666)
if err == nil {
break
}
if perr, ok := err.(*os.PathError); ok {
if perr.Err.(syscall.Errno) == syscall.EBUSY
{
continue
}
}
return nil, err
}
github.com/mdlayher/raw/raw_bsd.go
- 101. ifi, err := net.InterfaceByName(iface)
if err != nil {
return nil, err
}
pc, err := raw.ListenPacket(ifi, uint16(ethernet.EtherTypeIPv4),
&raw.Config{
// Don't set any timeouts to avoid syscall busy
// loops, since this server will run forever anyway.
NoTimeouts: true,
})
1. Open and bind raw socket
- 102. b := make([]byte, 1500)
for {
n, from, err := s.pc.ReadFrom(b)
if err != nil {
...
continue
}
buf := make([]byte, n)
copy(buf, b)
workC <- request{
buf: buf,
from: from,
}
}
2. Read data into buffer
- 104. func (h *handler) serveDHCPv4(ctx context.Context, req
*dhcp4.Packet, from *dhcp4conn.Addr) (*dhcp4.Packet, net.Addr)
{
...
if !from.EthernetSource.Equal(reqMAC) {
...
return h.shouldNAK(req.Type, to, broadcast)
}
...
}
4. Validate request
- 105. func (h *handler) serveDHCPv4(ctx context.Context, req
*dhcp4.Packet, from *dhcp4conn.Addr) (*dhcp4.Packet, net.Addr) {
...
res, err := h.buildResponse(ctx, params, req, from)
if err != nil {
topError(span, err, "failed to build response")
return h.shouldNAK(req.Type, to, broadcast)
}
res.Broadcast = broadcast
return res, to
...
}
5. Build and send response
- 106. DHCP Summary:
1. DHCP dynamic host configuration
protocol used to dynamically assign IP
address.
2. Use raw sockets.
3. Jump down to link-layer to do source MAC
validation.
- 108. Go can be good for building
networking primitives.
- 117. Sources
● Port Scanner: https://github.com/si74/portscanner
● Layer 7 loadbalancer: https://github.com/si74/layer7lb
● Layer 4 loadbalancer: https://github.com/si74/tcpproxy
● Raw package: https://github.com/mdlayher/raw
● Godocs: https://godoc.org/
● Golang: https://godoc.org/