Skip to content

Commit 98896e2

Browse files
committed
api: add custom IP support for peers during friend request/accept
1 parent 6e0cd17 commit 98896e2

10 files changed

Lines changed: 333 additions & 80 deletions

File tree

api/apiclient/client.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,21 @@ func (c *Client) UpdateProxySettings(usingPeerID string) error {
7474
return c.sendPostRequest(api.UpdateProxySettingsPath, request, nil)
7575
}
7676

77-
func (c *Client) SendFriendRequest(peerID, alias string) error {
77+
func (c *Client) SendFriendRequest(peerID, alias string, ipAddr string) error {
7878
request := entity.FriendRequest{
7979
PeerID: peerID,
8080
Alias: alias,
81+
IPAddr: ipAddr,
8182
}
8283
return c.sendPostRequest(api.SendFriendRequestPath, request, nil)
8384
}
8485

85-
func (c *Client) ReplyFriendRequest(peerID, alias string, decline bool) error {
86+
func (c *Client) ReplyFriendRequest(peerID, alias string, decline bool, ipAddr string) error {
8687
request := entity.FriendRequestReply{
8788
PeerID: peerID,
8889
Alias: alias,
8990
Decline: decline,
91+
IPAddr: ipAddr,
9092
}
9193
return c.sendPostRequest(api.AcceptPeerInvitationPath, request, nil)
9294
}

api/peers.go

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,11 @@ func (h *Handler) SendFriendRequest(c echo.Context) (err error) {
180180
ErrorMessage("You can't add yourself"))
181181
}
182182

183-
_, exist := h.conf.GetPeer(req.PeerID)
184-
if exist {
185-
return c.JSON(http.StatusBadRequest, ErrorMessage("Peer has already been added"))
186-
}
187-
188-
req.Alias = strings.TrimSpace(req.Alias)
189-
if !h.conf.IsUniqPeerAlias("", req.Alias) {
190-
return c.JSON(http.StatusBadRequest, ErrorMessage(ErrorPeerAliasIsNotUniq))
183+
err = h.authStatus.AddPeer(h.ctx, peerId, "", req.Alias, false, req.IPAddr)
184+
if err != nil {
185+
return c.JSON(http.StatusBadRequest, ErrorMessage(err.Error()))
191186
}
192187

193-
h.authStatus.AddPeer(h.ctx, peerId, "", req.Alias, false)
194-
195188
return c.NoContent(http.StatusOK)
196189
}
197190

@@ -224,11 +217,6 @@ func (h *Handler) AcceptFriend(c echo.Context) (err error) {
224217
ErrorMessage("You can't add yourself"))
225218
}
226219

227-
_, exist := h.conf.GetPeer(req.PeerID)
228-
if exist {
229-
return c.JSON(http.StatusBadRequest, ErrorMessage("Peer has been already added"))
230-
}
231-
232220
authRequestsMap := h.authStatus.GetIngoingAuthRequests()
233221
auth, exist := authRequestsMap[req.PeerID]
234222
if !exist {
@@ -240,13 +228,11 @@ func (h *Handler) AcceptFriend(c echo.Context) (err error) {
240228
return c.NoContent(http.StatusOK)
241229
}
242230

243-
req.Alias = strings.TrimSpace(req.Alias)
244-
if !h.conf.IsUniqPeerAlias("", req.Alias) {
245-
return c.JSON(http.StatusBadRequest, ErrorMessage(ErrorPeerAliasIsNotUniq))
231+
err = h.authStatus.AddPeer(h.ctx, peerId, auth.Name, req.Alias, true, req.IPAddr)
232+
if err != nil {
233+
return c.JSON(http.StatusBadRequest, ErrorMessage(err.Error()))
246234
}
247235

248-
h.authStatus.AddPeer(h.ctx, peerId, auth.Name, req.Alias, true)
249-
250236
return c.NoContent(http.StatusOK)
251237
}
252238

@@ -259,10 +245,27 @@ func (h *Handler) AcceptFriend(c echo.Context) (err error) {
259245
func (h *Handler) GetAuthRequests(c echo.Context) (err error) {
260246
authRequestsMap := h.authStatus.GetIngoingAuthRequests()
261247
authRequests := make([]entity.AuthRequest, 0, len(authRequestsMap))
262-
for peerID, req := range authRequestsMap {
248+
249+
// Maintain peer IDs order
250+
peerIDs := make([]string, 0, len(authRequestsMap))
251+
for peerID := range authRequestsMap {
252+
peerIDs = append(peerIDs, peerID)
253+
}
254+
sort.Strings(peerIDs)
255+
generatedIPs := make([]string, 0, len(authRequestsMap))
256+
257+
h.conf.RLock()
258+
defer h.conf.RUnlock()
259+
260+
for _, peerID := range peerIDs {
261+
req := authRequestsMap[peerID]
262+
suggestedIP := h.conf.GenerateNextIpAddrExcept(generatedIPs)
263+
generatedIPs = append(generatedIPs, suggestedIP)
264+
263265
authRequests = append(authRequests, entity.AuthRequest{
264-
AuthPeer: req,
265-
PeerID: peerID,
266+
AuthPeer: req,
267+
PeerID: peerID,
268+
SuggestedIP: suggestedIP,
266269
})
267270
}
268271
return c.JSON(http.StatusOK, authRequests)

application_test.go

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"net/url"
1010
"runtime"
11+
"sort"
1112
"strings"
1213
"sync"
1314
"testing"
@@ -60,7 +61,7 @@ func TestRemovePeer(t *testing.T) {
6061
ts.Len(peer2.app.AuthStatus.GetIngoingAuthRequests(), 0)
6162

6263
// Add peer2 from peer1 - should succeed
63-
err = peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2")
64+
err = peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2", "")
6465
ts.NoError(err)
6566
time.Sleep(500 * time.Millisecond)
6667

@@ -93,7 +94,7 @@ func TestDeclinePeerFriendRequest(t *testing.T) {
9394
peer2 := ts.NewTestPeer(false)
9495
ts.ensurePeersAvailableInDHT(peer1, peer2)
9596

96-
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2")
97+
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2", "")
9798
ts.NoError(err)
9899

99100
var authRequests []entity.AuthRequest
@@ -102,7 +103,7 @@ func TestDeclinePeerFriendRequest(t *testing.T) {
102103
ts.NoError(err)
103104
return len(authRequests) == 1
104105
}, 15*time.Second, 50*time.Millisecond)
105-
err = peer2.api.ReplyFriendRequest(authRequests[0].PeerID, "peer_1", true)
106+
err = peer2.api.ReplyFriendRequest(authRequests[0].PeerID, "peer_1", true, "")
106107
ts.NoError(err)
107108

108109
time.Sleep(500 * time.Millisecond)
@@ -127,7 +128,7 @@ func TestAutoAcceptFriendRequest(t *testing.T) {
127128
peer2.app.Conf.P2pNode.AutoAcceptAuthRequests = true
128129
peer2.app.Conf.Unlock()
129130

130-
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2")
131+
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2", "")
131132
ts.NoError(err)
132133

133134
ts.Eventually(func() bool {
@@ -148,6 +149,105 @@ func TestAutoAcceptFriendRequest(t *testing.T) {
148149
ts.False(knownPeer.Declined)
149150
}
150151

152+
func TestFriendRequestWithCustomIP(t *testing.T) {
153+
ts := NewTestSuite(t)
154+
155+
peer1 := ts.NewTestPeer(false)
156+
peer2 := ts.NewTestPeer(false)
157+
peer3 := ts.NewTestPeer(false)
158+
ts.ensurePeersAvailableInDHT(peer1, peer2)
159+
ts.ensurePeersAvailableInDHT(peer1, peer3)
160+
161+
t.Run("InviteWithCustomIP", func(t *testing.T) {
162+
customIP := "10.66.0.222"
163+
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer_2", customIP)
164+
ts.NoError(err)
165+
166+
// Check immediate state on peer1
167+
p2, exists := peer1.app.Conf.GetPeer(peer2.PeerID())
168+
ts.True(exists)
169+
ts.Equal(customIP, p2.IPAddr)
170+
})
171+
172+
t.Run("RespondWithCustomIP", func(t *testing.T) {
173+
err := peer3.api.SendFriendRequest(peer1.PeerID(), "peer_1", "")
174+
ts.NoError(err)
175+
176+
var authRequests []entity.AuthRequest
177+
ts.Eventually(func() bool {
178+
authRequests, err = peer1.api.AuthRequests()
179+
ts.NoError(err)
180+
return len(authRequests) == 1
181+
}, 15*time.Second, 50*time.Millisecond)
182+
183+
customIP := "10.66.0.223"
184+
err = peer1.api.ReplyFriendRequest(authRequests[0].PeerID, "peer_3", false, customIP)
185+
ts.NoError(err)
186+
187+
p3, exists := peer1.app.Conf.GetPeer(peer3.PeerID())
188+
ts.True(exists)
189+
ts.Equal(customIP, p3.IPAddr)
190+
})
191+
192+
t.Run("InviteWithInvalidIP", func(t *testing.T) {
193+
peer4 := ts.NewTestPeer(false)
194+
ts.ensurePeersAvailableInDHT(peer1, peer4)
195+
196+
err := peer1.api.SendFriendRequest(peer4.PeerID(), "peer_4", "invalid-ip")
197+
ts.Error(err)
198+
ts.ErrorContains(err, "Field validation for 'IPAddr' failed")
199+
})
200+
201+
t.Run("InviteWithDuplicateIP", func(t *testing.T) {
202+
peer5 := ts.NewTestPeer(false)
203+
ts.ensurePeersAvailableInDHT(peer1, peer5)
204+
205+
// Try to use peer2's IP which is 10.66.0.222
206+
err := peer1.api.SendFriendRequest(peer5.PeerID(), "peer_5", "10.66.0.222")
207+
ts.Error(err)
208+
ts.ErrorContains(err, "ip 10.66.0.222 is already used by peer")
209+
})
210+
}
211+
212+
func TestGetAuthRequestsSuggestedIP(t *testing.T) {
213+
ts := NewTestSuite(t)
214+
215+
peer1 := ts.NewTestPeer(false)
216+
peer2 := ts.NewTestPeer(false)
217+
peer3 := ts.NewTestPeer(false)
218+
ts.ensurePeersAvailableInDHT(peer1, peer2)
219+
ts.ensurePeersAvailableInDHT(peer1, peer3)
220+
221+
err := peer2.api.SendFriendRequest(peer1.PeerID(), "peer_1", "")
222+
ts.NoError(err)
223+
time.Sleep(200 * time.Millisecond)
224+
225+
err = peer3.api.SendFriendRequest(peer1.PeerID(), "peer_1", "")
226+
ts.NoError(err)
227+
228+
ts.Eventually(func() bool {
229+
reqs, err := peer1.api.AuthRequests()
230+
ts.NoError(err)
231+
return len(reqs) == 2
232+
}, 15*time.Second, 50*time.Millisecond)
233+
234+
reqs, err := peer1.api.AuthRequests()
235+
ts.NoError(err)
236+
ts.Len(reqs, 2)
237+
238+
ips := []string{reqs[0].SuggestedIP, reqs[1].SuggestedIP}
239+
sort.Strings(ips)
240+
241+
ts.Equal("10.66.0.2", ips[0])
242+
ts.Equal("10.66.0.3", ips[1])
243+
244+
// Verify they are valid and unused
245+
err = peer1.app.Conf.CheckIPUnique(ips[0], "")
246+
ts.NoError(err)
247+
err = peer1.app.Conf.CheckIPUnique(ips[1], "")
248+
ts.NoError(err)
249+
}
250+
151251
func TestUniquePeerAlias(t *testing.T) {
152252
ts := NewTestSuite(t)
153253

@@ -157,12 +257,12 @@ func TestUniquePeerAlias(t *testing.T) {
157257
ts.ensurePeersAvailableInDHT(peer1, peer2)
158258
ts.ensurePeersAvailableInDHT(peer2, peer3)
159259

160-
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer")
260+
err := peer1.api.SendFriendRequest(peer2.PeerID(), "peer", "")
161261
ts.NoError(err)
162262

163263
time.Sleep(200 * time.Millisecond)
164264

165-
err = peer1.api.SendFriendRequest(peer3.PeerID(), "peer")
265+
err = peer1.api.SendFriendRequest(peer3.PeerID(), "peer", "")
166266
ts.EqualError(err, api.ErrorPeerAliasIsNotUniq)
167267
}
168268

@@ -300,7 +400,7 @@ func TestUpdatePeerSettingsIPAddr(t *testing.T) {
300400
// Make peer3 and peer1 friends (manual to use unique alias "peer_3")
301401
// TODO: refactor makeFriends helper to accept alias arg
302402
ts.ensurePeersAvailableInDHT(peer3, peer1)
303-
err := peer3.api.SendFriendRequest(peer1.PeerID(), "peer_1")
403+
err := peer3.api.SendFriendRequest(peer1.PeerID(), "peer_1", "")
304404
ts.NoError(err)
305405

306406
var authRequests []entity.AuthRequest
@@ -309,7 +409,7 @@ func TestUpdatePeerSettingsIPAddr(t *testing.T) {
309409
ts.NoError(err)
310410
return len(authRequests) == 1
311411
}, 15*time.Second, 50*time.Millisecond)
312-
err = peer1.api.ReplyFriendRequest(authRequests[0].PeerID, "peer_3", false)
412+
err = peer1.api.ReplyFriendRequest(authRequests[0].PeerID, "peer_3", false, "")
313413
ts.NoError(err)
314414

315415
time.Sleep(500 * time.Millisecond)

cli/cli.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,15 @@ func (a *Application) init() {
194194
Usage: "peer name",
195195
Required: true,
196196
},
197+
&cli.StringFlag{
198+
Name: "ip",
199+
Usage: "override peer IP address",
200+
Required: false,
201+
},
197202
},
198203
Before: a.initApiConnection,
199204
Action: func(c *cli.Context) error {
200-
return addPeer(a.api, c.String("pid"), c.String("name"))
205+
return addPeer(a.api, c.String("pid"), c.String("name"), c.String("ip"))
201206
},
202207
},
203208
{

cli/peers.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func printFriendRequests(api *apiclient.Client) error {
144144
return nil
145145
}
146146
for _, req := range authRequests {
147-
fmt.Printf("Name: '%s' peerID: %s\n", req.Name, req.PeerID)
147+
fmt.Printf("Name: '%s' peerID: %s suggestedIP: %s\n", req.Name, req.PeerID, req.SuggestedIP)
148148
}
149149

150150
return nil
@@ -168,7 +168,7 @@ func getPeerIdByAlias(api *apiclient.Client, alias string) (string, error) {
168168
return "", fmt.Errorf("can't find peer with name \"%s\"", alias)
169169
}
170170

171-
func addPeer(api *apiclient.Client, peerID, alias string) error {
171+
func addPeer(api *apiclient.Client, peerID, alias, ipAddr string) error {
172172
authRequests, err := api.AuthRequests()
173173
if err != nil {
174174
return err
@@ -181,7 +181,7 @@ func addPeer(api *apiclient.Client, peerID, alias string) error {
181181
}
182182
}
183183
if hasRequest {
184-
err := api.ReplyFriendRequest(peerID, alias, false)
184+
err := api.ReplyFriendRequest(peerID, alias, false, ipAddr)
185185
if err != nil {
186186
return err
187187
}
@@ -190,7 +190,7 @@ func addPeer(api *apiclient.Client, peerID, alias string) error {
190190
return nil
191191
}
192192

193-
err = api.SendFriendRequest(peerID, alias)
193+
err = api.SendFriendRequest(peerID, alias, ipAddr)
194194
if err != nil {
195195
return err
196196
}

config/network_addr.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ func (c *Config) VPNLocalIPMaskUnlocked() (net.IP, net.IPMask) {
3131

3232
// GenerateNextIpAddr is not thread safe.
3333
func (c *Config) GenerateNextIpAddr() string {
34+
return c.GenerateNextIpAddrExcept(nil)
35+
}
36+
37+
// GenerateNextIpAddrExcept is not thread safe.
38+
func (c *Config) GenerateNextIpAddrExcept(except []string) string {
3439
localIP, netMask := c.VPNLocalIPMaskUnlocked()
3540
ipNet := &net.IPNet{
3641
IP: localIP.Mask(netMask),
3742
Mask: netMask,
3843
}
44+
3945
maxIp := localIP
4046
for _, known := range c.KnownPeers {
4147
ip := net.ParseIP(known.IPAddr)
@@ -50,9 +56,22 @@ func (c *Config) GenerateNextIpAddr() string {
5056
}
5157
}
5258

53-
newIp := incrementIPAddr(maxIp)
59+
exceptMap := make(map[string]struct{}, len(except))
60+
for _, ip := range except {
61+
exceptMap[ip] = struct{}{}
62+
}
63+
64+
// Find next available IP that is not in exceptMap
65+
for {
66+
newIp := incrementIPAddr(maxIp)
67+
newIpStr := newIp.String()
68+
69+
if _, excluded := exceptMap[newIpStr]; !excluded {
70+
return newIpStr
71+
}
5472

55-
return newIp.String()
73+
maxIp = newIp
74+
}
5675
}
5776

5877
// CheckIPUnique is not thread safe.

0 commit comments

Comments
 (0)