Initial commit of auth handshake
							parent
							
								
									05a6a013bc
								
							
						
					
					
						commit
						3f1485e493
					
				@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package pnyx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/ed25519"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/sha512"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Client struct {
 | 
				
			||||||
 | 
					  Key ed25519.PrivateKey
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func KeyID(key ed25519.PublicKey) ClientID {
 | 
				
			||||||
 | 
					  hash := sha512.Sum512([]byte(key))
 | 
				
			||||||
 | 
					  return (ClientID)((hash)[0:16])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(client Client) ID() ClientID { 
 | 
				
			||||||
 | 
					  return KeyID(client.Key.Public().(ed25519.PublicKey))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewClient(key ed25519.PrivateKey) (Client, error) {
 | 
				
			||||||
 | 
					  if key == nil {
 | 
				
			||||||
 | 
					    var err error
 | 
				
			||||||
 | 
					    _, key, err = ed25519.GenerateKey(rand.Reader)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return Client{}, err
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Client{
 | 
				
			||||||
 | 
					    Key: key,
 | 
				
			||||||
 | 
					  }, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
					  "net"
 | 
				
			||||||
 | 
					  "os"
 | 
				
			||||||
 | 
					  "time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
					  address, err := net.ResolveUDPAddr("udp", os.Args[1])
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  connection, err := net.DialUDP("udp", nil, address)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for true {
 | 
				
			||||||
 | 
					    written, err := connection.Write([]byte(os.Args[2]))
 | 
				
			||||||
 | 
					    if written != len(os.Args[2]) {
 | 
				
			||||||
 | 
					      panic(written)
 | 
				
			||||||
 | 
					    } else if err != nil {
 | 
				
			||||||
 | 
					      panic(err)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    time.Sleep(time.Second)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "git.metznet.ca/MetzNet/pnyx"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
					  os_sigs := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
					  signal.Notify(os_sigs, syscall.SIGINT, syscall.SIGINT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server := pnyx.NewServer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err := server.Start(os.Args[1])
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <-os_sigs
 | 
				
			||||||
 | 
					  err = server.Stop()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					module git.metznet.ca/MetzNet/pnyx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.21.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						filippo.io/edwards25519 v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/google/uuid v1.6.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 | 
				
			||||||
 | 
					filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 | 
				
			||||||
 | 
					github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
				
			||||||
 | 
					github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					package pnyx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/ed25519"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/sha512"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"filippo.io/edwards25519"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSessionOpen(key ed25519.PrivateKey) ([]byte, ed25519.PrivateKey, error) {
 | 
				
			||||||
 | 
					  if key == nil {
 | 
				
			||||||
 | 
					    return nil, nil, fmt.Errorf("Cannot create a SESSION_OPEN packet without a key")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public, private, err := ed25519.GenerateKey(rand.Reader)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return nil, nil, fmt.Errorf("Failed to generate ecdh key: %w", err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  packet := make([]byte, SESSION_OPEN_LENGTH)
 | 
				
			||||||
 | 
					  cur := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  copy(packet[cur:], []byte(key.Public().(ed25519.PublicKey)))
 | 
				
			||||||
 | 
					  cur += PUBKEY_LENGTH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  copy(packet[cur:], []byte(public)) 
 | 
				
			||||||
 | 
					  cur += PUBKEY_LENGTH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signature := ed25519.Sign(key, packet[:cur])
 | 
				
			||||||
 | 
					  copy(packet[cur:], signature)
 | 
				
			||||||
 | 
					  cur += SIGNATURE_LENGTH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return packet, private, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ParseSessionOpen(session_open []byte) (ed25519.PublicKey, ed25519.PublicKey, error) {
 | 
				
			||||||
 | 
					  if len(session_open) != SESSION_OPEN_LENGTH {
 | 
				
			||||||
 | 
					    return nil, nil, fmt.Errorf("Bad SESSION_OPEN length: %d/%d", len(session_open), SESSION_OPEN_LENGTH)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cur := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client_pubkey := (ed25519.PublicKey)(session_open[cur:cur+PUBKEY_LENGTH])
 | 
				
			||||||
 | 
					  cur += PUBKEY_LENGTH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client_ecdh := (ed25519.PublicKey)(session_open[cur:cur+PUBKEY_LENGTH])
 | 
				
			||||||
 | 
					  cur += PUBKEY_LENGTH
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  digest := session_open[:cur]
 | 
				
			||||||
 | 
					  signature := session_open[cur:cur+SIGNATURE_LENGTH]
 | 
				
			||||||
 | 
					  cur += SIGNATURE_LENGTH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if ed25519.Verify(client_pubkey, digest, signature) == false {
 | 
				
			||||||
 | 
					    return nil, nil, fmt.Errorf("SESSION_OPEN signature verification failed")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return client_pubkey, client_ecdh, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ECDH(public ed25519.PublicKey, private ed25519.PrivateKey) ([]byte, error) {
 | 
				
			||||||
 | 
					  public_point, err := (&edwards25519.Point{}).SetBytes(public)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return nil, err
 | 
				
			||||||
 | 
					  } 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h := sha512.Sum512(private.Seed())
 | 
				
			||||||
 | 
					  private_scalar, err := (&edwards25519.Scalar{}).SetBytesWithClamping(h[:32])
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return nil, err
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  shared_point := public_point.ScalarMult(private_scalar, public_point)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return shared_point.BytesMontgomery(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					package pnyx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/ed25519"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fatalErr(t *testing.T, err error) {
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    t.Fatal(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSessionOpen(t *testing.T) {
 | 
				
			||||||
 | 
					  client_pubkey, client_key, err := ed25519.GenerateKey(rand.Reader)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  session_open, client_ecdh, err := NewSessionOpen(client_key)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client_pubkey_parsed, client_ecdh_parsed, err := ParseSessionOpen(session_open)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if slices.Compare(client_pubkey, client_pubkey_parsed) != 0 {
 | 
				
			||||||
 | 
					    t.Fatalf("Client Pubkey %x does not match parsed %x", client_pubkey, client_pubkey_parsed)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if slices.Compare(client_ecdh.Public().(ed25519.PublicKey), client_ecdh_parsed) != 0 {
 | 
				
			||||||
 | 
					    t.Fatalf("Client Pubkey %x does not match parsed %x", client_pubkey, client_pubkey_parsed)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestECDH(t *testing.T) {
 | 
				
			||||||
 | 
					  client_public, client_private, err := ed25519.GenerateKey(rand.Reader)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server_public, server_private, err := ed25519.GenerateKey(rand.Reader)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server_secret, err := ECDH(client_public, server_private)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client_secret, err := ECDH(server_public, client_private)
 | 
				
			||||||
 | 
					  fatalErr(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if slices.Compare(server_secret, client_secret) != 0 {
 | 
				
			||||||
 | 
					    t.Fatalf("Server and Client secrets do not match")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,156 @@
 | 
				
			|||||||
 | 
					package pnyx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/binary"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
					  "os"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
					 SERVER_UDP_BUFFER_SIZE = 2048
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 PUBKEY_LENGTH = 32
 | 
				
			||||||
 | 
					 ECDH_LENGTH = 32
 | 
				
			||||||
 | 
					 SIGNATURE_LENGTH = 64
 | 
				
			||||||
 | 
					 SESSION_OPEN_LENGTH = PUBKEY_LENGTH + ECDH_LENGTH + SIGNATURE_LENGTH
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PacketType uint16
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
					  SESSION_OPEN PacketType = iota
 | 
				
			||||||
 | 
					  SESSION_AUTHENTICATE
 | 
				
			||||||
 | 
					  SESSION_CONNECT
 | 
				
			||||||
 | 
					  SESSION_CLOSE
 | 
				
			||||||
 | 
					  SESSION_CLOSED
 | 
				
			||||||
 | 
					  SESSION_DATA
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Session Flow:
 | 
				
			||||||
 | 
					1. Client send a SESSION_OPEN packet to the server with first half for ECDH and it's public key
 | 
				
			||||||
 | 
					2. Server responds with an SESSION_AUTHENTICATE packet with the server's ECDH half and public key
 | 
				
			||||||
 | 
					aside - at this point if the client and server both hold the private parts of their public keys,
 | 
				
			||||||
 | 
					        they both hold the same ECDH secret which they put in a KDF to generate a symmetric session key
 | 
				
			||||||
 | 
					aside - at this point the server creates the session in memory, but there is no return address associated
 | 
				
			||||||
 | 
					        with it yet
 | 
				
			||||||
 | 
					3. Client sends a SESSION_CONNECT packet to the server with the session ID in cleartext and the
 | 
				
			||||||
 | 
					   return address hashed with the key(to prove the return address has not been modified without the key) 
 | 
				
			||||||
 | 
					4. Server adds the return address to the session info, and maps the address to the session for future packets
 | 
				
			||||||
 | 
					5. Server sends the HELLO packet to the client encrypted by the session key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If a client disconnects at any point and gets a new return address:
 | 
				
			||||||
 | 
					1. Client sends a SESSION_CONNECT packet to the server from the new socket
 | 
				
			||||||
 | 
					2. Server removes the old return address, and fills/maps the new return address
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If a client wants to gracefully disconnect and notify the server to close the session:
 | 
				
			||||||
 | 
					1. Client sends a SESSION_CLOSE
 | 
				
			||||||
 | 
					2. Server responds with SESSION_CLOSED
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Session Packets:
 | 
				
			||||||
 | 
					1. SESSION_OPEN
 | 
				
			||||||
 | 
					 Payload is CLIENT_PUBKEY + ECDH_HALF + SIGNATURE
 | 
				
			||||||
 | 
					3. SESSION_CONNECT
 | 
				
			||||||
 | 
					4. SESSION_CLOSE
 | 
				
			||||||
 | 
					5. SESSION_CLOSED
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SessionID uuid.UUID
 | 
				
			||||||
 | 
					type ClientID uuid.UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Connection struct {
 | 
				
			||||||
 | 
					  state string
 | 
				
			||||||
 | 
					  session SessionID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Session struct {
 | 
				
			||||||
 | 
					  state string
 | 
				
			||||||
 | 
					  client ClientID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Server struct {
 | 
				
			||||||
 | 
					  active atomic.Bool
 | 
				
			||||||
 | 
					  connection *net.UDPConn
 | 
				
			||||||
 | 
					  stopped chan error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  connections map[string]SessionID
 | 
				
			||||||
 | 
					  sessions map[SessionID]Session
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewServer() *Server {
 | 
				
			||||||
 | 
					  server := &Server{
 | 
				
			||||||
 | 
					    connection: nil,
 | 
				
			||||||
 | 
					    active: atomic.Bool{},
 | 
				
			||||||
 | 
					    stopped: make(chan error, 0),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  server.active.Store(false)
 | 
				
			||||||
 | 
					  return server
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (server *Server) Log(format string, fields ...interface{}) {
 | 
				
			||||||
 | 
					  fmt.Fprint(os.Stderr, fmt.Sprintf(format, fields...) + "\n")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(server *Server) Stop() error {
 | 
				
			||||||
 | 
					  was_active := server.active.CompareAndSwap(true, false)
 | 
				
			||||||
 | 
					  if was_active {
 | 
				
			||||||
 | 
					    err := server.connection.Close()
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return err
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return <-server.stopped
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return fmt.Errorf("Called stop func on stopped server")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(server *Server) run() {
 | 
				
			||||||
 | 
					  server.Log("Started server on %s", server.connection.LocalAddr())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var buf [SERVER_UDP_BUFFER_SIZE]byte
 | 
				
			||||||
 | 
					  for true {
 | 
				
			||||||
 | 
					    read, addr, err := server.connection.ReadFromUDP(buf[:])
 | 
				
			||||||
 | 
					    if err == nil {
 | 
				
			||||||
 | 
					      var packet_type PacketType = PacketType(binary.BigEndian.Uint16(buf[0:2]))
 | 
				
			||||||
 | 
					      switch packet_type {
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        server.Log("Unhandled packet type 0x%04x from %s: %+v", packet_type, addr, buf[:read])
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if errors.Is(err, net.ErrClosed) {
 | 
				
			||||||
 | 
					      server.Log("UDP_CLOSE: %s", server.connection.LocalAddr())
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      server.Log("UDP_READ_ERROR: %s", err)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.Log("Shut down server on %s", server.connection.LocalAddr())
 | 
				
			||||||
 | 
					  server.stopped <- nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(server *Server) Start(listen string) error {
 | 
				
			||||||
 | 
					  was_inactive := server.active.CompareAndSwap(false, true)
 | 
				
			||||||
 | 
					  if was_inactive == false {
 | 
				
			||||||
 | 
					    return fmt.Errorf("Server already active")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  address, err := net.ResolveUDPAddr("udp", listen)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    server.active.Store(false)
 | 
				
			||||||
 | 
					    return err
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.connection, err = net.ListenUDP("udp", address)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    server.active.Store(false)
 | 
				
			||||||
 | 
					    return fmt.Errorf("Failed to create udp server: %w", err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  go server.run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue