1// +build linux
2// +build 386 amd64
3
4/*
5 *
6 * Copyright 2018 gRPC authors.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 */
21
22// SocketOptions is only supported on linux system. The functions defined in
23// this file are to parse the socket option field and the test is specifically
24// to verify the behavior of socket option parsing.
25
26package service
27
28import (
29	"context"
30	"reflect"
31	"strconv"
32	"testing"
33
34	"github.com/golang/protobuf/ptypes"
35	durpb "github.com/golang/protobuf/ptypes/duration"
36	"golang.org/x/sys/unix"
37	channelzpb "google.golang.org/grpc/channelz/grpc_channelz_v1"
38	"google.golang.org/grpc/internal/channelz"
39)
40
41func init() {
42	// Assign protoToSocketOption to protoToSocketOpt in order to enable socket option
43	// data conversion from proto message to channelz defined struct.
44	protoToSocketOpt = protoToSocketOption
45}
46
47func convertToDuration(d *durpb.Duration) (sec int64, usec int64) {
48	if d != nil {
49		if dur, err := ptypes.Duration(d); err == nil {
50			sec = int64(int64(dur) / 1e9)
51			usec = (int64(dur) - sec*1e9) / 1e3
52		}
53	}
54	return
55}
56
57func protoToLinger(protoLinger *channelzpb.SocketOptionLinger) *unix.Linger {
58	linger := &unix.Linger{}
59	if protoLinger.GetActive() {
60		linger.Onoff = 1
61	}
62	lv, _ := convertToDuration(protoLinger.GetDuration())
63	linger.Linger = int32(lv)
64	return linger
65}
66
67func protoToSocketOption(skopts []*channelzpb.SocketOption) *channelz.SocketOptionData {
68	skdata := &channelz.SocketOptionData{}
69	for _, opt := range skopts {
70		switch opt.GetName() {
71		case "SO_LINGER":
72			protoLinger := &channelzpb.SocketOptionLinger{}
73			err := ptypes.UnmarshalAny(opt.GetAdditional(), protoLinger)
74			if err == nil {
75				skdata.Linger = protoToLinger(protoLinger)
76			}
77		case "SO_RCVTIMEO":
78			protoTimeout := &channelzpb.SocketOptionTimeout{}
79			err := ptypes.UnmarshalAny(opt.GetAdditional(), protoTimeout)
80			if err == nil {
81				skdata.RecvTimeout = protoToTime(protoTimeout)
82			}
83		case "SO_SNDTIMEO":
84			protoTimeout := &channelzpb.SocketOptionTimeout{}
85			err := ptypes.UnmarshalAny(opt.GetAdditional(), protoTimeout)
86			if err == nil {
87				skdata.SendTimeout = protoToTime(protoTimeout)
88			}
89		case "TCP_INFO":
90			tcpi := &channelzpb.SocketOptionTcpInfo{}
91			err := ptypes.UnmarshalAny(opt.GetAdditional(), tcpi)
92			if err == nil {
93				skdata.TCPInfo = &unix.TCPInfo{
94					State:          uint8(tcpi.TcpiState),
95					Ca_state:       uint8(tcpi.TcpiCaState),
96					Retransmits:    uint8(tcpi.TcpiRetransmits),
97					Probes:         uint8(tcpi.TcpiProbes),
98					Backoff:        uint8(tcpi.TcpiBackoff),
99					Options:        uint8(tcpi.TcpiOptions),
100					Rto:            tcpi.TcpiRto,
101					Ato:            tcpi.TcpiAto,
102					Snd_mss:        tcpi.TcpiSndMss,
103					Rcv_mss:        tcpi.TcpiRcvMss,
104					Unacked:        tcpi.TcpiUnacked,
105					Sacked:         tcpi.TcpiSacked,
106					Lost:           tcpi.TcpiLost,
107					Retrans:        tcpi.TcpiRetrans,
108					Fackets:        tcpi.TcpiFackets,
109					Last_data_sent: tcpi.TcpiLastDataSent,
110					Last_ack_sent:  tcpi.TcpiLastAckSent,
111					Last_data_recv: tcpi.TcpiLastDataRecv,
112					Last_ack_recv:  tcpi.TcpiLastAckRecv,
113					Pmtu:           tcpi.TcpiPmtu,
114					Rcv_ssthresh:   tcpi.TcpiRcvSsthresh,
115					Rtt:            tcpi.TcpiRtt,
116					Rttvar:         tcpi.TcpiRttvar,
117					Snd_ssthresh:   tcpi.TcpiSndSsthresh,
118					Snd_cwnd:       tcpi.TcpiSndCwnd,
119					Advmss:         tcpi.TcpiAdvmss,
120					Reordering:     tcpi.TcpiReordering}
121			}
122		}
123	}
124	return skdata
125}
126
127func (s) TestGetSocketOptions(t *testing.T) {
128	czCleanup := channelz.NewChannelzStorage()
129	defer cleanupWrapper(czCleanup, t)
130	ss := []*dummySocket{
131		{
132			socketOptions: &channelz.SocketOptionData{
133				Linger:      &unix.Linger{Onoff: 1, Linger: 2},
134				RecvTimeout: &unix.Timeval{Sec: 10, Usec: 1},
135				SendTimeout: &unix.Timeval{},
136				TCPInfo:     &unix.TCPInfo{State: 1},
137			},
138		},
139	}
140	svr := newCZServer()
141	ids := make([]int64, len(ss))
142	svrID := channelz.RegisterServer(&dummyServer{}, "")
143	defer channelz.RemoveEntry(svrID)
144	for i, s := range ss {
145		ids[i] = channelz.RegisterNormalSocket(s, svrID, strconv.Itoa(i))
146		defer channelz.RemoveEntry(ids[i])
147	}
148	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
149	defer cancel()
150	for i, s := range ss {
151		resp, _ := svr.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: ids[i]})
152		metrics := resp.GetSocket()
153		if !reflect.DeepEqual(metrics.GetRef(), &channelzpb.SocketRef{SocketId: ids[i], Name: strconv.Itoa(i)}) || !reflect.DeepEqual(socketProtoToStruct(metrics), s) {
154			t.Fatalf("resp.GetSocket() want: metrics.GetRef() = %#v and %#v, got: metrics.GetRef() = %#v and %#v", &channelzpb.SocketRef{SocketId: ids[i], Name: strconv.Itoa(i)}, s, metrics.GetRef(), socketProtoToStruct(metrics))
155		}
156	}
157}
158