Skip to content

Commit

Permalink
Support nconnect network feature
Browse files Browse the repository at this point in the history
Signed-off-by: bill fort <[email protected]>
  • Loading branch information
billfort committed Aug 28, 2023
1 parent 4b35527 commit 8e4b31f
Show file tree
Hide file tree
Showing 62 changed files with 2,591 additions and 393 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
.DS_Store
build
nConnect
nConnect.exe
config.json
aws-ip.json
gcp-ip.json
geolite2-country.mmdb
*.favorite-node.json
*.avoid-node.json
*.log
*.log
config.member.json
member.json
network.json
config.manager.json
133 changes: 128 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ tunneling, thus benefits from all the advantages of
[nkn-tunnel](https://github.com/nknorg/nkn-tunnel):

- Network agnostic: Neither sender nor receiver needs to have public IP address
or port forwarding. NKN tunnel only establish outbound (websocket)
connections, so Internet access is all they need on both side.
or port forwarding. NKN tunnel only establishes outbound (websocket)
connections, so Internet access is all they need on both sides.

- Top level security: All data are end to end authenticated and encrypted. No
one else in the world except sender and receiver can see or modify the content
one else in the world except the sender and receiver can see or modify the content
of the data. The same public key is used for both routing and encryption,
eliminating the possibility of man in the middle attack.

Expand Down Expand Up @@ -288,10 +288,133 @@ $ nConnect -c -a server-address1 -a server-address2 -a server-address3
```


## Use `config.json` to Simplify Command Arguments

You can use `config.json` to simpliy command arguments. Move config.client.json or config.server.json as `config.json` and edit it before starting your nConnect client or server. After saving `config.json`, you can start nConnect simply.
You can use `config.json` to simplify command arguments. Copy config.client.json or config.server.json as `config.json` and edit it before starting your nConnect client or server. After saving `config.json`, you can start nConnect simply.

## Set up a Virtual Private Network by nConnect
Yes, nConnect supports setting up a virtual private network. It means many computers can join a nConnect virtual network, and access each other just like all nodes are in a local network no matter where they are.

In nConnect private virtual network, there are two types of nodes: one manager node, and network member nodes. The manager node is an administrative node that configures network parameters and authorizes members.

To set up a nConnect network, you need firstly to start a network manager node.

### Start a network manager
To start the network manager, we copy `config.network.json` to `config.manager.json`:

```
$ cp config.network.josn config.manager.json
```

Then edit config.json, enable `NetworkManager`, and give a value to "identifier", just like below:

```
{
"identifier": "manager",
"AdminHTTPAddr": "127.0.0.1:8001",
}
```

Then start nConnect as a network manager with parameter `-m -f config.manager.json` :

```
$ ./nConnect -m -f config.manager.json
```

After nConnect network manager starts, you can see a console printed message:

```
nConnect network manager is listening at: manager.0ec192083....
Network manager web serve at: http://127.0.0.1:8001/network
```

Copy this listening address: `manager.0ec192083....`, it is the manager's address. Other member nodes need this address to join this network.
After the manager starts, you can visit the web service `http://127.0.0.1:8001/network` (default), to config, to manage the network.

If you want to access nConnect manager from a public IP, you may configure `AdminHTTPAddr` with your computer's public IP. But do remember that other people can access your manager web page too. After configuring your network, you had better disable `AdminHTTPADDR` and set it to "127.0.0.0" or empty.

### Start some network members and join the network
On another computer, you can start a network member, and let it join the nConnect which you start above.
First, you copy `config.network.json` to `config.member.json`

```
$ cp config.network.json config.member.json
```

Then edit `config.member.json` to edit `identifier`, `managerAddress` and `nodeName`.

```
{
"identifier": "alice",
"managerAddress": "manager.0ec192083....",
"nodeName": "alice",
"seed": "...",
"AdminHTTPAddr": "127.0.0.1:8000",
}
```

Set `managerAddress` as your network manager's listening address, and identify your node name `nodeName`. Each network member should have a different `nodeName`.
The field `seed` is the seed of the wallet which you use to pay for `tuna` fee. Please keep it secured. If your wallet has zero balance, then nConnect Server cannot start at `tuna` mode.

Then you can start this node to join the network:

```
$ ./nConnect -n -s -c -f config.member.json --tuna --vpn --udp
or
$ ./nConnect.exe -n -s -c -f config.member.json --tuna --vpn --udp
```

`-n` means this is a network member `node`
For a network member, you may start both `-c` client, and `-s` server, which means you can access other nodes, and other nodes can access you too.
Or you can only set `-c`, which means you can access other nodes, but you don't want other nodes to access you.
Or you can only set `-s`, which means you can only be accessed, and you don't want to access other nodes.

### Manage the network

When a network member starts, it first will send `JoinNetwork` message to the network manager.
After the network administrator should open the manager's web administrate page `http://127.0.0.1:8001/network` (default), to configure the network name, IP range, netmask, and gateway.

There are two lists on the manager's web page:
* Waiting for Authorization
This lists all the nodes which are waiting for authorization to join this network. The administrator can accept it or reject it.
Only authorized nodes can become network members and will get a network-specific IP address.
When authorizing a node, it will pop up a dialog to set this node's permission to other nodes which decides if all members or only some of them can access this node.

* Network Members
In the network members list, the administrator can reset nodes' access permission and remove a node from the network (authorization).

If you don't see your node information in `Waiting for Authorization`, please click `Refresh` button to fetch updated data from the manager.

### Test your network
From the manager web page, you can see all member's IPs. To test your network, you can run a TCP server on a member node:

```
nconnect$ go run tests/tools/tcpmain.go -server
```

Then you run a TCP client on another member node, for example, the TCP server node's IP is: 10.20.30.2

```
nconnect$ go run tests/tools/tcpmain.go -serverAddr 10.20.30.2
```
You will see messages transmitted on both the server and the client.

## Contributing

Expand Down
75 changes: 49 additions & 26 deletions admin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package admin
import (
"encoding/json"
"errors"
"log"
"time"

"github.com/nknorg/nconnect/config"
Expand All @@ -14,7 +15,7 @@ const (
)

var (
errReplyTimeout = errors.New("wait for reply timeout")
ErrReplyTimeout = errors.New("wait for reply timeout")
)

var (
Expand All @@ -23,18 +24,21 @@ var (

type Client struct {
*nkn.MultiClient
replyTimeout time.Duration
ReplyTimeout time.Duration
}

func NewClient(account *nkn.Account, clientConfig *nkn.ClientConfig) (*Client, error) {
m, err := nkn.NewMultiClient(account, config.RandomIdentifier(), 4, false, clientConfig)
func NewClient(account *nkn.Account, clientConfig *nkn.ClientConfig, identifier string) (*Client, error) {
if identifier == "" {
identifier = config.RandomIdentifier()
}
m, err := nkn.NewMultiClient(account, identifier, 4, false, clientConfig)
if err != nil {
return nil, err
}

c := &Client{
MultiClient: m,
replyTimeout: replyTimeout,
ReplyTimeout: replyTimeout,
}

<-m.OnConnect.C
Expand All @@ -43,36 +47,18 @@ func NewClient(account *nkn.Account, clientConfig *nkn.ClientConfig) (*Client, e
}

func (c *Client) RPCCall(addr, method string, params interface{}, result interface{}) error {
req, err := json.Marshal(map[string]interface{}{
req := map[string]interface{}{
"id": "nConnect",
"method": method,
"params": params,
})
if err != nil {
return err
}

var onReply *nkn.OnMessage
var reply *nkn.Message
Loop:
for i := 0; i < 3; i++ { // retry 3 times if timeout
onReply, err = c.Send(nkn.NewStringArray(addr), req, nil)
if err != nil {
return err
}

select {
case reply = <-onReply.C:
break Loop
case <-time.After(c.replyTimeout):
err = errReplyTimeout
}
}
reply, err := c.SendMsg(addr, req, true)
if err != nil {
return err
}

resp := &rpcResp{
resp := &RpcResp{
Result: result,
}
err = json.Unmarshal(reply.Data, resp)
Expand All @@ -95,3 +81,40 @@ func (c *Client) GetInfo(addr string) (*GetInfoJSON, error) {
}
return res, nil
}

func (c *Client) SendMsg(address string, msg interface{}, waitResponse bool) (reply *nkn.Message, err error) {
if c.ReplyTimeout == 0 {
c.ReplyTimeout = replyTimeout
}

reqBytes, err := json.Marshal(msg)
if err != nil {
return nil, err
}

var onReply *nkn.OnMessage
for i := 0; i < 3; i++ {
onReply, err = c.Send(nkn.NewStringArray(address), reqBytes, nil)
if err != nil {
return nil, err
}

if !waitResponse {
return nil, nil
}

select {
case reply = <-onReply.C:
return reply, nil

case <-time.After(c.ReplyTimeout):
err = ErrReplyTimeout
}
}

if err == ErrReplyTimeout {
log.Printf("Wait for repsone timeout, please make sure the server is running and reachable")
}

return nil, err
}
8 changes: 4 additions & 4 deletions admin/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ var (
}
)

type rpcReq struct {
type RpcReq struct {
ID string `json:"id"`
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params map[string]interface{} `json:"params"`
Token string `json:"token"`
}

type rpcResp struct {
type RpcResp struct {
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
Expand Down Expand Up @@ -107,8 +107,8 @@ type getLogJSON struct {
MaxSize int `json:"maxSize"`
}

func handleRequest(req *rpcReq, persistConf, mergedConf *config.Config, tun *tunnel.Tunnel, rpcPerm permission) *rpcResp {
resp := &rpcResp{}
func handleRequest(req *RpcReq, persistConf, mergedConf *config.Config, tun *tunnel.Tunnel, rpcPerm permission) *RpcResp {
resp := &RpcResp{}

if rpcPermissions[req.Method]&rpcPerm == 0 {
resp.Error = errPermissionDenied.Error()
Expand Down
2 changes: 1 addition & 1 deletion admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func StartNKNServer(account *nkn.Account, identifier string, clientConfig *nkn.C
for {
msg := <-m.OnMessage.C

req := &rpcReq{}
req := &RpcReq{}
err := json.Unmarshal(msg.Data, req)
if err != nil {
log.Println("Unmarshal client request error:", err)
Expand Down
4 changes: 2 additions & 2 deletions admin/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ func StartWebServer(listenAddr string, tun *tunnel.Tunnel, persistConf, mergedCo
r.Use(gzip.Gzip(gzip.DefaultCompression))

r.POST("/rpc/admin", func(c *gin.Context) {
req := &rpcReq{}
req := &RpcReq{}
if err := c.ShouldBindJSON(req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if mergedConf.DisableAdminHTTPAPI {
c.JSON(http.StatusOK, &rpcResp{Error: errAdminHTTPAPIDisabled.Error()})
c.JSON(http.StatusOK, &RpcResp{Error: errAdminHTTPAPIDisabled.Error()})
return
}
resp := handleRequest(req, persistConf, mergedConf, tun, rpcPermissionWeb)
Expand Down
Loading

0 comments on commit 8e4b31f

Please sign in to comment.