Initial commit

This commit is contained in:
2025-07-15 22:31:05 +03:00
parent 4e0f8e62a0
commit 5056797b27
6 changed files with 807 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
# Editor/IDE
# .idea/
# .vscode/
+87
View File
@@ -0,0 +1,87 @@
config:
SIGNALING_SERVER: "ws://"
AUTHENTICATION:
USERNAME: "username"
PASSWORD: "password"
ICE_SERVERS:
- urls:
- "stun:stun.l.google.com:19302"
- urls:
- "turn:"
username: "turn_username"
credential: "turn_password"
DATA_CHANNEL: "datachannel"
messages:
START: "Loaded config. Starting...\n"
MISSING_PORT: "Missing port number.\n"
WS:
CONNECT:
SUCCESS: "Connected to WebSocket.\n"
ERROR: "Could not connect to WebSocket.\n %v\n\n"
CLOSE:
SUCCESS: "Disconnected from WebSocket.\n"
ERROR: "Could not close WebSocket connection.\n %v\n\n"
REGISTER:
SUCCESS: "Registered to WebSocket.\n"
ERROR: "Could not send register message.\n %v\n\n"
READ:
SUCCESS: "Received data from WebSocket.\n"
ERROR: "Could not read data from WebSocket.\n %v\n\n"
WRITE:
SUCCESS: "Sent data to WebSocket.\n"
ERROR: "Could not send data to WebSocket.\n %v\n\n"
JSON:
MARSHAL:
SUCCESS: "Converted struct to JSON string.\n"
ERROR: "Could not convert struct to JSON string.\n %v\n\n"
UNMARSHAL:
SUCCESS: "Converted JSON string to struct.\n"
ERROR: "Could not convert JSON string to struct.\n %v\n\n"
FORMAT:
SUCCESS: "Parsed JSON object type with value %s.\n"
ERROR: "Could not parse JSON object type.\n"
TYPE:
SUCCESS: "Handling JSON object as %s.\n"
ERROR: "Unknown JSON object %s.\n"
WEBRTC:
NEW:
SUCCESS: "Created WebRTC instance.\n"
ERROR: "Could not create WebRTC instance.\n %v\n\n"
OFFER:
SUCCESS: "Created offer.\n"
ERROR: "Could not create offer.\n %v\n\n"
LOCAL_DESCRIPTION:
SUCCESS: "Set local description.\n"
ERROR: "Could not set local description.\n %v\n\n"
REMOTE_DESCRIPTION:
SUCCESS: "Set remote description.\n"
ERROR: "Could not set remote description.\n %v\n\n"
ADD_CANDIDATE:
SUCCESS: "Added ICE candidate.\n"
ERROR: "Could not add ICE candidate.\n %v\n\n"
CREATE_DATA_CHANNEL:
SUCCESS: "Created data channel.\n"
ERROR: "Could not create data channel.\n %v\n\n"
SEND_DATA_CHANNEL:
SUCCESS: "Send data to data channel.\n"
ERROR: "Could not send data to data channel.\n %v\n\n"
ON_CANDIDATE: "Discovered ICE candidate.\n"
ON_DATA_CHANNEL: "Opened data channel.\n"
ON_MESSAGE: "Received data from data channel.\n"
HTTP:
LISTEN:
SUCCESS: "Listening for HTTP requests.\n"
ERROR: "Could not start HTTP server.\n %v\n\n"
READ:
SUCCESS: "Read HTTP request body.\n"
ERROR: "Could not read HTTP request body.\n %v\n\n"
WRITE:
SUCCESS: "Wrote HTTP response.\n"
ERROR: "Could not write HTTP response.\n %v\n\n"
GOB:
ENCODE:
SUCCESS: "Encoded struct to buffer.\n"
ERROR: "Could not encode struct to buffer.\n %v\n\n"
DECODE:
SUCCESS: "Converted buffer to struct.\n"
ERROR: "Could not convert buffer to struct.\n %v\n\n"
+33
View File
@@ -0,0 +1,33 @@
module peer
go 1.24.5
require (
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/pion/webrtc/v4 v4.1.3
)
require (
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.6 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.20 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.14 // indirect
github.com/pion/srtp/v3 v3.0.6 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
+63
View File
@@ -0,0 +1,63 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
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=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+474
View File
@@ -0,0 +1,474 @@
/*
* HTTP tunnel over WebRTC - Client implementation
* Author: Konstantinos Drakontidis
* Email: gedra100sh@gmail.com
*/
package main
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
"github.com/go-yaml/yaml"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v4"
)
var config_t Config
var http_port string
/*
* This function gets:
* pending_mutex: pointer to mutex
* pending: map with response channels
* dc: pointer to data channel
* Creates a HTTP server. Sends HTTP requests to data channel, waits and writes HTTP responses.
* The function does not return anything.
*/
func httpHandler(pending_mutex *sync.Mutex, pending map[string]chan Response,
dc *webrtc.DataChannel) {
// Creating the HTTP server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Generating a HTTP request id
id := uuid.New().String()
// Reading HTTP request body
req_body, read_error := io.ReadAll(r.Body)
if read_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.HTTP.READ.ERROR, read_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.HTTP.READ.SUCCESS)
}
// Constructing HTTP request struct
req_t := Request{
ID: id,
Method: r.Method,
Path: r.URL.Path + "?" + r.URL.RawQuery,
Headers: r.Header,
Body: req_body,
}
// Creating a response channel and adding it to the map
res_chan := make(chan Response)
(*pending_mutex).Lock()
pending[id] = res_chan
(*pending_mutex).Unlock()
// Encoding HTTP request struct and sending to data channel
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
encode_error := enc.Encode(req_t)
if encode_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.GOB.ENCODE.ERROR, encode_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.GOB.ENCODE.SUCCESS)
}
data_channel_send_error := dc.Send(buf.Bytes())
if data_channel_send_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.SEND_DATA_CHANNEL.ERROR,
data_channel_send_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.SEND_DATA_CHANNEL.SUCCESS)
}
// Waiting until HTTP response arrives
response := <-res_chan
// Setting HTTP headers and writing response
for key, vals := range response.Headers {
for _, val := range vals {
w.Header().Add(key, val)
}
}
w.WriteHeader(response.Status)
_, http_write_error := w.Write(response.Body)
if http_write_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.HTTP.WRITE.ERROR, http_write_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.HTTP.WRITE.SUCCESS)
}
// Removing response channel for answered requests
(*pending_mutex).Lock()
delete(pending, id)
(*pending_mutex).Unlock()
})
http_listen_error := http.ListenAndServe(":"+http_port, nil)
if http_listen_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.HTTP.LISTEN.ERROR, http_listen_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.HTTP.LISTEN.SUCCESS)
}
}
/*
* This function gets:
* pending_mutex: pointer to mutex
* pending: map with response channels
* raw: slice of bytes
* Sends the message to the corresponding channel from the map.
* The function does not return anything.
*/
func msgHandler(pending_mutex *sync.Mutex, pending map[string]chan Response, raw []byte) {
// Decoding bytes and storing them to struct
buf := bytes.NewBuffer(raw)
dec := gob.NewDecoder(buf)
var response_t Response
decode_error := dec.Decode(&response_t)
if decode_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.GOB.DECODE.ERROR, decode_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.GOB.DECODE.SUCCESS)
}
// Locking the map before indexing and unlocking
pending_mutex.Lock()
response_channel, exists := pending[response_t.ID]
if exists {
response_channel <- response_t
}
pending_mutex.Unlock()
}
/*
* This function gets:
* id: pointer to uuid
* p2p: pointer to the pointer of the peer connection
* offer: pointer to a session description
* conn: pointer to the websocket connection
* raw: slice of bytes
* Creates a new peer connection and a data channel and sets the listeners for them. Creates a
* peer connection offer and sends it to the signaling server.
* The function returns with pointers:
* id: set to the id received from the signaling server
* p2p: set to the peer connection that was created
* offer: set to the offer that was created
*/
func peerInit(id *uuid.UUID, p2p **webrtc.PeerConnection, offer *webrtc.SessionDescription,
conn *websocket.Conn, raw []byte) {
/*
* Creating a map with HTTP response channels and a mutex variable.
* These channels are used to return the HTTP response received from the data channel
* after a HTTP request has been made. Each request is identified by a uuid. The mutex
* locks the map so it is modified by one listener function at a time.
*/
var pending_mutex sync.Mutex
pending := make(map[string]chan Response)
// Decoding the received message
var id_t ID
message_unmarshal_error := json.Unmarshal(raw, &id_t)
if message_unmarshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.UNMARSHAL.ERROR, message_unmarshal_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.JSON.UNMARSHAL.SUCCESS)
}
*id = id_t.Id
// Creating the peer configuration
config := webrtc.Configuration{
ICEServers: config_t.Config.ICE_SERVERS,
}
// Creating peer connection
var new_peer_error error
*p2p, new_peer_error = webrtc.NewPeerConnection(config)
if new_peer_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.NEW.ERROR, new_peer_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.NEW.SUCCESS)
}
// Setting peer connection event handlers
(*p2p).OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) {
fmt.Printf("ICE: %s\n", is.String())
})
(*p2p).OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
fmt.Printf("WebRTC: %s\n", pcs.String())
})
(*p2p).OnICECandidate(func(candidate *webrtc.ICECandidate) {
// Checking if ICE gathering has finished
if candidate != nil {
// Constructing candidate, encoding to JSON and sending to websocket
candidate_t := Candidate{
Type: "candidate",
Id: id_t.Id,
Candidate: (*candidate).ToJSON(),
}
candidate_msg, message_marshal_error := json.Marshal(candidate_t)
if message_marshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.MARSHAL.ERROR, message_marshal_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.JSON.MARSHAL.SUCCESS)
}
ws_write_error := conn.WriteMessage(websocket.TextMessage, candidate_msg)
if ws_write_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.WRITE.ERROR, ws_write_error)
} else {
fmt.Print(config_t.Messages.WS.WRITE.SUCCESS)
}
}
})
// Creating data channel and setting event handlers
dc, create_data_channel_error := (*p2p).CreateDataChannel(config_t.Config.DATA_CHANNEL,
&webrtc.DataChannelInit{})
if create_data_channel_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.CREATE_DATA_CHANNEL.ERROR,
create_data_channel_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.CREATE_DATA_CHANNEL.SUCCESS)
}
dc.OnOpen(func() {
fmt.Print(config_t.Messages.WEBRTC.ON_DATA_CHANNEL)
httpHandler(&pending_mutex, pending, dc)
})
dc.OnMessage(func(msg webrtc.DataChannelMessage) {
fmt.Print(config_t.Messages.WEBRTC.ON_MESSAGE)
msgHandler(&pending_mutex, pending, msg.Data)
})
offer_options := webrtc.OfferOptions{
OfferAnswerOptions: webrtc.OfferAnswerOptions{
VoiceActivityDetection: false,
},
ICERestart: false,
}
// Creating and sending offer to websocket
var create_offer_error error
*offer, create_offer_error = (*p2p).CreateOffer(&offer_options)
if create_offer_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.OFFER.ERROR, create_offer_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.OFFER.SUCCESS)
}
offer_t := Offer{
Type: "offer",
Id: id_t.Id,
Offer: *offer,
}
offer_msg, message_marshal_error := json.Marshal(offer_t)
if message_marshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.MARSHAL.ERROR, message_marshal_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.JSON.MARSHAL.SUCCESS)
}
ws_write_error := conn.WriteMessage(websocket.TextMessage, offer_msg)
if ws_write_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.WRITE.ERROR, ws_write_error)
} else {
fmt.Print(config_t.Messages.WS.WRITE.SUCCESS)
}
}
/*
* This function gets:
* id: pointer to uuid
* p2p: pointer to the pointer of the peer connection
* offer: pointer to a session description
* raw: slice of bytes
* Sets both local and remote sessions descriptions.
* The function does not return anything.
*/
func answer(id *uuid.UUID, p2p **webrtc.PeerConnection, offer *webrtc.SessionDescription,
raw []byte) {
// Decoding JSON with answer in it
var answer_t Answer
message_unmarshal_error := json.Unmarshal(raw, &answer_t)
if message_unmarshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.UNMARSHAL.ERROR, message_unmarshal_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.JSON.UNMARSHAL.SUCCESS)
}
/*
* Checking if id matches and setting the session descriptions
* Once the local description is set, ICE gathering starts.
*/
if *id == answer_t.Id {
set_local_description_error := (*p2p).SetLocalDescription(*offer)
if set_local_description_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.LOCAL_DESCRIPTION.ERROR,
set_local_description_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.LOCAL_DESCRIPTION.SUCCESS)
}
set_remote_description_error := (*p2p).SetRemoteDescription(answer_t.Answer)
if set_remote_description_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WEBRTC.REMOTE_DESCRIPTION.ERROR,
set_remote_description_error)
os.Exit(-1)
} else {
fmt.Print(config_t.Messages.WEBRTC.REMOTE_DESCRIPTION.SUCCESS)
}
}
}
/*
* This function gets:
* id: pointer to uuid
* p2p: pointer to the pointer of the peer connection
* raw: slice of bytes
* Adds ICE candidate received from the signaling server.
* The function does not return anything.
*/
func candidate(id *uuid.UUID, p2p **webrtc.PeerConnection, raw []byte) {
// Decoding JSON with candidate in it
var candidate_t Candidate
json.Unmarshal(raw, &candidate_t)
// Checking if id matches and adding candidate
if *id == candidate_t.Id {
(*p2p).AddICECandidate(candidate_t.Candidate)
}
}
func main() {
// Attempting to read the config file
raw_config, config_error := os.ReadFile("config.yml")
if config_error != nil {
fmt.Fprintf(os.Stderr, "Could not read config file.\n%v\n", config_error)
return
}
// Loading config file to config struct
config_error = yaml.Unmarshal(raw_config, &config_t)
if config_error != nil {
fmt.Fprintf(os.Stderr, "Could not load config file.\n%v\n", config_error)
return
}
// Starting program
fmt.Print(config_t.Messages.START)
var (
id uuid.UUID
p2p *webrtc.PeerConnection
offer webrtc.SessionDescription
)
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, config_t.Messages.MISSING_PORT)
return
} else {
http_port = os.Args[1]
}
// Attempting to connect to the signaling server via WebSocket
conn, _, dial_error := websocket.DefaultDialer.Dial(config_t.Config.SIGNALING_SERVER, nil)
if dial_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.CONNECT.ERROR, dial_error)
return
} else {
fmt.Print(config_t.Messages.WS.CONNECT.SUCCESS)
}
// Attempt to close WebSocket connection on quit
defer func() {
ws_close_error := conn.Close()
if ws_close_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.CLOSE.ERROR, ws_close_error)
} else {
fmt.Print(config_t.Messages.WS.CLOSE.SUCCESS)
}
}()
// Constructing the register message
register_t := Register{
Type: "register",
Username: config_t.Config.AUTHENTICATION.USERNAME,
Password: config_t.Config.AUTHENTICATION.PASSWORD,
}
// Encoding register struct to JSON
register_msg, register_marshal_error := json.Marshal(register_t)
if register_marshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.MARSHAL.ERROR, register_marshal_error)
return
} else {
fmt.Print(config_t.Messages.JSON.MARSHAL.SUCCESS)
}
// Registering to the signaling server as normal user
ws_write_error := conn.WriteMessage(websocket.TextMessage, register_msg)
if ws_write_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.WRITE.ERROR, ws_write_error)
return
} else {
fmt.Print(config_t.Messages.WS.WRITE.SUCCESS)
}
// WebSocket loop for incomming messages
for {
// Reading message
_, ws_msg, ws_read_error := conn.ReadMessage()
if ws_read_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.WS.READ.ERROR, ws_read_error)
return
} else {
fmt.Print(config_t.Messages.WS.READ.SUCCESS)
}
// Decoding as any-format JSON
var parsed map[string]any
message_unmarshal_error := json.Unmarshal(ws_msg, &parsed)
if message_unmarshal_error != nil {
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.UNMARSHAL.ERROR, message_unmarshal_error)
continue
} else {
fmt.Print(config_t.Messages.JSON.UNMARSHAL.SUCCESS)
}
// Parsing message type
msg_type, exists := parsed["type"].(string)
if !exists {
fmt.Fprint(os.Stderr, config_t.Messages.JSON.FORMAT.ERROR)
continue
} else {
fmt.Printf(config_t.Messages.JSON.FORMAT.SUCCESS, msg_type)
}
// Setting handler function for each message type
switch msg_type {
case "id":
fmt.Printf(config_t.Messages.JSON.TYPE.SUCCESS, msg_type)
peerInit(&id, &p2p, &offer, conn, ws_msg)
case "answer":
fmt.Printf(config_t.Messages.JSON.TYPE.SUCCESS, msg_type)
answer(&id, &p2p, &offer, ws_msg)
case "candidate":
fmt.Printf(config_t.Messages.JSON.TYPE.SUCCESS, msg_type)
candidate(&id, &p2p, ws_msg)
default:
fmt.Fprintf(os.Stderr, config_t.Messages.JSON.TYPE.ERROR, msg_type)
continue
}
}
}
+118
View File
@@ -0,0 +1,118 @@
/*
* HTTP tunnel over WebRTC - Client types
* Author: Konstantinos Drakontidis
* Email: gedra100sh@gmail.com
*/
package main
import (
"github.com/google/uuid"
"github.com/pion/webrtc/v4"
)
type Terminate struct {
Type string `json:"type"`
Id uuid.UUID `json:"id"`
}
type ID struct {
Type string `json:"type"`
Id uuid.UUID `json:"id"`
}
type Answer struct {
Type string `json:"type"`
Id uuid.UUID `json:"id"`
Answer webrtc.SessionDescription `json:"answer"`
}
type Offer struct {
Type string `json:"type"`
Id uuid.UUID `json:"id"`
Offer webrtc.SessionDescription `json:"offer"`
}
type Candidate struct {
Type string `json:"type"`
Id uuid.UUID `json:"id"`
Candidate webrtc.ICECandidateInit `json:"candidate"`
}
type Register struct {
Type string `json:"type"`
Username string `json:"username"`
Password string `json:"password"`
}
type Request struct {
ID string
Method string
Path string
Headers map[string][]string
Body []byte
}
type Response struct {
ID string
Status int
Headers map[string][]string
Body []byte
Error string
}
type Message struct {
SUCCESS string `yaml:"SUCCESS"`
ERROR string `yaml:"ERROR"`
}
type Config struct {
Config struct {
SIGNALING_SERVER string `yaml:"SIGNALING_SERVER"`
AUTHENTICATION struct {
USERNAME string `yaml:"USERNAME"`
PASSWORD string `yaml:"PASSWORD"`
} `yaml:"AUTHENTICATION"`
ICE_SERVERS []webrtc.ICEServer `yaml:"ICE_SERVERS"`
DATA_CHANNEL string `yaml:"DATA_CHANNEL"`
} `yaml:"config"`
Messages struct {
START string `yaml:"START"`
MISSING_PORT string `yaml:"MISSING_PORT"`
WS struct {
CONNECT Message `yaml:"CONNECT"`
CLOSE Message `yaml:"CLOSE"`
REGISTER Message `yaml:"REGISTER"`
READ Message `yaml:"READ"`
WRITE Message `yaml:"WRITE"`
} `yaml:"WS"`
JSON struct {
MARSHAL Message `yaml:"MARSHAL"`
UNMARSHAL Message `yaml:"UNMARSHAL"`
FORMAT Message `yaml:"FORMAT"`
TYPE Message `yaml:"TYPE"`
} `yaml:"JSON"`
WEBRTC struct {
NEW Message `yaml:"NEW"`
OFFER Message `yaml:"OFFER"`
LOCAL_DESCRIPTION Message `yaml:"LOCAL_DESCRIPTION"`
REMOTE_DESCRIPTION Message `yaml:"REMOTE_DESCRIPTION"`
ADD_CANDIDATE Message `yaml:"ADD_CANDIDATE"`
CREATE_DATA_CHANNEL Message `yaml:"CREATE_DATA_CHANNEL"`
SEND_DATA_CHANNEL Message `yaml:"SEND_DATA_CHANNEL"`
ON_CANDIDATE string `yaml:"ON_CANDIDATE"`
ON_DATA_CHANNEL string `yaml:"ON_DATA_CHANNEL"`
ON_MESSAGE string `yaml:"ON_MESSAGE"`
} `yaml:"WEBRTC"`
HTTP struct {
LISTEN Message `yaml:"LISTEN"`
READ Message `yaml:"READ"`
WRITE Message `yaml:"WRITE"`
} `yaml:"HTTP"`
GOB struct {
ENCODE Message `yaml:"ENCODE"`
DECODE Message `yaml:"DECODE"`
} `yaml:"GOB"`
WS_CONNECT_ERROR string `yaml:"WS_CONNECT_ERROR"`
WS_CLOSE_ERROR string `yaml:"WS_CLOSE_ERROR"`
} `yaml:"messages"`
}