Skip to content

Commit

Permalink
tracker: add real-ip handling
Browse files Browse the repository at this point in the history
rpc: remove outdated documentation lines

website: redirect to /main.mp3 instead of just /main
  • Loading branch information
Wessie committed May 5, 2024
1 parent 0ba2746 commit c3eb4e4
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 14 deletions.
2 changes: 0 additions & 2 deletions rpc/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ func toProtoUser(u *radio.User) *User {
//Password: u.Password,
//Email: u.Email,
//RememberToken: u.RememberToken,
//UserPermissions: u.UserPermissions,
}
}

Expand All @@ -269,7 +268,6 @@ func fromProtoUser(u *User) *radio.User {
//Password: u.Password,
//Email: u.Email,
//RememberToken: u.RememberToken,
//UserPermissions: u.UserPermissions,
}
}

Expand Down
50 changes: 39 additions & 11 deletions tracker/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package tracker

import (
"context"
"net"
"net/http"
"net/url"
"strings"
"sync"
"sync/atomic"
Expand All @@ -20,12 +20,7 @@ import (
type Listener struct {
span trace.Span

// ID is the identifier icecast is using for this client
ID radio.ListenerClientID
// Start is the time this listener started listening
Start time.Time
// Info is the information icecast sends us through the POST form values
Info url.Values
radio.Listener
}

func NewRecorder(ctx context.Context) *Recorder {
Expand Down Expand Up @@ -86,10 +81,13 @@ func (r *Recorder) ListenerAdd(ctx context.Context, id radio.ListenerClientID, r
)

listener := Listener{
ID: id,
span: span,
Start: time.Now(),
Info: req.PostForm,
span: span,
Listener: radio.Listener{
ID: id,
UserAgent: req.PostFormValue("agent"), // passed by icecast
Start: time.Now(),
IP: IcecastRealIP(req),
},
}

var ok bool
Expand Down Expand Up @@ -131,3 +129,33 @@ func requestToOtelAttributes(req *http.Request) []attribute.KeyValue {
}
return res
}

const prefix = "client."

var xForwardedFor = strings.ToLower(prefix + "X-Forwarded-For")
var xRealIP = strings.ToLower(prefix + "X-Real-IP")
var trueClientIP = strings.ToLower(prefix + "True-Client-IP")

// icecastRealIP recovers the clients real ip address from the request
//
// This looks for X-Forwarded-For, X-Real-IP and True-Client-IP
func IcecastRealIP(r *http.Request) string {
var ip string

if tcip := r.PostForm.Get(trueClientIP); tcip != "" {
ip = tcip
} else if xrip := r.PostForm.Get(xRealIP); xrip != "" {
ip = xrip
} else if xff := r.PostForm.Get(xForwardedFor); xff != "" {
i := strings.Index(xff, ",")
if i == -1 {
i = len(xff)
}
ip = xff[:i]
}
if ip == "" || net.ParseIP(ip) == nil {
return ""
}
return ip

}
77 changes: 77 additions & 0 deletions tracker/recorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -167,3 +169,78 @@ func TestRecorderRemoveStalePending(t *testing.T) {
}, eventuallyDelay, eventuallyTick)
})
}

func TestIcecastRealIP(t *testing.T) {
t.Run(xForwardedFor, func(t *testing.T) {
ctx := testCtx(t)
r := NewRecorder(ctx)
id := radio.ListenerClientID(50)
ip := "192.168.1.1"
values := url.Values{}
values.Add(xForwardedFor, ip)

body := strings.NewReader(values.Encode())
req := httptest.NewRequest(http.MethodPost, "/listener_add", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

r.ListenerAdd(ctx, id, req)

r.mu.Lock()
assert.Equal(t, ip, r.listeners[id].IP)
r.mu.Unlock()
})
t.Run(xForwardedFor+"/multiple", func(t *testing.T) {
ctx := testCtx(t)
r := NewRecorder(ctx)
id := radio.ListenerClientID(50)
ip := "192.168.1.1, 203.0.113.195, 70.41.3.18, 150.172.238.178"
values := url.Values{}
values.Add(xForwardedFor, ip)

body := strings.NewReader(values.Encode())
req := httptest.NewRequest(http.MethodPost, "/listener_add", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

r.ListenerAdd(ctx, id, req)

r.mu.Lock()
assert.Equal(t, "192.168.1.1", r.listeners[id].IP)
r.mu.Unlock()
})
t.Run(trueClientIP, func(t *testing.T) {
ctx := testCtx(t)
r := NewRecorder(ctx)
id := radio.ListenerClientID(50)
ip := "192.168.1.1"
values := url.Values{}
values.Add(trueClientIP, ip)

body := strings.NewReader(values.Encode())
req := httptest.NewRequest(http.MethodPost, "/listener_add", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

r.ListenerAdd(ctx, id, req)

r.mu.Lock()
assert.Equal(t, ip, r.listeners[id].IP)
r.mu.Unlock()
})
t.Run(xRealIP, func(t *testing.T) {
ctx := testCtx(t)
r := NewRecorder(ctx)
id := radio.ListenerClientID(50)
ip := "192.168.1.1"
values := url.Values{}
values.Add(xRealIP, ip)

body := strings.NewReader(values.Encode())
req := httptest.NewRequest(http.MethodPost, "/listener_add", body)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

r.ListenerAdd(ctx, id, req)

r.mu.Lock()
assert.Equal(t, ip, r.listeners[id].IP)
r.mu.Unlock()
})
}
2 changes: 1 addition & 1 deletion website/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func Execute(ctx context.Context, cfg config.Config) error {

// RedirectLegacyStream redirects a request to the (new) icecast stream url
func RedirectLegacyStream(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "//stream.r-a-d.io/main")
w.Header().Set("Location", "//stream.r-a-d.io/main.mp3")
w.WriteHeader(http.StatusMovedPermanently)
}

Expand Down
1 change: 1 addition & 0 deletions website/public/faves.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func (s State) GetFaves(w http.ResponseWriter, r *http.Request) {
err := json.NewEncoder(w).Encode(NewFaveDownload(input.Faves))
if err != nil {
s.errorHandler(w, r, err)
return
}
return
}
Expand Down

0 comments on commit c3eb4e4

Please sign in to comment.