Skip to content

Commit 8901e63

Browse files
committed
feat: udp bandwidth limit support
1 parent 3e7e0fd commit 8901e63

File tree

3 files changed

+58
-9
lines changed

3 files changed

+58
-9
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Portbridge is a user-space port-forwarding tool with cross-platform support.
66

77
- Cross-Platform Support (Linux / Windows / Darwin)
88
- TCP and UDP Forward Support
9-
- Bandwidth Limit Support for TCP
9+
- TCP and UDP Bandwidth Limit Support
1010
- Batch Port Forwarding Rules Support
1111

1212
# Usage
@@ -17,15 +17,15 @@ Portbridge Options:
1717
-s, --source= Source address and port to bind locally
1818
-d, --destination= Destination address and port to connect remotely
1919
-p, --protocol= Specify the source protocol type
20-
-b, --bandwidth-limit= TCP Bandwidth limit in KiB (default: 0)
20+
-b, --bandwidth-limit= Bandwidth limit in KiB (default: 0)
2121
--udp-buffer-size= UDP data forwarding buffer size in bytes (default: 1024)
2222
--udp-timeout-second= UDP data forwarding time out in second (default: 5)
2323
-f, --rule-file= Batch port forwarding file path
2424
-h, --help Show help message
2525
-v, --version Print the version number
2626
```
2727

28-
Example:
28+
Examples:
2929

3030
- Access the Cloudflare DNS (ipv6) via 127.0.0.2:53 with 100 udp buffer size
3131

cmd/options/options.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type Options struct {
44
SourceAddr string `short:"s" long:"source" description:"Source address and port to bind locally" required:"true"`
55
DestinationAddr string `short:"d" long:"destination" description:"Destination address and port to connect remotely" required:"true"`
66
Protocol string `short:"p" long:"protocol" description:"Specify the source protocol type" required:"true"`
7-
BandwidthLimit uint64 `short:"b" long:"bandwidth-limit" description:"TCP Bandwidth limit in KiB" default:"0"`
7+
BandwidthLimit uint64 `short:"b" long:"bandwidth-limit" description:"Bandwidth limit in KiB" default:"0"`
88
UDPBufferSize uint64 `long:"udp-buffer-size" description:"UDP data forwarding buffer size in bytes" default:"1024"`
99
UDPTimeoutSecond uint64 `long:"udp-timeout-second" description:"UDP data forwarding time out in second" default:"5"`
1010
RuleFile string `short:"f" long:"rule-file" description:"Batch port forwarding file path"`

pkg/forward/udp.go

+54-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package forward
22

33
import (
4-
"errors"
4+
"context"
5+
"golang.org/x/time/rate"
56
"net"
67
"time"
78
)
@@ -21,7 +22,11 @@ func NewUDPDataForwarder() *UDPDataForwarder {
2122
}
2223

2324
func (f *UDPDataForwarder) Forward(sourceConn, destinationConn net.Conn) error {
24-
return f.ForwardWithNormal(sourceConn, destinationConn)
25+
if f.BandwidthLimit != DefaultBandwidthLimit {
26+
return f.ForwardWithTrafficControl(sourceConn, destinationConn)
27+
} else {
28+
return f.ForwardWithNormal(sourceConn, destinationConn)
29+
}
2530
}
2631

2732
func (f *UDPDataForwarder) ForwardWithNormal(sourceConn, destinationConn net.Conn) error {
@@ -47,8 +52,7 @@ func (f *UDPDataForwarder) ForwardWithNormal(sourceConn, destinationConn net.Con
4752
destinationConnBuffer := make([]byte, f.BufferSize)
4853
destinationConn.SetReadDeadline(time.Now().Add(f.DeadlineSecond * time.Second))
4954
m, _, err := destinationUDPConn.ReadFromUDP(destinationConnBuffer)
50-
var netErr net.Error
51-
if errors.As(err, &netErr) && netErr.Timeout() {
55+
if err != nil {
5256
return
5357
}
5458

@@ -62,7 +66,52 @@ func (f *UDPDataForwarder) ForwardWithNormal(sourceConn, destinationConn net.Con
6266
}
6367

6468
func (f *UDPDataForwarder) ForwardWithTrafficControl(sourceConn, destinationConn net.Conn) error {
65-
return f.ForwardWithNormal(sourceConn, destinationConn)
69+
sourceUDPConn, _ := sourceConn.(*net.UDPConn)
70+
destinationUDPConn, _ := destinationConn.(*net.UDPConn)
71+
72+
limiter := rate.NewLimiter(rate.Limit(f.BandwidthLimit*1024/8), int(f.BandwidthLimit*1024/8))
73+
74+
sourceConnBuffer := make([]byte, f.BufferSize)
75+
for {
76+
sourceConn.SetReadDeadline(time.Now().Add(f.DeadlineSecond * time.Second))
77+
n, sourceConnAddr, err := sourceUDPConn.ReadFromUDP(sourceConnBuffer)
78+
if err != nil {
79+
continue
80+
}
81+
82+
data := make([]byte, n)
83+
copy(data, sourceConnBuffer[:n])
84+
85+
go func(data []byte, sourceConnAddr *net.UDPAddr) {
86+
err := limiter.WaitN(context.Background(), n)
87+
if err != nil {
88+
return
89+
}
90+
91+
_, err = destinationConn.Write(data)
92+
if err != nil {
93+
return
94+
}
95+
96+
destinationConnBuffer := make([]byte, f.BufferSize)
97+
destinationConn.SetReadDeadline(time.Now().Add(f.DeadlineSecond * time.Second))
98+
m, _, err := destinationUDPConn.ReadFromUDP(destinationConnBuffer)
99+
if err != nil {
100+
return
101+
}
102+
103+
err = limiter.WaitN(context.Background(), m)
104+
if err != nil {
105+
return
106+
}
107+
108+
_, err = sourceUDPConn.WriteToUDP(destinationConnBuffer[:m], sourceConnAddr)
109+
if err != nil {
110+
return
111+
}
112+
113+
}(data, sourceConnAddr)
114+
}
66115
}
67116

68117
func (f *UDPDataForwarder) SetBandwidthLimit(bandwidthLimit uint64) *UDPDataForwarder {

0 commit comments

Comments
 (0)