client_linux.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //go:build linux
  2. // +build linux
  3. package wglinux
  4. import (
  5. "errors"
  6. "fmt"
  7. "os"
  8. "syscall"
  9. "github.com/mdlayher/genetlink"
  10. "github.com/mdlayher/netlink"
  11. "github.com/mdlayher/netlink/nlenc"
  12. "golang.org/x/sys/unix"
  13. "golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
  14. "golang.zx2c4.com/wireguard/wgctrl/internal/wglinux/internal/wgh"
  15. "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
  16. )
  17. var _ wginternal.Client = &Client{}
  18. // A Client provides access to Linux WireGuard netlink information.
  19. type Client struct {
  20. c *genetlink.Conn
  21. family genetlink.Family
  22. interfaces func() ([]string, error)
  23. }
  24. // New creates a new Client and returns whether or not the generic netlink
  25. // interface is available.
  26. func New() (*Client, bool, error) {
  27. c, err := genetlink.Dial(nil)
  28. if err != nil {
  29. return nil, false, err
  30. }
  31. return initClient(c)
  32. }
  33. // initClient is the internal Client constructor used in some tests.
  34. func initClient(c *genetlink.Conn) (*Client, bool, error) {
  35. f, err := c.GetFamily(wgh.GenlName)
  36. if err != nil {
  37. _ = c.Close()
  38. if errors.Is(err, os.ErrNotExist) {
  39. // The generic netlink interface is not available.
  40. return nil, false, nil
  41. }
  42. return nil, false, err
  43. }
  44. return &Client{
  45. c: c,
  46. family: f,
  47. // By default, gather only WireGuard interfaces using rtnetlink.
  48. interfaces: rtnlInterfaces,
  49. }, true, nil
  50. }
  51. // Close implements wginternal.Client.
  52. func (c *Client) Close() error {
  53. return c.c.Close()
  54. }
  55. // Devices implements wginternal.Client.
  56. func (c *Client) Devices() ([]*wgtypes.Device, error) {
  57. // By default, rtnetlink is used to fetch a list of all interfaces and then
  58. // filter that list to only find WireGuard interfaces.
  59. //
  60. // The remainder of this function assumes that any returned device from this
  61. // function is a valid WireGuard device.
  62. ifis, err := c.interfaces()
  63. if err != nil {
  64. return nil, err
  65. }
  66. ds := make([]*wgtypes.Device, 0, len(ifis))
  67. for _, ifi := range ifis {
  68. d, err := c.Device(ifi)
  69. if err != nil {
  70. return nil, err
  71. }
  72. ds = append(ds, d)
  73. }
  74. return ds, nil
  75. }
  76. // Device implements wginternal.Client.
  77. func (c *Client) Device(name string) (*wgtypes.Device, error) {
  78. // Don't bother querying netlink with empty input.
  79. if name == "" {
  80. return nil, os.ErrNotExist
  81. }
  82. // Fetching a device by interface index is possible as well, but we only
  83. // support fetching by name as it seems to be more convenient in general.
  84. b, err := netlink.MarshalAttributes([]netlink.Attribute{{
  85. Type: wgh.DeviceAIfname,
  86. Data: nlenc.Bytes(name),
  87. }})
  88. if err != nil {
  89. return nil, err
  90. }
  91. msgs, err := c.execute(wgh.CmdGetDevice, netlink.Request|netlink.Dump, b)
  92. if err != nil {
  93. return nil, err
  94. }
  95. return parseDevice(msgs)
  96. }
  97. // ConfigureDevice implements wginternal.Client.
  98. func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
  99. // Large configurations are split into batches for use with netlink.
  100. for _, b := range buildBatches(cfg) {
  101. attrs, err := configAttrs(name, b)
  102. if err != nil {
  103. return err
  104. }
  105. // Request acknowledgement of our request from netlink, even though the
  106. // output messages are unused. The netlink package checks and trims the
  107. // status code value.
  108. if _, err := c.execute(wgh.CmdSetDevice, netlink.Request|netlink.Acknowledge, attrs); err != nil {
  109. return err
  110. }
  111. }
  112. return nil
  113. }
  114. // execute executes a single WireGuard netlink request with the specified command,
  115. // header flags, and attribute arguments.
  116. func (c *Client) execute(command uint8, flags netlink.HeaderFlags, attrb []byte) ([]genetlink.Message, error) {
  117. msg := genetlink.Message{
  118. Header: genetlink.Header{
  119. Command: command,
  120. Version: wgh.GenlVersion,
  121. },
  122. Data: attrb,
  123. }
  124. msgs, err := c.c.Execute(msg, c.family.ID, flags)
  125. if err == nil {
  126. return msgs, nil
  127. }
  128. // We don't want to expose netlink errors directly to callers so unpack to
  129. // something more generic.
  130. oerr, ok := err.(*netlink.OpError)
  131. if !ok {
  132. // Expect all errors to conform to netlink.OpError.
  133. return nil, fmt.Errorf("wglinux: netlink operation returned non-netlink error (please file a bug: https://golang.zx2c4.com/wireguard/wgctrl): %v", err)
  134. }
  135. switch oerr.Err {
  136. // Convert "no such device" and "not a wireguard device" to an error
  137. // compatible with os.ErrNotExist for easy checking.
  138. case unix.ENODEV, unix.ENOTSUP:
  139. return nil, os.ErrNotExist
  140. default:
  141. // Expose the inner error directly (such as EPERM).
  142. return nil, oerr.Err
  143. }
  144. }
  145. // rtnlInterfaces uses rtnetlink to fetch a list of WireGuard interfaces.
  146. func rtnlInterfaces() ([]string, error) {
  147. // Use the stdlib's rtnetlink helpers to get ahold of a table of all
  148. // interfaces, so we can begin filtering it down to just WireGuard devices.
  149. tab, err := syscall.NetlinkRIB(unix.RTM_GETLINK, unix.AF_UNSPEC)
  150. if err != nil {
  151. return nil, fmt.Errorf("wglinux: failed to get list of interfaces from rtnetlink: %v", err)
  152. }
  153. msgs, err := syscall.ParseNetlinkMessage(tab)
  154. if err != nil {
  155. return nil, fmt.Errorf("wglinux: failed to parse rtnetlink messages: %v", err)
  156. }
  157. return parseRTNLInterfaces(msgs)
  158. }
  159. // parseRTNLInterfaces unpacks rtnetlink messages and returns WireGuard
  160. // interface names.
  161. func parseRTNLInterfaces(msgs []syscall.NetlinkMessage) ([]string, error) {
  162. var ifis []string
  163. for _, m := range msgs {
  164. // Only deal with link messages, and they must have an ifinfomsg
  165. // structure appear before the attributes.
  166. if m.Header.Type != unix.RTM_NEWLINK {
  167. continue
  168. }
  169. if len(m.Data) < unix.SizeofIfInfomsg {
  170. return nil, fmt.Errorf("wglinux: rtnetlink message is too short for ifinfomsg: %d", len(m.Data))
  171. }
  172. ad, err := netlink.NewAttributeDecoder(m.Data[syscall.SizeofIfInfomsg:])
  173. if err != nil {
  174. return nil, err
  175. }
  176. // Determine the interface's name and if it's a WireGuard device.
  177. var (
  178. ifi string
  179. isWG bool
  180. )
  181. for ad.Next() {
  182. switch ad.Type() {
  183. case unix.IFLA_IFNAME:
  184. ifi = ad.String()
  185. case unix.IFLA_LINKINFO:
  186. ad.Do(isWGKind(&isWG))
  187. }
  188. }
  189. if err := ad.Err(); err != nil {
  190. return nil, err
  191. }
  192. if isWG {
  193. // Found one; append it to the list.
  194. ifis = append(ifis, ifi)
  195. }
  196. }
  197. return ifis, nil
  198. }
  199. // wgKind is the IFLA_INFO_KIND value for WireGuard devices.
  200. const wgKind = "wireguard"
  201. // isWGKind parses netlink attributes to determine if a link is a WireGuard
  202. // device, then populates ok with the result.
  203. func isWGKind(ok *bool) func(b []byte) error {
  204. return func(b []byte) error {
  205. ad, err := netlink.NewAttributeDecoder(b)
  206. if err != nil {
  207. return err
  208. }
  209. for ad.Next() {
  210. if ad.Type() != unix.IFLA_INFO_KIND {
  211. continue
  212. }
  213. if ad.String() == wgKind {
  214. *ok = true
  215. return nil
  216. }
  217. }
  218. return ad.Err()
  219. }
  220. }