I wrote a simple TCP echo server and UDP echo server in Golang.
The main goal is to expose these two services behind envoyproxy.
Below the configuration I have written:
compose.yaml
services:
envoy:
image: envoyproxy/envoy:dev
ports:
- "10000:10000"
- "9001:9001"
- "9002:9002"
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
- ./cds.yaml:/var/lib/envoy/cds.yaml
- ./lds.yaml:/var/lib/envoy/lds.yaml
echo-tcp:
build:
context: .
dockerfile: Dockerfile-echo-tcp
ports:
- 8081:8081
volumes:
- .:/tmp
echo-udp:
build:
context: .
dockerfile: Dockerfile-echo-udp
ports:
- 8082:8082
volumes:
- .:/tmp
envoy.yaml
node:
cluster: civo-envoy-cluster
id: civo-id
dynamic_resources:
cds_config:
resource_api_version: V3
path: /var/lib/envoy/cds.yaml
lds_config:
resource_api_version: V3
path: /var/lib/envoy/lds.yaml
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 10000
cds.yaml
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: civo_tcp_cluster
load_assignment:
cluster_name: civo_tcp_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 0.0.0.0
port_value: 8081
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: civo_udp_cluster
load_assignment:
cluster_name: civo_udp_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 0.0.0.0
port_value: 8082
lds.yaml
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: listener_tcp
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9001
filter_chains:
- filters:
- name: filter_tcp
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
cluster: civo_tcp_cluster
stat_prefix: civo_stat_tcp
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: listener_udp
address:
socket_address:
protocol: UDP
address: 0.0.0.0
port_value: 9002
filter_chains:
- filters:
- name: filter_udp
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig
cluster: civo_udp_cluster
stat_prefix: civo_stat_udp
listener_filters:
- name: envoy.filters.udp_listener.udp_proxy
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig
stat_prefix: civo_stat_udp
matcher:
on_no_match:
action:
name: udp_route
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route
cluster: civo_udp_cluster
Dockerfile-echo-tcp
FROM golang:1.22.2 as builder
WORKDIR /opt
COPY tcp/tcp.go .
COPY tcp/go.mod .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o tcp tcp.go
FROM scratch
WORKDIR /opt
COPY --from=builder /opt/tcp .
ENTRYPOINT [ "/opt/tcp" ]
EXPOSE 8081
Dockerfile-echo-udp
FROM golang:1.22.2 as builder
WORKDIR /opt
COPY udp/udp.go .
COPY udp/go.mod .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o udp udp.go
FROM scratch
WORKDIR /opt
COPY --from=builder /opt/udp .
ENTRYPOINT [ "/opt/udp" ]
EXPOSE 8082
tcp.go
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
ln, err := net.Listen("tcp", ":8081")
if err != nil {
fmt.Fprintf(os.Stderr, "Listen(): %sn", err)
os.Exit(1)
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Fprintf(os.Stderr, "Accept(): %sn", err)
continue
}
reader, writer := bufio.NewReader(conn), bufio.NewWriter(conn)
go func() {
buffer := make([]byte, 1024)
reader.Read(buffer)
writer.Write(buffer)
}()
}
}
udp.go
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:8082")
ln, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Fprintf(os.Stderr, "Listen(): %sn", err)
os.Exit(1)
}
reader, writer := bufio.NewReader(ln), bufio.NewWriter(ln)
buffer := make([]byte, 1024)
for {
reader.Read(buffer)
writer.Write(buffer)
}
}
envoy.logs
envoy-1 | [2024-06-07 09:51:14.035][37][debug][filter] [source/common/tcp_proxy/tcp_proxy.cc:264] [Tags: "ConnectionId":"1"] new tcp proxy session
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:401] [Tags: "ConnectionId":"1"] readDisable: disable=true disable_count=0 state=0 buffer_length=0
envoy-1 | [2024-06-07 09:51:14.035][37][debug][filter] [source/common/tcp_proxy/tcp_proxy.cc:459] [Tags: "ConnectionId":"1"] Creating connection to cluster civo_tcp_cluster
envoy-1 | [2024-06-07 09:51:14.035][37][debug][misc] [source/common/upstream/cluster_manager_impl.cc:2269] Allocating TCP conn pool
envoy-1 | [2024-06-07 09:51:14.035][37][debug][pool] [source/common/conn_pool/conn_pool_base.cc:291] trying to create new connection
envoy-1 | [2024-06-07 09:51:14.035][37][trace][pool] [source/common/conn_pool/conn_pool_base.cc:292] ConnPoolImplBase 0x25fc7fc2a280, ready_clients_.size(): 0, busy_clients_.size(): 0, connecting_clients_.size(): 0, connecting_stream_capacity_: 0, num_active_streams_: 0, pending_streams_.size(): 1 per upstream preconnect ratio: 1
envoy-1 | [2024-06-07 09:51:14.035][37][debug][pool] [source/common/conn_pool/conn_pool_base.cc:145] creating a new connection (connecting=0)
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:1017] [Tags: "ConnectionId":"2"] connecting to 0.0.0.0:8081
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:1036] [Tags: "ConnectionId":"2"] connection in progress
envoy-1 | [2024-06-07 09:51:14.035][37][trace][pool] [source/common/conn_pool/conn_pool_base.cc:131] not creating a new connection, shouldCreateNewConnection returned false.
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:469] [Tags: "ConnectionId":"1"] raising connection event 2
envoy-1 | [2024-06-07 09:51:14.035][37][trace][filter] [source/common/tcp_proxy/tcp_proxy.cc:806] [Tags: "ConnectionId":"1"] on downstream event 2, has upstream = false
envoy-1 | [2024-06-07 09:51:14.035][37][debug][conn_handler] [source/common/listener_manager/active_tcp_listener.cc:160] [Tags: "ConnectionId":"1"] new connection from 172.20.0.1:35208
envoy-1 | [2024-06-07 09:51:14.035][37][trace][misc] [source/common/network/tcp_listener_impl.cc:114] TcpListener accepted 1 new connections.
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:614] [Tags: "ConnectionId":"1"] socket event: 2
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:737] [Tags: "ConnectionId":"1"] write ready
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:614] [Tags: "ConnectionId":"2"] socket event: 3
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:737] [Tags: "ConnectionId":"2"] write ready
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:757] [Tags: "ConnectionId":"2"] delayed connect error: 111
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:276] [Tags: "ConnectionId":"2"] closing socket: 0
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:469] [Tags: "ConnectionId":"2"] raising connection event 0
envoy-1 | [2024-06-07 09:51:14.035][37][debug][pool] [source/common/conn_pool/conn_pool_base.cc:495] [Tags: "ConnectionId":"2"] client disconnected, failure reason: delayed connect error: 111
envoy-1 | [2024-06-07 09:51:14.035][37][debug][filter] [source/common/tcp_proxy/tcp_proxy.cc:459] [Tags: "ConnectionId":"1"] Creating connection to cluster civo_tcp_cluster
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:150] [Tags: "ConnectionId":"1"] closing data_to_write=0 type=1
envoy-1 | [2024-06-07 09:51:14.035][37][debug][connection] [source/common/network/connection_impl.cc:276] [Tags: "ConnectionId":"1"] closing socket: 1
envoy-1 | [2024-06-07 09:51:14.035][37][trace][connection] [source/common/network/connection_impl.cc:469] [Tags: "ConnectionId":"1"] raising connection event 1
envoy-1 | [2024-06-07 09:51:14.035][37][trace][filter] [source/common/tcp_proxy/tcp_proxy.cc:806] [Tags: "ConnectionId":"1"] on downstream event 1, has upstream = false
envoy-1 | [2024-06-07 09:51:14.035][37][trace][conn_handler] [source/common/listener_manager/active_stream_listener_base.cc:126] [Tags: "ConnectionId":"1"] tcp connection on event 1
envoy-1 | [2024-06-07 09:51:14.035][37][debug][conn_handler] [source/common/listener_manager/active_stream_listener_base.cc:136] [Tags: "ConnectionId":"1"] adding to cleanup list
envoy-1 | [2024-06-07 09:51:14.035][37][trace][main] [source/common/event/dispatcher_impl.cc:228] item added to deferred deletion list (size=1)
envoy-1 | [2024-06-07 09:51:14.035][37][trace][main] [source/common/event/dispatcher_impl.cc:228] item added to deferred deletion list (size=2)
envoy-1 | [2024-06-07 09:51:14.035][37][trace][pool] [source/common/conn_pool/conn_pool_base.cc:131] not creating a new connection, shouldCreateNewConnection returned false.
envoy-1 | [2024-06-07 09:51:14.035][37][trace][main] [source/common/event/dispatcher_impl.cc:228] item added to deferred deletion list (size=3)
envoy-1 | [2024-06-07 09:51:14.035][37][debug][pool] [source/common/conn_pool/conn_pool_base.cc:463] invoking 1 idle callback(s) - is_draining_for_deletion_=false
envoy-1 | [2024-06-07 09:51:14.035][37][trace][upstream] [source/common/upstream/cluster_manager_impl.cc:2151] Idle pool, erasing pool for host 0.0.0.0:8081
envoy-1 | [2024-06-07 09:51:14.035][37][trace][main] [source/common/event/dispatcher_impl.cc:228] item added to deferred deletion list (size=4)
envoy-1 | [2024-06-07 09:51:14.036][37][trace][main] [source/common/event/dispatcher_impl.cc:122] clearing deferred deletion list (size=4)
The connection was made with netcat
with nc -4 127.0.0.1 9001
The UDP datagram was sent with netcat
with echo 'Hello from netcat!' | nc -4 -u 127.0.0.1 9002
but nothing is logged
What I am doing wrong ?
I am expecting that when a TCP connection is made and I send a payload, the payload is echoed back.
Same behaviour for UDP, datagrams must be echoed back.
Gianluca