草庐IT

c - Linux : When sending Ethernet frames the ethertype is being re-written

coder 2023-06-18 原文

我编写了一个C程序,将以太网帧直接写到电线上(以两种模式运行,即发送者或接收者)。 发送方正在发送带有两个VLAN标签的帧(QinQ),但是奇怪的是,当帧到达接收方时,以太类型已更改为标准(单个)VLAN封装帧的类型。 NIC是否可能这样做,或者Linux不允许这样做? Wireshark显示与tcpdump相同的行为。

为了说明下图,发送方正在将帧发送到以太网广播地址FF:FF:FF:FF:FF:FF以找到接收方(这是两台通过交叉电缆连接的测试机,但是下面的结果与交换机或集线器)。可以看到帧带有两个VLAN标记,外部标记的以太类型为0x8100,VLAN ID为40,内部VLAN的以太类型为0x8100,VLAN ID为20。众所周知,使用QinQ框架时,外部框架的以太类型应该为0x88a8!

当在我的应用程序中从发件人发送帧时,它们确实具有外部以太类型0x88a8,但如下图所示,内部和外部以太类型都以0x8100接收它们。突出显示的文本是接收方发回的答复,因为您可以看到外部框架的帧为0x88a8,内部框架的帧为0x8100。另一台计算机上的tcpdump显示相同(代码相同!发送帧时使用0x88a8外部0x8100内部,但始终以0x8100外部和0x8100内部接收)。

void BuildHeaders(char* &txBuffer, unsigned char (&destMAC)[6], 
     unsigned char (&sourceMAC)[6], short &PCP, short &vlanID,
     short &qinqID, short &qinqPCP, int &headersLength)
{

int offset = 0;

short TPI = 0;
short TCI = 0;
short *p = &TPI;
short *c = &TCI;
short vlanIDtemp;

// Copy the destination and source MAC addresses
memcpy((void*)txBuffer, (void*)destMAC, ETH_ALEN);
memcpy((void*)(txBuffer+ETH_ALEN), (void*)sourceMAC, ETH_ALEN);
offset = (ETH_ALEN*2);

// Add on the QinQ Tag Protocol Identifier
vlanIDtemp = qinq
TPI = htons(0x88a8); //0x88a8 == IEEE802.1ad, 0x9100 == older IEEE802.1QinQ
memcpy((void*)(txBuffer+offset), p, 2);
offset+=2;

// Now build the QinQ Tag Control Identifier:
TCI = (qinqPCP & 0x07) << 5;
qinqID = qinqID >> 8;
TCI = TCI | (qinqID & 0x0f);
qinqID = vlanIDtemp;
qinqID = qinqID << 8;
TCI = TCI | (qinqID & 0xffff);

memcpy((void*)(txBuffer+offset), c, 2);
offset+=2;

// VLAN headers
vlanIDtemp = vlanID;
TPI = htons(0x8100);
memcpy((void*)(txBuffer+offset), p, 2);
offset+=2;

TCI = (PCP & 0x07) << 5;
vlanID = vlanID >> 8;
TCI = TCI | (vlanID & 0x0f);
vlanID = vlanIDtemp;
vlanID = vlanID << 8;
TCI = TCI | (vlanID & 0xffff);

memcpy((void*)(txBuffer+offset), c, 2);
offset+=2;

// Push on the Ethertype (IPv4) for the payload
TPI = htons(0x0800);
memcpy((void*)(txBuffer+offset), p, 2);
offset+=2;

headersLength = offset;

}

sendResult = sendto(sockFD, txBuffer, fSizeTotal, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));

最佳答案

(完全重写以简化答案。我还修复了下面列出的C头文件和源文件中的许多错误。)

2014年4月在about exactly this上进行了讨论oj​​it_a,主题为“802.1AD数据包-内核将所有数据包的以太类型从88A8更改为8100”。

事实证明,内核不会更改以太类型,它只是在接收到数据包时消耗它。我在下面显示,考虑到内核足够近,它可以正确地用于VLAN路由(包括用于802.1AD和802.1Q VLAN的单独规则)。即使VLAN标记未用于路由(例如,如果未配置VLAN,或者未加载8021q内核模块),VLAN标记也会被内核占用。

因此,最初的问题“在发送以太网帧时,正在重写以太类型”,这是不正确的:未重写以太类型。它由内核使用。

因为VLAN标记由内核使用,所以libpcap -这是tcpdump,wireshark等人使用的数据包捕获库。 -尝试将其重新引入包头中。不幸的是,它始终使用802.1Q VLAN header (8100)。

libpcap有一个linux-netdev mailing list可以完全解决libpcap中的这个问题,但是在撰写本文时,它似乎尚未包含在内。您仍然可以在suggested change的多个位置看到htons(ETH_P_8021Q)硬编码。

我不能认为您会信守 promise ,所以让我向您展示如何自己确定这一点。

让我们编写一个简单的数据包发送方和接收方,它们直接使用内核接口(interface),而无需libpcap的帮助。

rawpacket.h:

#ifndef   RAWPACKET_H
#define   RAWPACKET_H
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static int rawpacket_socket(const int protocol,
                            const char *const interface,
                            void *const hwaddr)
{
    struct ifreq        iface;
    struct sockaddr_ll  addr;
    int                 socketfd, result;
    int                 ifindex = 0;

    if (!interface || !*interface) {
        errno = EINVAL;
        return -1;
    }

    socketfd = socket(AF_PACKET, SOCK_RAW, htons(protocol));
    if (socketfd == -1)
        return -1;

    do {

        memset(&iface, 0, sizeof iface);
        strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
        result = ioctl(socketfd, SIOCGIFINDEX, &iface);
        if (result == -1)
            break;
        ifindex = iface.ifr_ifindex;

        memset(&iface, 0, sizeof iface);
        strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
        result = ioctl(socketfd, SIOCGIFFLAGS, &iface);
        if (result == -1)
            break;
        iface.ifr_flags |= IFF_PROMISC;
        result = ioctl(socketfd, SIOCSIFFLAGS, &iface);
        if (result == -1)
            break;

        memset(&iface, 0, sizeof iface);
        strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
        result = ioctl(socketfd, SIOCGIFHWADDR, &iface);
        if (result == -1)
            break;

        memset(&addr, 0, sizeof addr);
        addr.sll_family = AF_PACKET;
        addr.sll_protocol = htons(protocol);
        addr.sll_ifindex = ifindex;
        addr.sll_hatype = 0;
        addr.sll_pkttype = 0;
        addr.sll_halen = ETH_ALEN; /* Assume ethernet! */
        memcpy(&addr.sll_addr, &iface.ifr_hwaddr.sa_data, addr.sll_halen);
        if (hwaddr)
            memcpy(hwaddr, &iface.ifr_hwaddr.sa_data, ETH_ALEN);

        if (bind(socketfd, (struct sockaddr *)&addr, sizeof addr))
            break;

        errno = 0;
        return socketfd;

    } while (0);

    {
        const int saved_errno = errno;
        close(socketfd);
        errno = saved_errno;
        return -1;
    }
}

static unsigned int tci(const unsigned int priority,
                        const unsigned int drop,
                        const unsigned int vlan)
{
    return (vlan & 0xFFFU)
         | ((!!drop) << 12U)
         | ((priority & 7U) << 13U);
}

static size_t rawpacket_qinq(unsigned char *const buffer, size_t const length,
                             const unsigned char *const srcaddr,
                             const unsigned char *const dstaddr,
                             const unsigned int service_tci,
                             const unsigned int customer_tci,
                             const unsigned int ethertype)
{
    unsigned char *ptr = buffer;
    uint32_t       tag;
    uint16_t       type;

    if (length < 2 * ETH_ALEN + 4 + 4 + 2) {
        errno = ENOSPC;
        return (size_t)0;
    }

    memcpy(ptr, dstaddr, ETH_ALEN);
    ptr += ETH_ALEN;

    memcpy(ptr, srcaddr, ETH_ALEN);
    ptr += ETH_ALEN;

    /* Service 802.1AD tag. */
    tag = htonl( ((uint32_t)(ETH_P_8021AD) << 16U)
               | ((uint32_t)service_tci & 0xFFFFU) );
    memcpy(ptr, &tag, 4);
    ptr += 4;

    /* Client 802.1Q tag. */
    tag = htonl( ((uint32_t)(ETH_P_8021Q) << 16U)
               | ((uint32_t)customer_tci & 0xFFFFU) );
    memcpy(ptr, &tag, 4);
    ptr += 4;

    /* Ethertype tag. */
    type = htons((uint16_t)ethertype);
    memcpy(ptr, &type, 2);
    ptr += 2;

    return (size_t)(ptr - buffer);
}

#endif /* RAWPACKET_H */

sender.c:
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "rawpacket.h"

static size_t parse_data(unsigned char *const data, const size_t size,
                         const char *const string)
{
    char *ends = strncpy((char *)data, string, size);
    return (size_t)(ends - (char *)data);
}


static int parse_hwaddr(const char *const string,
                        void *const hwaddr)
{
    unsigned int addr[6];
    char         dummy;

    if (sscanf(string, " %02x:%02x:%02x:%02x:%02x:%02x %c",
                       &addr[0], &addr[1], &addr[2],
                       &addr[3], &addr[4], &addr[5],
                       &dummy) == 6 ||
        sscanf(string, " %02x%02x%02x%02x%02x%02x %c",
                       &addr[0], &addr[1], &addr[2],
                       &addr[3], &addr[4], &addr[5],
                       &dummy) == 6) {
        if (hwaddr) {
            ((unsigned char *)hwaddr)[0] = addr[0];
            ((unsigned char *)hwaddr)[1] = addr[1];
            ((unsigned char *)hwaddr)[2] = addr[2];
            ((unsigned char *)hwaddr)[3] = addr[3];
            ((unsigned char *)hwaddr)[4] = addr[4];
            ((unsigned char *)hwaddr)[5] = addr[5];
        }
        return 0;
    }

    errno = EINVAL;
    return -1;
}

int main(int argc, char *argv[])
{
    unsigned char packet[ETH_FRAME_LEN + ETH_FCS_LEN];
    unsigned char srcaddr[6], dstaddr[6];
    int           socketfd;
    size_t        size, i;
    ssize_t       n;

    if (argc < 3 || argc > 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s interface hwaddr [message]\n", argv[0]);
        fprintf(stderr, "\n");
        return 1;
    }

    if (parse_hwaddr(argv[2], &dstaddr)) {
        fprintf(stderr, "%s: Invalid destination hardware address.\n", argv[2]);
        return 1;
    }

    socketfd = rawpacket_socket(ETH_P_ALL, argv[1], &srcaddr);
    if (socketfd == -1) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return 1;
    }

    memset(packet, 0, sizeof packet);

    /* Construct a QinQ header for a fake Ethernet packet type. */
    size = rawpacket_qinq(packet, sizeof packet, srcaddr, dstaddr,
                                  tci(7, 0, 1U), tci(7, 0, 2U),
                                  ETH_P_IP);
    if (!size) {
        fprintf(stderr, "Failed to construct QinQ headers: %s.\n", strerror(errno));
        close(socketfd);
        return 1;
    }

    /* Add packet payload. */
    if (argc > 3)
        size += parse_data(packet + size, sizeof packet - size, argv[3]);
    else
        size += parse_data(packet + size, sizeof packet - size, "Hello!");

    /* Pad with zeroes to minimum 64 octet length. */
    if (size < 64)
        size = 64;

    /* Send it. */
    n = send(socketfd, packet, size, 0);
    if (n == -1) {
        fprintf(stderr, "Failed to send packet: %s.\n", strerror(errno));
        shutdown(socketfd, SHUT_RDWR);
        close(socketfd);
        return 1;
    }

    fprintf(stderr, "Sent %ld bytes:", (long)n);
    for (i = 0; i < size; i++)
        fprintf(stderr, " %02x", packet[i]);
    fprintf(stderr, "\n");
    fflush(stderr);

    shutdown(socketfd, SHUT_RDWR);
    if (close(socketfd)) {
        fprintf(stderr, "Error closing socket: %s.\n", strerror(errno));
        return 1;
    }

    return 0;
}

receiver.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include "rawpacket.h"

static volatile sig_atomic_t  done = 0;

static void handle_done(int signum)
{
    done = signum;
}

static int install_done(const int signum)
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL))
        return errno;
    return 0;
}

static const char *protocol_name(const unsigned int protocol)
{
    static char buffer[16];
    switch (protocol & 0xFFFFU) {
    case 0x0001: return "ETH_P_802_3";
    case 0x0002: return "ETH_P_AX25";
    case 0x0003: return "ETH_P_ALL";
    case 0x0060: return "ETH_P_LOOP";
    case 0x0800: return "ETH_P_IP";
    case 0x0806: return "ETH_P_ARP";
    case 0x8100: return "ETH_P_8021Q (802.1Q VLAN)";
    case 0x88A8: return "ETH_P_8021AD (802.1AD VLAN)";
    default:
        snprintf(buffer, sizeof buffer, "0x%04x", protocol & 0xFFFFU);
        return (const char *)buffer;
    }
}

static const char *header_type(const unsigned int hatype)
{
    static char buffer[16];
    switch (hatype) {
    case   1: return "ARPHRD_ETHER: Ethernet 10Mbps";
    case   2: return "ARPHRD_EETHER: Experimental Ethernet";
    case 768: return "ARPHRD_TUNNEL: IP Tunnel";
    case 772: return "ARPHRD_LOOP: Loopback";
    default:
        snprintf(buffer, sizeof buffer, "0x%04x", hatype);
        return buffer;
    }
}

static const char *packet_type(const unsigned int pkttype)
{
    static char buffer[16];
    switch (pkttype) {
    case PACKET_HOST:      return "PACKET_HOST";
    case PACKET_BROADCAST: return "PACKET_BROADCAST";
    case PACKET_MULTICAST: return "PACKET_MULTICAST";
    case PACKET_OTHERHOST: return "PACKET_OTHERHOST";
    case PACKET_OUTGOING:  return "PACKET_OUTGOING";
    default:
        snprintf(buffer, sizeof buffer, "0x%02x", pkttype);
        return (const char *)buffer;
    }
}

static void fhex(FILE *const out,
                 const char *const before,
                 const char *const after,
                 const void *const src, const size_t len)
{
    const unsigned char *const data = src;
    size_t i;

    if (len < 1)
        return;

    if (before)
        fputs(before, out);

    for (i = 0; i < len; i++)
        fprintf(out, " %02x", data[i]);

    if (after)
        fputs(after, out);
}

int main(int argc, char *argv[])
{
    struct sockaddr_ll  addr;
    socklen_t           addrlen;
    unsigned char       data[2048];
    ssize_t             n;
    int                 socketfd, flag;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s interface\n", argv[0]);
        fprintf(stderr, "\n");
        return 1;
    }

    if (install_done(SIGINT) ||
        install_done(SIGHUP) ||
        install_done(SIGTERM)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return 1;
    }

    socketfd = rawpacket_socket(ETH_P_ALL, argv[1], NULL);
    if (socketfd == -1) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return 1;
    }

    flag = 1;
    if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag)) {
        fprintf(stderr, "Cannot set REUSEADDR socket option: %s.\n", strerror(errno));
        close(socketfd);
        return 1;
    }

    if (setsockopt(socketfd, SOL_SOCKET, SO_BINDTODEVICE, argv[1], strlen(argv[1]) + 1)) {
        fprintf(stderr, "Cannot bind to device %s: %s.\n", argv[1], strerror(errno));
        close(socketfd);
        return 1;
    }

    while (!done) {

        memset(data, 0, sizeof data);
        memset(&addr, 0, sizeof addr);
        addrlen = sizeof addr;
        n = recvfrom(socketfd, &data, sizeof data, 0,
                     (struct sockaddr *)&addr, &addrlen);
        if (n == -1) {
            if (errno == EINTR)
                continue;
            fprintf(stderr, "Receive error: %s.\n", strerror(errno));
            break;
        }

        printf("Received %d bytes:\n", (int)n);
        printf("\t    Protocol: %s\n", protocol_name(htons(addr.sll_protocol)));
        printf("\t   Interface: %d\n", (int)addr.sll_ifindex);
        printf("\t Header type: %s\n", header_type(addr.sll_hatype));
        printf("\t Packet type: %s\n", packet_type(addr.sll_pkttype));
        fhex(stdout, "\t     Address:", "\n", addr.sll_addr, addr.sll_halen);
        fhex(stdout, "\t        Data:", "\n", data, n);
        printf("\n");

        fflush(stdout);
    }

    shutdown(socketfd, SHUT_RDWR);
    close(socketfd);
    return 0;
}

要进行编译,您可以使用
gcc -O2 receiver.c -o receiver
gcc -O2 sender.c -o sender

在不带参数的情况下运行,或者与-h一起运行,以查看二者的用法。 sender仅发送一个数据包。 receiver侦听指定接口(interface)(处于混杂模式),直到您中断它(Ctrl + C)或向其发送TERM信号。

在回送接口(interface)上的一个虚拟终端中启动接收器:
sudo ./receiver lo

在同一台计算机上的另一个虚拟终端中,运行
sudo ./sender lo FF:FF:FF:FF:FF:FF '_The contents of a 64-byte Ethernet frame_'

将输出(添加换行符和缩进以便于理解)
Sent 64 bytes: ff ff ff ff ff ff
               00 00 00 00 00 00
               88 a8 e0 01
               81 00 e0 02
               08 00
               5f 54 68 65 20 63 6f 6e
               74 65 6e 74 73 20 6f 66
               20 61 20 36 34 2d 62 79
               74 65 20 45 74 68 65 72
               6e 65 74 20 66 72 61 6d
               65 5f

但是,在接收器终端中,我们看到了(添加了换行符和缩进):
Received 64 bytes:
    Protocol: ETH_P_ALL
   Interface: 1
 Header type: ATPHRD_LOOP: Loopback
 Packet type: PACKET_OUTGOING
     Address: 00 00 00 00 00 00
        Data: ff ff ff ff ff ff
              00 00 00 00 00 00
              88 a8 e0 01
              81 00 e0 02
              08 00
              5f 54 68 65 20 63 6f 6e
              74 65 6e 74 73 20 6f 66
              20 61 20 36 34 2d 62 79
              74 65 20 45 74 68 65 72
              6e 65 74 20 66 72 61 6d
              65 5f

Received 60 bytes:
    Protocol: ETH_P_8021Q (802.1Q VLAN)
   Interface: 1
 Header type: ATPHRD_LOOP: Loopback
 Packet type: PACKET_MULTICAST
     Address: 00 00 00 00 00 00
        Data: ff ff ff ff ff ff
              00 00 00 00 00 00
              81 00 e0 02
              08 00
              5f 54 68 65 20 63 6f 6e
              74 65 6e 74 73 20 6f 66
              20 61 20 36 34 2d 62 79
              74 65 20 45 74 68 65 72
              6e 65 74 20 66 72 61 6d
              65 5f

第一个,PACKET_OUTGOING,被捕获为传出;它表明发送数据包时内核没有消耗任何报头。

第二个数据包PACKET_MULTICAST在到达时被捕获。 (由于以太网地址为FF:FF:FF:FF:FF:FF,因此它是一个多播数据包。)

如您所见,后一个数据包仅包含802.1Q VLAN header (即客户端VLAN),内核已消耗了802.1AD服务VLAN标记。

以上至少确认了环回接口(interface)的场景。使用原始数据包接口(interface),内核将使用802.1AD VLAN header (紧随接收者地址之后的那个)。如果在接收方旁边使用tcpdump -i eth0,则可以看到libpcap正在将使用的 header 重新插入到数据包中!

回送接口(interface)有点特殊,因此让我们使用虚拟机重做测试。我碰巧正在运行最新的Xubuntu 14.04(所有更新已于2014-06-28安装,Ubuntu 3.13.0-29-generiC#53 x86_64内核已安装)。发件人硬件地址为08 00 00 00 00 02,接收者为08 00 00 00 00 01,两者均连接到内部网络,没有其他人在场。

(同样,我在输出中添加换行符和缩进,以使其更易于阅读。)

发送方,在虚拟机2上:
sudo ./sender eth0 08:00:00:00:00:01 '_The contents of a 64-byte Ethernet frame_'

Sent 64 bytes: 08 00 00 00 00 01
               08 00 00 00 00 02
               88 a8 e0 01
               81 00 e0 02
               08 00
               5f 54 68 65 20 63 6f 6e
               74 65 6e 74 73 20 6f 66
               20 61 20 36 34 2d 62 79
               74 65 20 45 74 68 65 72
               6e 65 74 20 66 72 61 6d
               65 5f

虚拟机1上的接收器:
sudo ./receiver eth0

Received 60 bytes:
    Protocol: ETH_P_8021Q (802.1Q VLAN)
   Interface: 2
 Header type: ARPHRD_ETHER: Ethernet 10Mbps
 Packet type: PACKET_HOST
     Address: 08 00 00 00 00 02
        Data: 08 00 00 00 00 01
              08 00 00 00 00 02
              81 00 e0 02
              08 00
              5f 54 68 65 20 63 6f 6e
              74 65 6e 74 73 20 6f 66
              20 61 20 36 34 2d 62 79
              74 65 20 45 74 68 65 72
              6e 65 74 20 66 72 61 6d
              65 5f

如您所见,结果与回送情况基本相同。特别地,802.1AD服务VLAN标记在接收时被消耗。 (您可以使用tcpdump或wirehark来比较接收到的数据包:libpcap显然会将已使用的VLAN标记包重新插入到数据包中。)

如果您的内核最近(在2013年4月添加了libpcap source file for Linux),则可以使用以下步骤在收件人上配置802.1AD VLAN:
sudo modprobe 8021q

sudo ip link add link eth0 eth0.service1 type vlan proto 802.1ad id 1

eth0上接收将接收所有数据包,但在eth0.service1上仅接收那些具有802.1ID VLAN标签且VLAN ID为1的数据包。它不会捕获具有相同VLAN ID的具有802.1Q VLAN标签的帧,这意味着您可以在同时接收802.1AD和802.1Q VLAN。

我本人并不仅仅相信以上测试。我创建了多个802.1AD和802.1Q VLAN,每个VLAN分别具有单独的receive实例,并更改了数据包头(不仅是sender.c中tci()调用中的服务(第一个)tci()和客户端(第二个)rawpacket_qinq(),以更改服务和客户端VLAN ID,但也更改了rawpacket.h,以验证802.1AD(88a8)和802.1Q(8100)VLAN header 在接收时是否正确路由)。一切都很漂亮,没有任何打h。

综上所述:

给定最新的Linux内核版本,以太网帧将在接收时由Linux内核(由8021q模块)正确路由,包括用于具有相同VLAN ID的802.1AD和802.1Q的单独VLAN接口(interface)。即使未配置任何VLAN,内核也会消耗用于路由的VLAN header 。

有什么问题吗

关于c - Linux : When sending Ethernet frames the ethertype is being re-written,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24355597/

有关c - Linux : When sending Ethernet frames the ethertype is being re-written的更多相关文章

  1. ruby-on-rails - Rails 单表继承 : How to override the value written to the type field - 2

    在我的系统中,我已经定义了STI。Dog继承自Animal,在animals表中有一个type列,其值为"Dog"。现在我想让SpecialDog继承自dog,只是为了在某些特殊情况下稍微修改一下行为。数据还是一样。我需要通过SpecialDog运行的所有查询,以返回数据库中类型为Dog的值。我的问题是因为我有一个type列,rails将WHERE"animals"."type"IN('SpecialDog')附加到我的查询中,所以我不能获取原始的Dog条目。所以我想要的是以某种方式覆盖rails在通过SpecialDog访问数据库时使用的值,使其表现得像Dog。有没有办法覆盖用于类型

  2. 【Linux操作系统】——网络配置与SSH远程 - 2

    Linux操作系统——网络配置与SSH远程安装完VMware与系统后,需要进行网络配置。第一个目标为进行SSH连接,可以从本机到VMware进行文件传送,首先需要进行网络配置。1.下载远程软件首先需要先下载安装一款远程软件:FinalShell或者xhell7FinalShellxhell7FinalShell下载:Windows下载http://www.hostbuf.com/downloads/finalshell_install.exemacOS下载http://www.hostbuf.com/downloads/finalshell_install.pkg2.配置CentOS网络安装好

  3. Linux磁盘分区中物理卷(PV)、卷组(VG)、逻辑卷(LV)创建和(LVM)管理 - 2

    文章目录一基础定义二创建逻辑卷2-1准备物理设备2-2创建物理卷2-3创建卷组2-4创建逻辑卷2-5创建文件系统并挂载文件三扩展卷组和缩减卷组3-1准备物理设备3-2创建物理卷3-3扩展卷组3-4查看卷组的详细信息以验证3-5缩减卷组四扩展逻辑卷4-1检查卷组是否有可用的空间4-2扩展逻辑卷4-3扩展文件系统五删除逻辑卷5-1备份数据5-2卸载文件系统5-3删除逻辑卷5-4删除卷组5-5删除物理卷六LVM逻辑卷缩容6-1缩容注意事项6-2标准缩容步骤一基础定义LVM,LogicalVolumeManger,逻辑卷管理,Linux磁盘分区管理的一种机制,建立在硬盘和分区上的一个逻辑层,提高磁盘分

  4. ruby - 如何在 Ruby 中获取 linux 系统信息 - 2

    如何在Ruby中获取linux系统(这必须适用于Fedora、Ubuntu等)的软件/硬件信息? 最佳答案 Chef背后的优秀人才,拥有一颗名为Ohai的优秀gemhttps://github.com/opscode/ohai以散列形式返回系统信息,例如操作系统、内核、规范、fqdn、磁盘、空间、内存、用户、接口(interface)、sshkey等。它非常完整,非常好。它还会安装命令行二进制文件(也称为ohai)。 关于ruby-如何在Ruby中获取linux系统信息,我们在Stack

  5. ruby - rbenv:在 Linux Mint 上找不到 gem 命令 - 2

    我在LinuxMint17.2上。我最近使用apt-getpurgeruby​​删除了ruby​​。然后我安装了rbenv然后rbenvinstall2.3.0所以现在,~/.rbenv/versions/2.3.0/bin/ruby存在。但是现在,我无法执行geminstallrubocop。我明白了:$geminstallrubocoprbenv:gem:commandnotfoundThe`gem'commandexistsintheseRubyversions:2.3.0但是我可以~/.rbenv/versions/2.3.0/bin/geminstallrubocop。但是,

  6. ruby - 在 Linux 上编译 Ruby 1.9.2 所需的先决条件? - 2

    我是Ruby和RoR的新手。我有一个带有Ubuntu镜像的干净Linode实例,我想从源代码编译Ruby而不是使用apt-get。我已经在谷歌上搜索了执行此操作的说明,但经过一些尝试后,当我尝试运行一些教程示例时,我不断收到有关缺少zlib和其他一些包的错误。任何人都可以给我详细的说明(或链接),教我如何在从源代码编译Ruby之前安装必要的必备包吗?我的目的是编译Ruby的最新稳定版本,然后安装Rubygems和Rails。提前感谢您的帮助!!! 最佳答案 Thisblogpost涵盖从源代码编译ruby​​所需的包和安装过程;它引

  7. Linux网络编程必备的POSIX API的细节 - 2

    目录POSIXAPI大集合五元组三次握手的过程,内核协议栈分析listen函数DDOS攻击,洪水攻击DDOS攻击的应对措施数据发送 怎么保证顺序?如何保证包地顺序到达(序号+确认应答机制+重传)TCP断开连接的过程问题1.大量的CLOSE_WAIT+FIN_WAIT2是为啥?time_wait状态存在的原因?POSIXAPI大集合五元组(sip,sport,dip,dport,protocol)三次握手的过程,内核协议栈分析内核协议栈中是有内核数据结构的.  我们send/write数据,都是先发送到内核协议栈中,然后由内核协议栈封装发送到物理介质中传输到对端的对端的接收过程也是经有内核协议栈

  8. Linux export 命令及如何删除export设置的环境变量 - 2

    背景:Linuxexport命令用于设置或显示环境变量。在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅限于该次登陆操作。语法:export[-fnp][变量名称]=[变量设置值]参数说明:-f 代表[变量名称]中为函数名称。-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。-p 列出所有的shell赋予程序的环境变量。实例:列出当前所有的环境变量#export-p//列出当前的环境变量值定义环境变量赋值#exportMYENV=7//定义环境变量并赋值添加环境变量:默认保存在

  9. Linux操作系统CentOS7安装Nginx[详细版] - 2

    Nginx安装1.官网下载Nginx2.使用XShell和Xftp将压缩包上传到Linux虚拟机中3.解压文件nginx-1.20.2.tar.gz4.配置nginx5.启动nginx6.拓展(修改端口和常用命令)(一)修改nginx端口(二)常用命令1.官网下载Nginxhttp://nginx.org/en/download.html这里我下载的是1.20.2版本,大家按需下载对应稳定版即可2.使用XShell和Xftp将压缩包上传到Linux虚拟机中没有XShell可以参考《Linux操作系统CentOS7连接XShell》3.解压文件nginx-1.20.2.tar.gz1)检查是否存

  10. 【Linux】初识Linux --指令Ⅰ - 2

    Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法,Linux…感兴趣就关注我吧!你定不会失望。目录1.ls显示当前目录下的文件内内容2.pwd-显示用户当前所在的目录3.cd-改变工作目录。将当前工作目录改变到指定的目录下1.cd-回到上一次待的工作空间2.cd..返回上一层目录1.相对路径:cd../aurora2.绝对路径:cd/home/aurora/lesson1/aurora3.cd~进入用户家目录4.cd/进入root目录4.mkdir-新建目录5.rmdir/rm-删除1.rmdir删除空文件夹2.rm删除1.rm-f2.rm-i3.rm-r1.ls显示当前目

随机推荐