Skip to content

Commit

Permalink
Merge pull request #316 from Jigsaw-Code/fortuna-opts
Browse files Browse the repository at this point in the history
feat: create opts package
  • Loading branch information
fortuna authored Nov 7, 2024
2 parents dc0a0b0 + b7aaba9 commit 111d01c
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 0 deletions.
91 changes: 91 additions & 0 deletions x/sockopt/sockopt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sockopt provides cross-platform ways to interact with socket options.
package sockopt

import (
"fmt"
"net"
"net/netip"

"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)

// HasHopLimit enables manipulation of the hop limit option.
type HasHopLimit interface {
// HopLimit returns the hop limit field value for outgoing packets.
HopLimit() (int, error)
// SetHopLimit sets the hop limit field value for future outgoing packets.
SetHopLimit(hoplim int) error
}

// hopLimitOption implements HasHopLimit.
type hopLimitOption struct {
hopLimit func() (int, error)
setHopLimit func(hoplim int) error
}

func (o *hopLimitOption) HopLimit() (int, error) {
return o.hopLimit()
}

func (o *hopLimitOption) SetHopLimit(hoplim int) error {
return o.setHopLimit(hoplim)
}

var _ HasHopLimit = (*hopLimitOption)(nil)

// TCPOptions represents options for TCP connections.
type TCPOptions interface {
HasHopLimit
}

type tcpOptions struct {
hopLimitOption
}

var _ TCPOptions = (*tcpOptions)(nil)

// newHopLimit creates a hopLimitOption from a [net.Conn]. Works for both TCP or UDP.
func newHopLimit(conn net.Conn) (*hopLimitOption, error) {
addr, err := netip.ParseAddrPort(conn.LocalAddr().String())
if err != nil {
return nil, err
}
opt := &hopLimitOption{}
switch {
case addr.Addr().Is4():
ipConn := ipv4.NewConn(conn)
opt.hopLimit = ipConn.TTL
opt.setHopLimit = ipConn.SetTTL
case addr.Addr().Is6():
ipConn := ipv6.NewConn(conn)
opt.hopLimit = ipConn.HopLimit
opt.setHopLimit = ipConn.SetHopLimit
default:
return nil, fmt.Errorf("address is not IPv4 or IPv6 (%v)", addr.Addr().String())
}
return opt, nil
}

// NewTCPOptions creates a [TCPOptions] for the given [net.TCPConn].
func NewTCPOptions(conn *net.TCPConn) (TCPOptions, error) {
hopLimit, err := newHopLimit(conn)
if err != nil {
return nil, err
}
return &tcpOptions{hopLimitOption: *hopLimit}, nil
}
54 changes: 54 additions & 0 deletions x/sockopt/sockopt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2024 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sockopt

import (
"net"
"testing"

"github.com/stretchr/testify/require"
)

func TestTCPOptions(t *testing.T) {
type Params struct {
Net string
Addr string
}
for _, params := range []Params{{Net: "tcp4", Addr: "127.0.0.1:0"}, {Net: "tcp6", Addr: "[::1]:0"}} {
l, err := net.Listen(params.Net, params.Addr)
require.NoError(t, err)
defer l.Close()

conn, err := net.Dial("tcp", l.Addr().String())
require.NoError(t, err)
tcpConn, ok := conn.(*net.TCPConn)
require.True(t, ok)

opts, err := NewTCPOptions(tcpConn)
require.NoError(t, err)

require.NoError(t, opts.SetHopLimit(1))

hoplim, err := opts.HopLimit()
require.NoError(t, err)
require.Equal(t, 1, hoplim)

require.NoError(t, opts.SetHopLimit(20))

hoplim, err = opts.HopLimit()
require.NoError(t, err)
require.Equal(t, 20, hoplim)
}
}

0 comments on commit 111d01c

Please sign in to comment.