mirror of
https://github.com/fosrl/newt.git
synced 2025-05-13 05:30:39 +01:00
202 lines
5.2 KiB
Go
202 lines
5.2 KiB
Go
package network
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/vishvananda/netlink"
|
|
"golang.org/x/net/bpf"
|
|
"golang.org/x/net/ipv4"
|
|
)
|
|
|
|
const (
|
|
udpProtocol = 17
|
|
// EmptyUDPSize is the size of an empty UDP packet
|
|
EmptyUDPSize = 28
|
|
timeout = time.Second * 10
|
|
)
|
|
|
|
// Server stores data relating to the server
|
|
type Server struct {
|
|
Hostname string
|
|
Addr *net.IPAddr
|
|
Port uint16
|
|
}
|
|
|
|
// PeerNet stores data about a peer's endpoint
|
|
type PeerNet struct {
|
|
Resolved bool
|
|
IP net.IP
|
|
Port uint16
|
|
NewtID string
|
|
}
|
|
|
|
// GetClientIP gets source ip address that will be used when sending data to dstIP
|
|
func GetClientIP(dstIP net.IP) net.IP {
|
|
routes, err := netlink.RouteGet(dstIP)
|
|
if err != nil {
|
|
log.Fatalln("Error getting route:", err)
|
|
}
|
|
return routes[0].Src
|
|
}
|
|
|
|
// HostToAddr resolves a hostname, whether DNS or IP to a valid net.IPAddr
|
|
func HostToAddr(hostStr string) *net.IPAddr {
|
|
remoteAddrs, err := net.LookupHost(hostStr)
|
|
if err != nil {
|
|
log.Fatalln("Error parsing remote address:", err)
|
|
}
|
|
|
|
for _, addrStr := range remoteAddrs {
|
|
if remoteAddr, err := net.ResolveIPAddr("ip4", addrStr); err == nil {
|
|
return remoteAddr
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetupRawConn creates an ipv4 and udp only RawConn and applies packet filtering
|
|
func SetupRawConn(server *Server, client *PeerNet) *ipv4.RawConn {
|
|
packetConn, err := net.ListenPacket("ip4:udp", client.IP.String())
|
|
if err != nil {
|
|
log.Fatalln("Error creating packetConn:", err)
|
|
}
|
|
|
|
rawConn, err := ipv4.NewRawConn(packetConn)
|
|
if err != nil {
|
|
log.Fatalln("Error creating rawConn:", err)
|
|
}
|
|
|
|
ApplyBPF(rawConn, server, client)
|
|
|
|
return rawConn
|
|
}
|
|
|
|
// ApplyBPF constructs a BPF program and applies it to the RawConn
|
|
func ApplyBPF(rawConn *ipv4.RawConn, server *Server, client *PeerNet) {
|
|
const ipv4HeaderLen = 20
|
|
const srcIPOffset = 12
|
|
const srcPortOffset = ipv4HeaderLen + 0
|
|
const dstPortOffset = ipv4HeaderLen + 2
|
|
|
|
ipArr := []byte(server.Addr.IP.To4())
|
|
ipInt := uint32(ipArr[0])<<(3*8) + uint32(ipArr[1])<<(2*8) + uint32(ipArr[2])<<8 + uint32(ipArr[3])
|
|
|
|
bpfRaw, err := bpf.Assemble([]bpf.Instruction{
|
|
bpf.LoadAbsolute{Off: srcIPOffset, Size: 4},
|
|
bpf.JumpIf{Cond: bpf.JumpEqual, Val: ipInt, SkipFalse: 5, SkipTrue: 0},
|
|
|
|
bpf.LoadAbsolute{Off: srcPortOffset, Size: 2},
|
|
bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(server.Port), SkipFalse: 3, SkipTrue: 0},
|
|
|
|
bpf.LoadAbsolute{Off: dstPortOffset, Size: 2},
|
|
bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(client.Port), SkipFalse: 1, SkipTrue: 0},
|
|
|
|
bpf.RetConstant{Val: 1<<(8*4) - 1},
|
|
bpf.RetConstant{Val: 0},
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatalln("Error assembling BPF:", err)
|
|
}
|
|
|
|
err = rawConn.SetBPF(bpfRaw)
|
|
if err != nil {
|
|
log.Fatalln("Error setting BPF:", err)
|
|
}
|
|
}
|
|
|
|
// MakePacket constructs a request packet to send to the server
|
|
func MakePacket(payload []byte, server *Server, client *PeerNet) []byte {
|
|
buf := gopacket.NewSerializeBuffer()
|
|
|
|
opts := gopacket.SerializeOptions{
|
|
FixLengths: true,
|
|
ComputeChecksums: true,
|
|
}
|
|
|
|
ipHeader := layers.IPv4{
|
|
SrcIP: client.IP,
|
|
DstIP: server.Addr.IP,
|
|
Version: 4,
|
|
TTL: 64,
|
|
Protocol: layers.IPProtocolUDP,
|
|
}
|
|
|
|
udpHeader := layers.UDP{
|
|
SrcPort: layers.UDPPort(client.Port),
|
|
DstPort: layers.UDPPort(server.Port),
|
|
}
|
|
|
|
payloadLayer := gopacket.Payload(payload)
|
|
|
|
udpHeader.SetNetworkLayerForChecksum(&ipHeader)
|
|
|
|
gopacket.SerializeLayers(buf, opts, &ipHeader, &udpHeader, &payloadLayer)
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// SendPacket sends packet to the Server
|
|
func SendPacket(packet []byte, conn *ipv4.RawConn, server *Server, client *PeerNet) error {
|
|
fullPacket := MakePacket(packet, server, client)
|
|
_, err := conn.WriteToIP(fullPacket, server.Addr)
|
|
return err
|
|
}
|
|
|
|
// SendDataPacket sends a JSON payload to the Server
|
|
func SendDataPacket(data interface{}, conn *ipv4.RawConn, server *Server, client *PeerNet) error {
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal payload: %v", err)
|
|
}
|
|
|
|
return SendPacket(jsonData, conn, server, client)
|
|
}
|
|
|
|
// RecvPacket receives a UDP packet from server
|
|
func RecvPacket(conn *ipv4.RawConn, server *Server, client *PeerNet) ([]byte, int, error) {
|
|
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
response := make([]byte, 4096)
|
|
n, err := conn.Read(response)
|
|
if err != nil {
|
|
return nil, n, err
|
|
}
|
|
return response, n, nil
|
|
}
|
|
|
|
// RecvDataPacket receives and unmarshals a JSON packet from server
|
|
func RecvDataPacket(conn *ipv4.RawConn, server *Server, client *PeerNet) ([]byte, error) {
|
|
response, n, err := RecvPacket(conn, server, client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Extract payload from UDP packet
|
|
payload := response[EmptyUDPSize:n]
|
|
return payload, nil
|
|
}
|
|
|
|
// ParseResponse takes a response packet and parses it into an IP and port
|
|
func ParseResponse(response []byte) (net.IP, uint16) {
|
|
ip := net.IP(response[:4])
|
|
port := binary.BigEndian.Uint16(response[4:6])
|
|
return ip, port
|
|
}
|
|
|
|
func parseForBPF(response []byte) (srcIP net.IP, srcPort uint16, dstPort uint16) {
|
|
srcIP = net.IP(response[12:16])
|
|
srcPort = binary.BigEndian.Uint16(response[20:22])
|
|
dstPort = binary.BigEndian.Uint16(response[22:24])
|
|
return
|
|
}
|