mirror of
				https://github.com/therootcompany/telebit.git
				synced 2025-11-04 07:12:52 +00:00 
			
		
		
		
	Several Considerations for Load Balancing, Collection of Bulk ServerStatus
- added a number of global status collections - this requires wedging into things.. - removing direct address to come functions putting them though serverStatus
This commit is contained in:
		
							parent
							
								
									057ec00f82
								
							
						
					
					
						commit
						ba67cebb29
					
				@ -1,4 +1,5 @@
 | 
			
		||||
rvpn:
 | 
			
		||||
  serverName: rvpn1
 | 
			
		||||
  wssdomain: localhost.daplie.me
 | 
			
		||||
  admindomain: rvpn.daplie.invalid
 | 
			
		||||
  genericlistener: 9999
 | 
			
		||||
@ -14,6 +15,6 @@ rvpn:
 | 
			
		||||
    test3.daplie.me:
 | 
			
		||||
      secret: abc123
 | 
			
		||||
  loadbalancing:
 | 
			
		||||
    defaultmethod: 'round-robin'
 | 
			
		||||
    defaultmethod: round-robin
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@
 | 
			
		||||
                 </div> 
 | 
			
		||||
                    <nav class="collapse navbar-collapse" id="bs-navbar"> 
 | 
			
		||||
                        <ul class="nav navbar-nav"> 
 | 
			
		||||
                            <li> <a href="/admin/status">Status</a> </li>
 | 
			
		||||
                            <li> <a href="/admin/servers">Servers</a> </li>
 | 
			
		||||
                            <li> <a href="/admin/#domains">Domains</a> </li> 
 | 
			
		||||
                            <li>  <a href="/admin/#connections">Connections</a> </li>
 | 
			
		||||
 | 
			
		||||
@ -4,15 +4,23 @@ var app = angular.module("rvpnApp", ["ngRoute", "angular-duration-format"]);
 | 
			
		||||
 | 
			
		||||
app.config(function($routeProvider, $locationProvider) {
 | 
			
		||||
    $routeProvider
 | 
			
		||||
 | 
			
		||||
    .when("/admin/status/", {
 | 
			
		||||
        templateUrl : "admin/partials/status.html"
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    .when("/admin/index.html", {
 | 
			
		||||
        templateUrl : "admin/partials/servers.html"
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    .when("/admin/servers/", {
 | 
			
		||||
        templateUrl : "admin/partials/servers.html"
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    .when("/admin/#domains", {
 | 
			
		||||
        templateUrl : "green.htm"
 | 
			
		||||
    })
 | 
			
		||||
    
 | 
			
		||||
    .when("/blue", {
 | 
			
		||||
        templateUrl : "blue.htm"
 | 
			
		||||
    });
 | 
			
		||||
@ -81,7 +89,7 @@ app.controller('serverController', function ($scope, $http) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $scope.triggerDetail = function(id) {
 | 
			
		||||
        console.log("triggerDetail ", id, $scope.servers_trigger_details[id])
 | 
			
		||||
        //console.log("triggerDetail ", id, $scope.servers_trigger_details[id])
 | 
			
		||||
        if ($scope.servers_trigger_details[id] == true) {
 | 
			
		||||
            $scope.servers_trigger_details[id] = false;
 | 
			
		||||
        } else {
 | 
			
		||||
@ -90,7 +98,7 @@ app.controller('serverController', function ($scope, $http) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    $scope.checkDetail = function(id) {
 | 
			
		||||
        console.log("checkDetail ", id, $scope.servers_trigger_details[id])
 | 
			
		||||
        //console.log("checkDetail ", id, $scope.servers_trigger_details[id])
 | 
			
		||||
        if ($scope.servers_trigger_details[id] == true) {
 | 
			
		||||
            return false;
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@
 | 
			
		||||
        <th width="10%">Name</th>
 | 
			
		||||
        <th width="10%">Address</th>
 | 
			
		||||
        <th width="10%">Xfer (in/out)</th>
 | 
			
		||||
        <th width="10%">Req/Resp</th>
 | 
			
		||||
        <th width="10%">Time</th>
 | 
			
		||||
        <th width="10%">Idle</th>
 | 
			
		||||
        <th width="1%"><center><span class="glyphicon glyphicon-option-vertical" aria-hidden="true"></span></center></th>
 | 
			
		||||
@ -46,16 +47,34 @@
 | 
			
		||||
            <td>
 | 
			
		||||
                {{ s.bytes_in | bytes }}/{{ s.bytes_out | bytes }}
 | 
			
		||||
                <div ng-hide="checkDetail(s.server_id)">
 | 
			
		||||
                    domains({{ s.domains.length}})
 | 
			
		||||
                     
 | 
			
		||||
                    <div  ng-repeat="d in s.domains | orderBy:'domain_name'">
 | 
			
		||||
                       {{ d.bytes_in | bytes }}/{{ d.bytes_out | bytes }}
 | 
			
		||||
                    </div>   
 | 
			
		||||
                </div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                {{ s.requests }}/{{ s.responses }}
 | 
			
		||||
                <div ng-hide="checkDetail(s.server_id)">
 | 
			
		||||
                     
 | 
			
		||||
                    <div  ng-repeat="d in s.domains | orderBy:'domain_name'">
 | 
			
		||||
                       {{ d.requests }}/{{ d.responses  }}
 | 
			
		||||
                    </div>   
 | 
			
		||||
                </div>
 | 
			
		||||
            </td>
 | 
			
		||||
 | 
			
		||||
            <td>{{ s.duration | hfcduration }}</td>
 | 
			
		||||
            <td>{{ s.idle | hfcduration }}</td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <span class="glyphicon glyphicon-zoom-in" title="Detail" aria-hidden="false" ng-click="triggerDetail(s.server_id)"></span>
 | 
			
		||||
                <div ng-hide="checkDetail(s.server_id)">
 | 
			
		||||
                     
 | 
			
		||||
                    <div  ng-repeat="d in s.domains | orderBy:'domain_name'">
 | 
			
		||||
                    <span class="glyphicon glyphicon-zoom-in" title="Detail" aria-hidden="false" ng-click="triggerDetail(s.server_id+d.domain_name)"></span>
 | 
			
		||||
                    </div>   
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								html/admin/partials/status.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								html/admin/partials/status.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
<div class="panel panel-default" data-ng-controller="serverController">
 | 
			
		||||
  <div class="panel-heading">
 | 
			
		||||
      <div class="panel-title">
 | 
			
		||||
          <div class="row">
 | 
			
		||||
            <div class="col-md-6">
 | 
			
		||||
                Status
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6">
 | 
			
		||||
                <form class="form-inline pull-right">
 | 
			
		||||
                    <div class="form-group">
 | 
			
		||||
                        <label for="search">Search:</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="search" data-ng-model="servers_search">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <button type="button" title="Refresh" class="btn btn-default" aria-label="Refresh">
 | 
			
		||||
                        <span class="glyphicon glyphicon-refresh" title="Refresh" aria-hidden="false" ng-click="updateView()"></span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										24
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								main.go
									
									
									
									
									
								
							@ -33,6 +33,7 @@ var (
 | 
			
		||||
	dwell                    int
 | 
			
		||||
	cancelcheck              int
 | 
			
		||||
	lbDefaultMethod          string
 | 
			
		||||
	serverName               string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
@ -48,7 +49,7 @@ func main() {
 | 
			
		||||
	viper.AddConfigPath("./")
 | 
			
		||||
	err := viper.ReadInConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
 | 
			
		||||
		panic(fmt.Errorf("fatal error config file: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag.IntVar(&argDeadTime, "dead-time-counter", 5, "deadtime counter in seconds")
 | 
			
		||||
@ -61,6 +62,7 @@ func main() {
 | 
			
		||||
	dwell = deadtime.(map[string]interface{})["dwell"].(int)
 | 
			
		||||
	cancelcheck = deadtime.(map[string]interface{})["cancelcheck"].(int)
 | 
			
		||||
	lbDefaultMethod = viper.Get("rvpn.loadbalancing.defaultmethod").(string)
 | 
			
		||||
	serverName = viper.Get("rvpn.serverName").(string)
 | 
			
		||||
 | 
			
		||||
	loginfo.Println("startup")
 | 
			
		||||
 | 
			
		||||
@ -78,6 +80,14 @@ func main() {
 | 
			
		||||
	ctx, cancelContext := context.WithCancel(context.Background())
 | 
			
		||||
	defer cancelContext()
 | 
			
		||||
 | 
			
		||||
	serverStatus := genericlistener.NewStatus(ctx)
 | 
			
		||||
	serverStatus.AdminDomain = adminHostName
 | 
			
		||||
	serverStatus.WssDomain = wssHostName
 | 
			
		||||
	serverStatus.Name = serverName
 | 
			
		||||
	serverStatus.StartTime = time.Now()
 | 
			
		||||
	serverStatus.DeadTime = genericlistener.NewStatusDeadTime(dwell, idle, cancelcheck)
 | 
			
		||||
	serverStatus.LoadbalanceDefaultMethod = lbDefaultMethod
 | 
			
		||||
 | 
			
		||||
	// Setup for GenericListenServe.
 | 
			
		||||
	// - establish context for the generic listener
 | 
			
		||||
	// - startup listener
 | 
			
		||||
@ -88,17 +98,17 @@ func main() {
 | 
			
		||||
	// - match protocol
 | 
			
		||||
 | 
			
		||||
	connectionTracking := genericlistener.NewTracking()
 | 
			
		||||
	serverStatus.ConnectionTracking = connectionTracking
 | 
			
		||||
	go connectionTracking.Run(ctx)
 | 
			
		||||
 | 
			
		||||
	connectionTable = genericlistener.NewTable(dwell, idle)
 | 
			
		||||
	serverStatus.ConnectionTable = connectionTable
 | 
			
		||||
	go connectionTable.Run(ctx)
 | 
			
		||||
 | 
			
		||||
	genericListeners := genericlistener.NewGenerListeners(ctx, connectionTable, connectionTracking, secretKey, certbundle, wssHostName,
 | 
			
		||||
		adminHostName, cancelcheck, lbDefaultMethod)
 | 
			
		||||
	genericListeners := genericlistener.NewGenerListeners(ctx, secretKey, certbundle, serverStatus)
 | 
			
		||||
	serverStatus.GenericListeners = genericListeners
 | 
			
		||||
 | 
			
		||||
	genericListeners.Run(ctx, argGenericBinding)
 | 
			
		||||
	go genericListeners.Run(ctx, argGenericBinding)
 | 
			
		||||
 | 
			
		||||
	//Run for 10 minutes and then shutdown cleanly
 | 
			
		||||
	time.Sleep(6000 * time.Second)
 | 
			
		||||
	cancelContext()
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@ type DomainsAPI struct {
 | 
			
		||||
	ServerID   int64  `json:"server_id"`
 | 
			
		||||
	BytesIn    int64  `json:"bytes_in"`
 | 
			
		||||
	BytesOut   int64  `json:"bytes_out"`
 | 
			
		||||
	Requests   int64  `json:"requests"`
 | 
			
		||||
	Responses  int64  `json:"responses"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewDomainsAPI - Constructor
 | 
			
		||||
@ -15,6 +17,8 @@ func NewDomainsAPI(c *Connection, d *DomainTrack) (s *DomainsAPI) {
 | 
			
		||||
	s.ServerID = c.ConnectionID()
 | 
			
		||||
	s.BytesIn = d.BytesIn()
 | 
			
		||||
	s.BytesOut = d.BytesOut()
 | 
			
		||||
	s.Requests = d.requests
 | 
			
		||||
	s.Responses = d.responses
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@ -37,16 +41,20 @@ type DomainAPI struct {
 | 
			
		||||
	ServerID   int64  `json:"server_id"`
 | 
			
		||||
	BytesIn    int64  `json:"bytes_in"`
 | 
			
		||||
	BytesOut   int64  `json:"bytes_out"`
 | 
			
		||||
	Requests   int64  `json:"requests"`
 | 
			
		||||
	Responses  int64  `json:"responses"`
 | 
			
		||||
	Source     string `json:"source_addr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewDomainsAPI - Constructor
 | 
			
		||||
//NewDomainAPI - Constructor
 | 
			
		||||
func NewDomainAPI(c *Connection, d *DomainTrack) (s *DomainAPI) {
 | 
			
		||||
	s = new(DomainAPI)
 | 
			
		||||
	s.DomainName = d.DomainName
 | 
			
		||||
	s.ServerID = c.ConnectionID()
 | 
			
		||||
	s.BytesIn = d.BytesIn()
 | 
			
		||||
	s.BytesOut = d.BytesOut()
 | 
			
		||||
	s.Requests = d.requests
 | 
			
		||||
	s.Responses = d.responses
 | 
			
		||||
	s.Source = c.Source()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,8 @@ type ServersAPI struct {
 | 
			
		||||
	Idle       float64      `json:"idle"`
 | 
			
		||||
	BytesIn    int64        `json:"bytes_in"`
 | 
			
		||||
	BytesOut   int64        `json:"bytes_out"`
 | 
			
		||||
	Requests   int64        `json:"requests"`
 | 
			
		||||
	Responses  int64        `json:"responses"`
 | 
			
		||||
	Source     string       `json:"source_address"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -27,6 +29,8 @@ func NewServersAPI(c *Connection) (s *ServersAPI) {
 | 
			
		||||
	s.Idle = time.Since(c.LastUpdate()).Seconds()
 | 
			
		||||
	s.BytesIn = c.BytesIn()
 | 
			
		||||
	s.BytesOut = c.BytesOut()
 | 
			
		||||
	s.Requests = c.requests
 | 
			
		||||
	s.Responses = c.responses
 | 
			
		||||
	s.Source = c.Source()
 | 
			
		||||
 | 
			
		||||
	for d := range c.DomainTrack {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								rvpn/genericlistener/api_collect_status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								rvpn/genericlistener/api_collect_status.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
//StatusAPI -- Structure to support the server API
 | 
			
		||||
type StatusAPI struct {
 | 
			
		||||
	Name                     string             `json:"name"`
 | 
			
		||||
	Uptime                   float64            `json:"uptime"`
 | 
			
		||||
	WssDomain                string             `json:"wss_domain"`
 | 
			
		||||
	AdminDomain              string             `json:"admin_domain"`
 | 
			
		||||
	LoadbalanceDefaultMethod string             `json:"loadbalance_default_method"`
 | 
			
		||||
	DeadTime                 *StatusDeadTimeAPI `json:"dead_time"`
 | 
			
		||||
	AdminStats               *TrafficAPI        `json:"admin_traffic"`
 | 
			
		||||
	TrafficStats             *TrafficAPI        `json:"traffic"`
 | 
			
		||||
	ExtConnections           *ConnectionStats
 | 
			
		||||
	WSSConnections           *ConnectionStats
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewStatusAPI - Constructor
 | 
			
		||||
func NewStatusAPI(c *Status) (s *StatusAPI) {
 | 
			
		||||
	s = new(StatusAPI)
 | 
			
		||||
	s.Name = c.Name
 | 
			
		||||
	s.Uptime = time.Since(c.StartTime).Seconds()
 | 
			
		||||
	s.WssDomain = c.WssDomain
 | 
			
		||||
	s.AdminDomain = c.AdminDomain
 | 
			
		||||
	s.LoadbalanceDefaultMethod = c.LoadbalanceDefaultMethod
 | 
			
		||||
	s.DeadTime = NewStatusDeadTimeAPI(c.DeadTime.dwell, c.DeadTime.idle, c.DeadTime.cancelcheck)
 | 
			
		||||
	s.AdminStats = NewTrafficAPI(c.AdminStats.Requests, c.AdminStats.Responses, c.AdminStats.BytesIn, c.AdminStats.BytesOut)
 | 
			
		||||
	s.TrafficStats = NewTrafficAPI(c.TrafficStats.Requests, c.TrafficStats.Responses, c.TrafficStats.BytesIn, c.TrafficStats.BytesOut)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								rvpn/genericlistener/api_collect_status_dead time.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								rvpn/genericlistener/api_collect_status_dead time.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
//StatusDeadTimeAPI -- structure for deadtime configuration
 | 
			
		||||
type StatusDeadTimeAPI struct {
 | 
			
		||||
	Dwell       int `json:"dwell"`
 | 
			
		||||
	Idle        int `json:"idle"`
 | 
			
		||||
	Cancelcheck int `json:"cancel_check"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewStatusDeadTimeAPI -- constructor
 | 
			
		||||
func NewStatusDeadTimeAPI(dwell int, idle int, cancelcheck int) (p *StatusDeadTimeAPI) {
 | 
			
		||||
	p = new(StatusDeadTimeAPI)
 | 
			
		||||
	p.Dwell = dwell
 | 
			
		||||
	p.Idle = idle
 | 
			
		||||
	p.Cancelcheck = cancelcheck
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								rvpn/genericlistener/api_collect_status_traffic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								rvpn/genericlistener/api_collect_status_traffic.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
//TrafficStats --
 | 
			
		||||
type TrafficAPI struct {
 | 
			
		||||
	Requests  int64
 | 
			
		||||
	Responses int64
 | 
			
		||||
	BytesIn   int64
 | 
			
		||||
	BytesOut  int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewTrafficStats -- Consttuctor
 | 
			
		||||
func NewTrafficAPI(requests int64, responses int64, bytes_in int64, bytes_out int64) (p *TrafficAPI) {
 | 
			
		||||
	p = new(TrafficAPI)
 | 
			
		||||
	p.Requests = requests
 | 
			
		||||
	p.Responses = responses
 | 
			
		||||
	p.BytesIn = bytes_in
 | 
			
		||||
	p.BytesOut = bytes_out
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@ -16,12 +16,16 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var connectionTable *Table
 | 
			
		||||
var serverStatusAPI *Status
 | 
			
		||||
 | 
			
		||||
//handleAdminClient -
 | 
			
		||||
// - expecting an existing oneConnListener with a qualified wss client connected.
 | 
			
		||||
// - auth will happen again since we were just peeking at the token.
 | 
			
		||||
func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
 | 
			
		||||
	connectionTable = ctx.Value(ctxConnectionTable).(*Table)
 | 
			
		||||
	serverStatus := ctx.Value(ctxServerStatus).(*Status)
 | 
			
		||||
 | 
			
		||||
	connectionTable = serverStatus.ConnectionTable
 | 
			
		||||
	serverStatusAPI = serverStatus
 | 
			
		||||
	router := mux.NewRouter().StrictSlash(true)
 | 
			
		||||
 | 
			
		||||
	router.PathPrefix("/admin/").Handler(http.StripPrefix("/admin/", http.FileServer(http.Dir("html/admin"))))
 | 
			
		||||
@ -46,6 +50,7 @@ func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
 | 
			
		||||
	router.HandleFunc(endPointPrefix+"servers", getServersEndpoint).Methods("GET")
 | 
			
		||||
	router.HandleFunc(endPointPrefix+"server/", getServerEndpoint).Methods("GET")
 | 
			
		||||
	router.HandleFunc(endPointPrefix+"server/{server-id}", getServerEndpoint).Methods("GET")
 | 
			
		||||
	router.HandleFunc(endPointPrefix+"status/", getStatusEndpoint).Methods("GET")
 | 
			
		||||
 | 
			
		||||
	s := &http.Server{
 | 
			
		||||
		Addr:    ":80",
 | 
			
		||||
@ -64,6 +69,20 @@ func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getStatusEndpoint(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	pc, _, _, _ := runtime.Caller(0)
 | 
			
		||||
	loginfo.Println(runtime.FuncForPC(pc).Name())
 | 
			
		||||
 | 
			
		||||
	statusContainer := NewStatusAPI(serverStatusAPI)
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
 | 
			
		||||
 | 
			
		||||
	env := envelope.NewEnvelope("domains/GET")
 | 
			
		||||
	env.Result = statusContainer
 | 
			
		||||
	env.GenerateWriter(w)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDomainsEndpoint(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	pc, _, _, _ := runtime.Caller(0)
 | 
			
		||||
	loginfo.Println(runtime.FuncForPC(pc).Name())
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,12 @@ type Connection struct {
 | 
			
		||||
	// bytes out
 | 
			
		||||
	bytesOut int64
 | 
			
		||||
 | 
			
		||||
	// requests
 | 
			
		||||
	requests int64
 | 
			
		||||
 | 
			
		||||
	// response
 | 
			
		||||
	responses int64
 | 
			
		||||
 | 
			
		||||
	// Connect Time
 | 
			
		||||
	connectTime time.Time
 | 
			
		||||
 | 
			
		||||
@ -79,6 +85,8 @@ func NewConnection(connectionTable *Table, conn *websocket.Conn, remoteAddress s
 | 
			
		||||
	p.source = remoteAddress
 | 
			
		||||
	p.bytesIn = 0
 | 
			
		||||
	p.bytesOut = 0
 | 
			
		||||
	p.requests = 0
 | 
			
		||||
	p.responses = 0
 | 
			
		||||
	p.send = make(chan *SendTrack)
 | 
			
		||||
	p.connectTime = time.Now()
 | 
			
		||||
	p.initialDomains = initialDomains
 | 
			
		||||
@ -143,6 +151,14 @@ func (c *Connection) addOut(num int64) {
 | 
			
		||||
	c.bytesOut = c.bytesOut + num
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Connection) addRequests() {
 | 
			
		||||
	c.requests = c.requests + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Connection) addResponse() {
 | 
			
		||||
	c.responses = c.responses + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//ConnectionTable -- property
 | 
			
		||||
func (c *Connection) ConnectionTable() (table *Table) {
 | 
			
		||||
	table = c.connectionTable
 | 
			
		||||
@ -251,12 +267,14 @@ func (c *Connection) Reader(ctx context.Context) {
 | 
			
		||||
		//Support for tracking outbound traffic based on domain.
 | 
			
		||||
		if domainTrack, ok := c.DomainTrack[track.domain]; ok {
 | 
			
		||||
			//if ok then add to structure, else warn there is something wrong
 | 
			
		||||
			domainTrack.AddIn(int64(len(message)))
 | 
			
		||||
			domainTrack.AddOut(int64(len(message)))
 | 
			
		||||
			domainTrack.AddResponses()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		track.conn.Write(p.Data.Data())
 | 
			
		||||
 | 
			
		||||
		c.addIn(int64(len(message)))
 | 
			
		||||
		c.addResponse()
 | 
			
		||||
		loginfo.Println("end of read")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -290,11 +308,13 @@ func (c *Connection) Writer() {
 | 
			
		||||
			messageLen := int64(len(message.data))
 | 
			
		||||
 | 
			
		||||
			c.addOut(messageLen)
 | 
			
		||||
			c.addRequests()
 | 
			
		||||
 | 
			
		||||
			//Support for tracking outbound traffic based on domain.
 | 
			
		||||
			if domainTrack, ok := c.DomainTrack[message.domain]; ok {
 | 
			
		||||
				//if ok then add to structure, else warn there is something wrong
 | 
			
		||||
				domainTrack.AddOut(messageLen)
 | 
			
		||||
				domainTrack.AddIn(messageLen)
 | 
			
		||||
				domainTrack.AddRequests()
 | 
			
		||||
				loginfo.Println("adding ", messageLen, " to ", message.domain)
 | 
			
		||||
			} else {
 | 
			
		||||
				logdebug.Println("attempting to add bytes to ", message.domain, "it does not exist")
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,8 @@ type DomainTrack struct {
 | 
			
		||||
	DomainName string
 | 
			
		||||
	bytesIn    int64
 | 
			
		||||
	bytesOut   int64
 | 
			
		||||
	requests   int64
 | 
			
		||||
	responses  int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewDomainTrack -- Constructor
 | 
			
		||||
@ -13,6 +15,8 @@ func NewDomainTrack(domainName string) (p *DomainTrack) {
 | 
			
		||||
	p.DomainName = domainName
 | 
			
		||||
	p.bytesIn = 0
 | 
			
		||||
	p.bytesOut = 0
 | 
			
		||||
	p.requests = 0
 | 
			
		||||
	p.responses = 0
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -28,7 +32,7 @@ func (c *DomainTrack) BytesOut() (b int64) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//AddIn - Porperty
 | 
			
		||||
//AddIn - Property
 | 
			
		||||
func (c *DomainTrack) AddIn(num int64) {
 | 
			
		||||
	c.bytesIn = c.bytesIn + num
 | 
			
		||||
}
 | 
			
		||||
@ -37,3 +41,13 @@ func (c *DomainTrack) AddIn(num int64) {
 | 
			
		||||
func (c *DomainTrack) AddOut(num int64) {
 | 
			
		||||
	c.bytesOut = c.bytesOut + num
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//AddRequests - Property
 | 
			
		||||
func (c *DomainTrack) AddRequests() {
 | 
			
		||||
	c.requests = c.requests + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//AddResponses - Property
 | 
			
		||||
func (c *DomainTrack) AddResponses() {
 | 
			
		||||
	c.responses = c.responses + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,10 @@ type contextKey string
 | 
			
		||||
//CtxConnectionTrack
 | 
			
		||||
const (
 | 
			
		||||
	ctxSecretKey    contextKey = "secretKey"
 | 
			
		||||
	ctxConnectionTable          contextKey = "connectionTable"
 | 
			
		||||
	ctxServerStatus contextKey = "serverstatus"
 | 
			
		||||
 | 
			
		||||
	//ctxConnectionTable          contextKey = "connectionTable"
 | 
			
		||||
 | 
			
		||||
	ctxConfig                   contextKey = "config"
 | 
			
		||||
	ctxListenerRegistration     contextKey = "listenerRegistration"
 | 
			
		||||
	ctxConnectionTrack          contextKey = "connectionTrack"
 | 
			
		||||
@ -271,16 +274,16 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
 | 
			
		||||
//handleExternalHTTPRequest -
 | 
			
		||||
// - get a wConn and start processing requests
 | 
			
		||||
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname string, service string) {
 | 
			
		||||
	connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
 | 
			
		||||
	//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
 | 
			
		||||
	serverStatus := ctx.Value(ctxServerStatus).(*Status)
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		connectionTracking.unregister <- extConn
 | 
			
		||||
		serverStatus.ExtConnectionUnregister(extConn)
 | 
			
		||||
		extConn.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	connectionTable := ctx.Value(ctxConnectionTable).(*Table)
 | 
			
		||||
	//find the connection by domain name
 | 
			
		||||
	conn, ok := connectionTable.ConnByDomain(hostname)
 | 
			
		||||
	conn, ok := serverStatus.ConnectionTable.ConnByDomain(hostname)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		//matching connection can not be found based on ConnByDomain
 | 
			
		||||
		loginfo.Println("unable to match ", hostname, " to an existing connection")
 | 
			
		||||
@ -289,7 +292,7 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	track := NewTrack(extConn, hostname)
 | 
			
		||||
	connectionTracking.register <- track
 | 
			
		||||
	serverStatus.ExtConnectionRegister(track)
 | 
			
		||||
 | 
			
		||||
	loginfo.Println("Domain Accepted", hostname, extConn.RemoteAddr().String())
 | 
			
		||||
 | 
			
		||||
@ -322,10 +325,11 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
 | 
			
		||||
		p.Data.AppendBytes(buffer[0:cnt])
 | 
			
		||||
		buf := p.PackV1()
 | 
			
		||||
 | 
			
		||||
		loginfo.Println(hex.Dump(buf.Bytes()))
 | 
			
		||||
		//loginfo.Println(hex.Dump(buf.Bytes()))
 | 
			
		||||
 | 
			
		||||
		//Bundle up the send request and dispatch
 | 
			
		||||
		sendTrack := NewSendTrack(buf.Bytes(), hostname)
 | 
			
		||||
		conn.SendCh() <- sendTrack
 | 
			
		||||
		serverStatus.SendExtRequest(conn, sendTrack)
 | 
			
		||||
 | 
			
		||||
		_, err = extConn.Discard(cnt)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -341,7 +345,9 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
 | 
			
		||||
// - auth will happen again since we were just peeking at the token.
 | 
			
		||||
func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
 | 
			
		||||
	secretKey := ctx.Value(ctxSecretKey).(string)
 | 
			
		||||
	connectionTable := ctx.Value(ctxConnectionTable).(*Table)
 | 
			
		||||
	serverStatus := ctx.Value(ctxServerStatus).(*Status)
 | 
			
		||||
 | 
			
		||||
	//connectionTable := ctx.Value(ctxConnectionTable).(*Table)
 | 
			
		||||
 | 
			
		||||
	router := mux.NewRouter().StrictSlash(true)
 | 
			
		||||
	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
@ -380,9 +386,10 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
 | 
			
		||||
 | 
			
		||||
			//newConnection := connection.NewConnection(connectionTable, conn, r.RemoteAddr, domains)
 | 
			
		||||
 | 
			
		||||
			connectionTrack := ctx.Value(ctxConnectionTrack).(*Tracking)
 | 
			
		||||
			newRegistration := NewRegistration(conn, r.RemoteAddr, domains, connectionTrack)
 | 
			
		||||
			connectionTable.Register() <- newRegistration
 | 
			
		||||
			//connectionTrack := ctx.Value(ctxConnectionTrack).(*Tracking)
 | 
			
		||||
			newRegistration := NewRegistration(conn, r.RemoteAddr, domains, serverStatus.ConnectionTracking)
 | 
			
		||||
			serverStatus.WSSConnectionRegister(newRegistration)
 | 
			
		||||
 | 
			
		||||
			ok = <-newRegistration.CommCh()
 | 
			
		||||
			if !ok {
 | 
			
		||||
				loginfo.Println("connection registration failed ", newRegistration)
 | 
			
		||||
 | 
			
		||||
@ -57,23 +57,24 @@ type GenericListeners struct {
 | 
			
		||||
	adminHostName      string
 | 
			
		||||
	cancelCheck        int
 | 
			
		||||
	lbDefaultMethod    string
 | 
			
		||||
	serverStatus       *Status
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewGenerListeners --
 | 
			
		||||
func NewGenerListeners(ctx context.Context, connectionTable *Table, connectionTrack *Tracking, secretKey string, certbundle tls.Certificate,
 | 
			
		||||
	wssHostName string, adminHostName string, cancelCheck int, lbDefaultMethod string) (p *GenericListeners) {
 | 
			
		||||
func NewGenerListeners(ctx context.Context, secretKey string, certbundle tls.Certificate, serverStatus *Status) (p *GenericListeners) {
 | 
			
		||||
	p = new(GenericListeners)
 | 
			
		||||
	p.listeners = make(map[*net.Listener]int)
 | 
			
		||||
	p.ctx = ctx
 | 
			
		||||
	p.connnectionTable = connectionTable
 | 
			
		||||
	p.connectionTracking = connectionTrack
 | 
			
		||||
	p.connnectionTable = serverStatus.ConnectionTable
 | 
			
		||||
	p.connectionTracking = serverStatus.ConnectionTracking
 | 
			
		||||
	p.secretKey = secretKey
 | 
			
		||||
	p.certbundle = certbundle
 | 
			
		||||
	p.register = make(chan *ListenerRegistration)
 | 
			
		||||
	p.wssHostName = wssHostName
 | 
			
		||||
	p.adminHostName = adminHostName
 | 
			
		||||
	p.cancelCheck = cancelCheck
 | 
			
		||||
	p.lbDefaultMethod = lbDefaultMethod
 | 
			
		||||
	p.wssHostName = serverStatus.WssDomain
 | 
			
		||||
	p.adminHostName = serverStatus.AdminDomain
 | 
			
		||||
	p.cancelCheck = serverStatus.DeadTime.cancelcheck
 | 
			
		||||
	p.lbDefaultMethod = serverStatus.LoadbalanceDefaultMethod
 | 
			
		||||
	p.serverStatus = serverStatus
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -86,7 +87,6 @@ func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
 | 
			
		||||
	config := &tls.Config{Certificates: []tls.Certificate{gl.certbundle}}
 | 
			
		||||
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxSecretKey, gl.secretKey)
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxConnectionTable, gl.connnectionTable)
 | 
			
		||||
 | 
			
		||||
	loginfo.Println(gl.connectionTracking)
 | 
			
		||||
 | 
			
		||||
@ -97,6 +97,7 @@ func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxAdminHostName, gl.adminHostName)
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxCancelCheck, gl.cancelCheck)
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxLoadbalanceDefaultMethod, gl.lbDefaultMethod)
 | 
			
		||||
	ctx = context.WithValue(ctx, ctxServerStatus, gl.serverStatus)
 | 
			
		||||
 | 
			
		||||
	go func(ctx context.Context) {
 | 
			
		||||
		for {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										78
									
								
								rvpn/genericlistener/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								rvpn/genericlistener/status.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//Status --
 | 
			
		||||
type Status struct {
 | 
			
		||||
	ctx                      context.Context
 | 
			
		||||
	Name                     string
 | 
			
		||||
	StartTime                time.Time
 | 
			
		||||
	WssDomain                string
 | 
			
		||||
	AdminDomain              string
 | 
			
		||||
	DeadTime                 *StatusDeadTime
 | 
			
		||||
	ConnectionTracking       *Tracking
 | 
			
		||||
	ConnectionTable          *Table
 | 
			
		||||
	GenericListeners         *GenericListeners
 | 
			
		||||
	LoadbalanceDefaultMethod string
 | 
			
		||||
	AdminStats               *TrafficStats
 | 
			
		||||
	TrafficStats             *TrafficStats
 | 
			
		||||
	ExtConnections           *ConnectionStats
 | 
			
		||||
	WSSConnections           *ConnectionStats
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewStatus --
 | 
			
		||||
func NewStatus(ctx context.Context) (p *Status) {
 | 
			
		||||
	p = new(Status)
 | 
			
		||||
	p.ctx = ctx
 | 
			
		||||
	p.AdminStats = new(TrafficStats)
 | 
			
		||||
	p.TrafficStats = new(TrafficStats)
 | 
			
		||||
	p.ExtConnections = new(ConnectionStats)
 | 
			
		||||
	p.WSSConnections = new(ConnectionStats)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// South Facing Functions
 | 
			
		||||
 | 
			
		||||
//WSSConnectionRegister --
 | 
			
		||||
func (p *Status) WSSConnectionRegister(newRegistration *Registration) {
 | 
			
		||||
	p.ConnectionTable.Register() <- newRegistration
 | 
			
		||||
	p.WSSConnections.IncConnections()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//WSSConnectionUnregister --
 | 
			
		||||
//unregisters a south facing connection
 | 
			
		||||
//intercept and update global statistics
 | 
			
		||||
func (p *Status) WSSConnectionUnregister() {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// External Facing Functions
 | 
			
		||||
 | 
			
		||||
//ExtConnectionRegister --
 | 
			
		||||
//registers an ext facing connection
 | 
			
		||||
//intercept and update global statistics
 | 
			
		||||
func (p *Status) ExtConnectionRegister(newTrack *Track) {
 | 
			
		||||
	p.ConnectionTracking.register <- newTrack
 | 
			
		||||
	p.ExtConnections.IncConnections()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//ExtConnectionUnregister --
 | 
			
		||||
//unregisters an ext facing connection
 | 
			
		||||
//intercept and update global statistics
 | 
			
		||||
func (p *Status) ExtConnectionUnregister(extConn *WedgeConn) {
 | 
			
		||||
	p.ConnectionTracking.unregister <- extConn
 | 
			
		||||
	p.ExtConnections.DecConnections()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//SendExtRequest --
 | 
			
		||||
//sends a request to a south facing connection
 | 
			
		||||
//intercept the send, update our global stats
 | 
			
		||||
func (p *Status) SendExtRequest(conn *Connection, sendTrack *SendTrack) {
 | 
			
		||||
	p.TrafficStats.IncRequests()
 | 
			
		||||
	p.TrafficStats.AddBytesOut(int64(len(sendTrack.data)))
 | 
			
		||||
	conn.SendCh() <- sendTrack
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								rvpn/genericlistener/status_dead_time.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								rvpn/genericlistener/status_dead_time.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
//StatusDeadTime -- structure for deadtime configuration
 | 
			
		||||
type StatusDeadTime struct {
 | 
			
		||||
	dwell       int
 | 
			
		||||
	idle        int
 | 
			
		||||
	cancelcheck int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewStatusDeadTime -- constructor
 | 
			
		||||
func NewStatusDeadTime(dwell int, idle int, cancelcheck int) (p *StatusDeadTime) {
 | 
			
		||||
	p = new(StatusDeadTime)
 | 
			
		||||
	p.dwell = dwell
 | 
			
		||||
	p.idle = idle
 | 
			
		||||
	p.cancelcheck = cancelcheck
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								rvpn/genericlistener/status_traffic_connections.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								rvpn/genericlistener/status_traffic_connections.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
//ConnectionStats --
 | 
			
		||||
type ConnectionStats struct {
 | 
			
		||||
	Connections int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewConnectionStats -- Consttuctor
 | 
			
		||||
func NewConnectionStats() (p *ConnectionStats) {
 | 
			
		||||
	p = new(ConnectionStats)
 | 
			
		||||
	p.Connections = 0
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//IncConnections --
 | 
			
		||||
func (p *ConnectionStats) IncConnections() {
 | 
			
		||||
	p.Connections++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//DecConnections --
 | 
			
		||||
func (p *ConnectionStats) DecConnections() {
 | 
			
		||||
	p.Connections--
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								rvpn/genericlistener/status_traffic_stats.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								rvpn/genericlistener/status_traffic_stats.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package genericlistener
 | 
			
		||||
 | 
			
		||||
//TrafficStats --
 | 
			
		||||
type TrafficStats struct {
 | 
			
		||||
	Requests  int64
 | 
			
		||||
	Responses int64
 | 
			
		||||
	BytesIn   int64
 | 
			
		||||
	BytesOut  int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//NewTrafficStats -- Consttuctor
 | 
			
		||||
func NewTrafficStats() (p *TrafficStats) {
 | 
			
		||||
	p = new(TrafficStats)
 | 
			
		||||
	p.Requests = 0
 | 
			
		||||
	p.Responses = 0
 | 
			
		||||
	p.BytesIn = 0
 | 
			
		||||
	p.BytesOut = 0
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//IncRequests --
 | 
			
		||||
func (p *TrafficStats) IncRequests() {
 | 
			
		||||
	p.Requests++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//IncResponses --
 | 
			
		||||
func (p *TrafficStats) IncResponses() {
 | 
			
		||||
	p.Responses++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//AddBytesIn --
 | 
			
		||||
func (p *TrafficStats) AddBytesIn(count int64) {
 | 
			
		||||
	p.BytesIn = p.BytesIn + count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//AddBytesOut --
 | 
			
		||||
func (p *TrafficStats) AddBytesOut(count int64) {
 | 
			
		||||
	p.BytesOut = p.BytesOut + count
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user